软件License授权原理

概述

在 ToB 软件交付或一些收费的桌面软件(如 IDEA、Navicat)中,我们经常会接触到 License(许可证)

通俗的讲,License 就是软件的“驾驶证”。当你购买了软件,厂商发给你一个 License 文件(或者激活码),软件读取这个文件,验证你是否有权使用,以及可以使用多久、可以使用哪些功能。

虽然 SaaS 模式(账号登录验证)越来越流行,但在很多私有化部署、离线环境或对数据隐私要求极高的场景下,License 依然是目前最主流的授权保护方式。

今天我们就来聊聊 License 是怎么设计出来的,以及它是如何一步步升级来对抗破解者的。

License 的核心能力与要求

设计一个合格的 License 系统,通常需要满足以下几个核心诉求:

  1. 完整性(防篡改):用户不能随意修改 License 里的内容(比如把过期时间从 2025 年改成 2099 年)。
  2. 唯一性(防复制/机器绑定):A 客户购买的 License,不能直接拷贝给 B 客户使用。
  3. 时效性(过期控制):能够精确控制授权的开始和结束时间。
  4. 功能控制(按需授权):可以限制用户只能使用标准版功能,或者解锁高级版功能。

为了实现这些目标,我们的 License 实现方案经历了几个版本的迭代。

迭代一:明文配置文件(裸奔版)

这是最原始的思路。我们定义一个 JSON 文件作为 License,里面记录授权信息。

License 文件内容:

{
  "company": "字节跳动",
  "expireTime": "2025-12-31",
  "modules": ["user", "order"]
}

验证逻辑:
程序读取文件,解析 JSON,判断 CurrentTime > expireTime,如果是则停止服务。

存在的问题:
这就好比把家里的钥匙放在大门口的地毯下。任何懂一点电脑的用户,用记事本打开这个文件,把 2025 改成 2999,破解就完成了。

结论: 毫无安全性,只能防君子不能防小人。

迭代二:对称加密(虽然加密了,但钥匙在锁上)

为了防止用户修改文件,我们决定对文件内容进行加密。使用 AES 或 DES 等对称加密算法。

生成逻辑(厂商端):

  1. 准备一个密钥 SecretKey = "123456"
  2. 使用该密钥将 JSON 内容加密成乱码字符串。

License 文件内容:
U2FsdGVkX1+... (一串看不懂的密文)

验证逻辑(客户端):

  1. 代码中硬编码写死密钥 SecretKey = "123456"
  2. 软件启动时,用密钥解密 License 文件。
  3. 解密成功则校验时间,解密失败则认为 License 非法。

存在的问题:
对称加密的核心缺陷在于:加密和解密用的是同一个密钥
为了让软件能运行,你必须把密钥写在代码里。破解者只需要反编译你的 Jar 包或 exe 文件,全局搜索一下 "AES" 或者 "Key" 相关的字符串,就能拿到密钥。拿到密钥后,他就可以自己生成任意有效期的 License 了。

结论: 安全性略有提升,但对于稍有经验的逆向工程师来说,形同虚设。

迭代三:非对称加密 + 数字签名(行业标准版)

为了解决“密钥必须下发给客户端”的问题,我们需要引入 非对称加密(RSA)数字签名 技术。这是目前大多数 License 系统的核心原理。

原理:
非对称加密有一对密钥:私钥(Private Key)公钥(Public Key)

  • 私钥:保存在厂商手里,绝不泄露,用于签名
  • 公钥:埋在客户端代码里,用于验签

生成逻辑(厂商端):

  1. 准备明文授权信息(JSON)。
  2. 对 JSON 内容进行 Hash 计算(如 MD5 或 SHA256),得到摘要。
  3. 使用 私钥 对摘要进行加密,生成 “数字签名”
  4. 明文信息 + 数字签名 打包发给用户,这就是 License 文件。

