- 私钥(private_key.pem):用于签名许可证
- AES密钥(aes_key.bin):用于加密许可证内容
- 公钥(public_key.pem):用于验证许可证签名
- AES密钥(aes_key.bin):用于解密许可证内容
可以公开的原因:
- 公钥密码学的基础就是公钥可以公开
- 即使泄露,攻击者也无法:
- ❌ 生成新的有效许可证(需要私钥签名)
- ❌ 修改现有许可证(修改后签名会失效)
- ❌ 伪造许可证(无法生成有效签名)
建议:
- 公钥可以随客户端程序一起分发
- 可以嵌入到程序中,或作为配置文件
如果泄露,攻击者可以:
- ✅ 解密许可证内容(看到设备ID、过期时间等信息)
- ✅ 修改许可证内容(但无法重新签名)
- ❌ 无法伪造新的有效许可证(需要私钥签名)
风险:
- 虽然无法伪造新许可证,但可以:
- 查看许可证中的敏感信息
- 尝试修改许可证(虽然签名会失效,但可能用于其他攻击)
建议:
- 不要明文存储 AES 密钥
- 可以考虑使用代码混淆
- 或者使用更安全的方案(见下文)
如果泄露,攻击者可以:
- ✅ 伪造任意许可证
- ✅ 为任意设备生成有效许可证
- ✅ 绕过所有安全措施
建议:
- 绝对不要分发私钥
- 私钥只存在于服务器端
- 使用密钥管理服务(如 AWS KMS、HashiCorp Vault)
- 定期轮换密钥
将 AES 密钥嵌入到程序中,并使用代码混淆技术保护:
// 在编译时嵌入 AES 密钥
//go:embed aes_key.bin
var embeddedAESKey []byte
// 使用代码混淆工具(如 garble)编译优点:
- 不需要单独分发 AES 密钥文件
- 代码混淆可以增加逆向难度
缺点:
- 仍然可能被逆向工程
- 需要额外的构建步骤
在许可证中包含用公钥加密的 AES 密钥:
- 每次生成许可证时,生成一个随机的 AES 密钥
- 用公钥加密这个 AES 密钥
- 将加密的 AES 密钥包含在许可证中
- 客户端用私钥解密 AES 密钥(但客户端没有私钥...)
问题: 这个方案需要客户端有私钥,但私钥不能分发。
移除 AES 加密,只使用 RSA 签名:
- 许可证内容:JSON(明文)+ RSA 签名
- 客户端:只需要公钥验证签名
优点:
- 只需要分发公钥
- 更简单,更安全
缺点:
- 许可证内容是明文的(但签名仍然有效)
- 攻击者可以看到许可证内容,但无法修改(签名会失效)
对于高安全要求的场景,使用在线验证:
- 客户端只需要公钥验证签名
- 所有验证逻辑在服务器端
- 服务器端可以实时检查许可证状态
当前方案(公钥 + AES 密钥):
- ✅ 可以防止伪造许可证(需要私钥)
- ✅ 可以防止修改许可证(签名会失效)
⚠️ 无法防止查看许可证内容(如果有 AES 密钥)⚠️ 无法防止重放攻击(如果许可证被复制)
安全等级:中等
适用场景:
- 一般商业软件
- 不需要极高安全性的场景
- 可以接受一定程度的逆向工程
不适用场景:
- 高安全要求的软件
- 需要防止许可证内容泄露的场景
- 需要防止许可证复制的场景
-
公钥可以公开分发 ✅
- 可以嵌入到程序中
- 可以作为配置文件
-
AES 密钥需要保护
⚠️ - 考虑使用代码混淆
- 或者嵌入到程序中
- 不要明文存储在配置文件中
-
私钥绝对不能泄露 🚨
- 只存在于服务器端
- 使用密钥管理服务
- 定期备份和轮换
-
使用在线验证(推荐)
- 所有验证逻辑在服务器端
- 可以实时检查许可证状态
- 可以防止许可证复制
-
使用代码混淆
- 保护 AES 密钥
- 增加逆向难度
-
定期轮换密钥
- 定期更换密钥对
- 通知用户更新许可证
当前方案的安全性:
- 公钥泄露:无风险 ✅
- AES 密钥泄露:中等风险
⚠️ (可以查看内容,但无法伪造) - 私钥泄露:极高风险 🚨(可以伪造任意许可证)
建议:
- 对于一般场景,当前方案足够安全
- 公钥可以公开,AES 密钥需要一定保护
- 对于高安全要求,考虑使用在线验证或代码混淆