验证逻辑(客户端):

  1. 软件读取 License 文件,拆解出 明文信息数字签名
  2. 客户端使用代码里内置的 公钥 解密 数字签名,得到 摘要A(如果解密失败,说明签名被篡改)。
  3. 客户端对 明文信息 进行同样的 Hash 计算,得到 摘要B
  4. 对比 摘要A摘要B
    • 如果相等:说明明文没有被篡改,且确实是由拥有私钥的厂商签发的。
    • 如果不等:说明被篡改了。

是如何解决之前的问题的?
破解者手里只有公钥。公钥只能用来解密(验证),不能用来加密(生成签名)。所以破解者即使改了明文里的过期时间,因为他没有私钥,无法生成对应的新签名,软件校验时就会发现 摘要A != 摘要B,从而拒绝启动。

新的问题:
虽然防篡改解决了,但防复制没解决。A 公司买了一个 License,把它发给 B 公司,B 公司也能通过验证(因为 License 本身是合法的)。

迭代四:机器特征绑定(进阶版)

为了解决“一证多用”的问题,我们需要把 License 和运行软件的机器硬件绑定。

生成逻辑(厂商端):

  1. 要求用户在部署服务器上运行一个脚本,获取服务器的唯一指纹(Machine ID)。指纹通常由 CPU序列号 + 网卡MAC地址 + 主板序列号 组合而成。
  2. 用户将 Machine ID 发给厂商。
  3. 厂商将 Machine ID 写入到 License 的明文 JSON 中。
    {
     "expireTime": "2025-12-31",
     "machineId": "CPU-BF31-MAC-8821" // 绑定机器
    }
  4. 使用私钥生成签名。

验证逻辑(客户端):

  1. 使用公钥验签(确保文件没被改过)。
  2. 新增步骤:软件获取当前服务器的硬件信息,计算出本地的 Local Machine ID
  3. Local Machine ID 与 License 文件中的 machineId 对比。如果不一致,说明 License 是从别的机器拷贝过来的,拒绝启动。

最终版本的局限性:为什么还是防不住?

到了“迭代四”,我们已经拥有了一个包含 RSA 签名校验 + 机器绑定 + 有效期控制 的完善 License 系统。这已经能防住绝大部分普通用户和初级黑客。

但是,它依然不是绝对安全的。为什么?

因为代码最终是在用户的机器上运行的,客户端是没有秘密的

  1. 公钥替换攻击
    破解者虽然拿不到你的私钥,但他可以生成一对自己的“私钥B”和“公钥B”。他修改你的客户端程序(比如修改 Jar 包),把你内置的“公钥A”换成他的“公钥B”。然后他就可以用“私钥B”随意签发 License 了。

  2. 暴力修改判断逻辑(爆破)
    最终的代码里,总会有类似这样的一行判断:

    if (license.verify()) {
        run();
    } else {
        exit();
    }

    破解者不需要搞懂你的加密算法,他只需要通过反编译工具(如 Javassist)找到这行代码,把它改成:

    if (true) { // 强制为真
        run();
    }

    或者直接删除 else 分支。这就是所谓的“暴力破解”。

应对方案(且战且退):
为了对抗上述攻击,厂商通常会引入 代码混淆(Obfuscation)加壳 技术,增加反编译的难度,或者在程序中埋入多个隐蔽的校验点。但这本质上是一场“猫鼠游戏”,只能增加破解成本,无法从根本上杜绝破解。

总结

License 的本质不是为了实现“绝对安全”,而是为了提高破解门槛

一个成熟的 License 方案(RSA + 签名 + 机器绑定)足以挡住 99% 的普通用户和非专业人士,保障厂商的商业利益。对于那 1% 精通逆向工程的高手,单纯靠技术手段是防不住的,这时候就需要法律手段(律师函警告)来补位了。

订阅评论
提醒
guest
0 评论
最新
最旧
内联反馈
查看所有评论
0
希望看到您的想法,请发表评论。x