LicenseManager v1.2.0 - 统一许可证管理工具
🎯 概述
LicenseManager 是一个功能完整的许可证管理系统,用于生成、验证和管理软件许可证。v1.2.0 版本新增 Ed25519 非对称签名(v2 许可证格式),客户端只需内嵌公钥即可验证许可证,从根本上解决了对称加密下"验证密钥即可伪造许可证"的问题。
✨ 主要特性
- 🔏 Ed25519 签名(v2,推荐):私钥签发、公钥验证,客户端无法伪造许可证
- 🔐 AES加密(v1,兼容):支持16/24/32字节密钥,使用随机IV
- 📱 设备绑定:基于设备UUID的许可证绑定机制
- ⏰ 到期管理:支持许可证过期时间设置和剩余天数计算
- 🛠️ 统一工具:一个命令行工具包含所有功能(create/verify/show/checkuuid/genkeys)
- 📊 多种输出:支持文本和JSON格式输出
- 🔍 详细信息:完整的许可证信息查看功能
🚀 快速开始
安装
# 克隆项目
git clone https://github.com/xinjiayu/LicenseManager.git
cd LicenseManager
# 编译统一工具
go build -o licensemanager ./cmd/licensemanager
# 或者直接运行
go run ./cmd/licensemanager help
基本使用(v2 Ed25519 签名,推荐)
# 1. 生成 Ed25519 密钥对(一次性操作,私钥妥善保管)
./licensemanager genkeys -o ./keys
# 2. 获取设备UUID(发给签发方)
./licensemanager checkuuid
# 3. 签发许可证(签发方持有私钥)
./licensemanager create -f config.json -privkey keys/license_private_key.pem -o app.lic
# 4. 验证许可证(客户端只需公钥)
./licensemanager verify -f app.lic -pubkey keys/license_public_key.pem
# 5. 查看许可证信息
./licensemanager show -f app.lic -pubkey keys/license_public_key.pem -format json
基本使用(v1 AES 对称加密,兼容旧版)
./licensemanager create -f config.json -k "your-16-byte-key"
./licensemanager verify -f app.lic -k "your-16-byte-key"
./licensemanager show -f app.lic -k "your-16-byte-key"
🔏 Ed25519 非对称签名(v2 格式)
为什么需要非对称签名
v1 使用 AES 对称加密:客户端要验证许可证就必须内嵌解密密钥,而持有解密密钥就意味着可以自己加密生成任意许可证——逆向客户端二进制拿到密钥后即可无限伪造。v2 使用 Ed25519 签名:私钥只存在于签发方,客户端仅内嵌公钥;公钥只能验证、不能签发,任何对许可证内容的篡改(改到期时间、换设备UUID、换应用名)都会导致验签失败。
v2 许可证文件格式
{
"version": 2,
"alg": "Ed25519",
"issued_at": "2026-09-04T00:23:02+08:00",
"payload": "base64编码的授权信息JSON",
"signature": "base64编码的Ed25519签名(覆盖payload解码后的原始字节)"
}
说明:v2 格式的授权内容是"签名保护的明文"而非加密密文。许可证需要的是防篡改而非保密——被授权设备上本就能读到这些信息。如确有保密需求,可在签名前自行对 payload 做一层加密。
密钥管理建议
- 私钥(
license_private_key.pem):只在签发许可证的机器上保存,权限 0600,可离线保管
- 公钥(
license_public_key.pem):内嵌到客户端程序(硬编码常量或随二进制分发)
- 密钥遗失/泄露时的处理:换新密钥对重新签发,客户端升级时更新内嵌公钥
Go API(v2 签名许可证)
package main
import (
"fmt"
"log"
"github.com/xinjiayu/LicenseManager"
)
func main() {
// 签发端:生成密钥对(一次性)
privatePEM, publicPEM, err := LicenseManager.GenerateSigningKeyPair()
if err != nil {
log.Fatal(err)
}
// privatePEM 妥善保存,publicPEM 内嵌到客户端 ...
// 签发端:生成签名许可证
if err := LicenseManager.EncryptLicSigned("config.json", "app.lic", privatePEM); err != nil {
log.Fatal(err)
}
// 客户端:验证签名许可证(只需公钥)
ok, err := LicenseManager.ValidAppLicSigned("app.lic", publicPEM)
if err != nil || !ok {
log.Fatalf("许可证无效: %v", err)
}
// 客户端:获取详细信息
info, err := LicenseManager.GetLicenseInfoSigned("app.lic", publicPEM)
if err != nil {
log.Fatal(err)
}
fmt.Printf("应用: %s, 状态: %s, 剩余: %d天\n", info.AppName, info.Status, info.DaysRemaining)
}
📖 详细功能(v1/v2 通用说明)
1. 创建许可证 (create)
从JSON配置文件生成许可证文件,推荐使用 v2 签名格式。
# v2 Ed25519签名格式(推荐)
licensemanager create -f config.json -privkey license_private_key.pem
# v1 AES加密格式(已不推荐,-k 必须显式传入16/24/32字节密钥)
licensemanager create -f config.json -k "your-32-byte-secret-key" -o production.lic
配置文件格式:
{
"AppName": "YourApp",
"AppCompany": "Your Company Ltd",
"AppUUID": "app-unique-identifier",
"ObjUUID": "device-uuid-from-checkuuid-command",
"AuthorizedName": "客户公司名称",
"LimitedTime": "20261231",
"LicenseID": "LIC202501001",
"LicenseQuantity": 100
}
2. 验证许可证 (verify)
验证许可证文件的有效性,包括:
- 签名验证(v2)或解密校验(v1)
- 设备UUID匹配验证
- 过期时间检查(按UTC)
- 系统时间回拨检测
# v2 签名格式(推荐)
licensemanager verify -f app.lic -pubkey license_public_key.pem
# v1 AES格式(已不推荐)
licensemanager verify -f app.lic -k "your-16-byte-key"
3. 显示许可证信息 (show/info)
获取许可证的详细信息,支持文本和JSON格式。
# v2 签名格式(推荐)
licensemanager show -f app.lic -pubkey license_public_key.pem
# JSON格式(适合API集成)
licensemanager show -f app.lic -pubkey license_public_key.pem -format json
JSON输出示例:
{
"app_name": "YourApp",
"app_company": "Your Company Ltd",
"authorized_name": "客户公司名称",
"limited_time": "20261231",
"license_id": "LIC202501001",
"license_quantity": 100,
"status": "valid",
"days_remaining": 365,
"is_valid": true,
"error_message": ""
}
4. 获取设备UUID (checkuuid/uuid)
获取当前设备的唯一标识符,用于生成设备绑定的许可证。
licensemanager checkuuid
# 输出: F6235A40-C9E2-5681-B236-ED9C4C15E58D
5. 生成签名密钥对 (genkeys/genkey)
生成 Ed25519 密钥对,用于 v2 签名格式许可证的签发与验证。
licensemanager genkeys -o ./keys
# 输出:
# keys/license_private_key.pem 私钥(签发用,权限0600,妥善保管)
# keys/license_public_key.pem 公钥(内嵌到客户端验证)
🔧 API 使用
以下为 v1 AES 格式的 API。新接入请使用上文 v2 签名 API(ValidAppLicSigned 等),
v1 API 仅为兼容存量许可证保留。
Go API 集成
package main
import (
"fmt"
"github.com/xinjiayu/LicenseManager"
)
func main() {
// 获取许可证信息
info, err := LicenseManager.GetLicenseInfo("app.lic", "your-16-byte-key")
if err != nil {
panic(err)
}
if info.IsValid {
fmt.Printf("应用: %s, 剩余: %d天\n", info.AppName, info.DaysRemaining)
} else {
fmt.Printf("许可证无效: %s\n", info.ErrorMessage)
}
// 验证许可证
isValid, err := LicenseManager.ValidAppLic("app.lic", "your-16-byte-key")
if err != nil {
fmt.Printf("验证失败: %v\n", err)
return
}
fmt.Printf("许可证状态: %v\n", isValid)
}
Web API 示例
package main
import (
"encoding/json"
"net/http"
"github.com/xinjiayu/LicenseManager"
)
func licenseHandler(w http.ResponseWriter, r *http.Request) {
info, err := LicenseManager.GetLicenseInfo("app.lic", "secret-key")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(info)
}
func main() {
http.HandleFunc("/license", licenseHandler)
http.ListenAndServe(":8080", nil)
}
🔐 安全特性
- Ed25519签名(v2):私钥签发、公钥验证,客户端无法伪造或篡改许可证
- 时间回拨防护:本地记录每个许可证最后通过验证的时间(只增不减),系统时间被明显回拨(超过48小时)时拒绝验证;状态文件损坏不阻塞验证
- 设备绑定:许可证与特定设备UUID绑定
- 到期管理:到期比较与剩余天数统一按UTC计算,避免跨时区偏差
- 完整性检查:签名验证(v2)与PKCS7填充校验(v1)确保数据完整性
- AES加密(v1,已弃用):随机IV;因客户端内嵌的解密密钥可被用于伪造许可证,v1仅保留用于兼容存量许可证,新签发请使用v2
已知局限(离线许可证的固有边界)
- 防回拨是尽力而为:状态文件(
~/.licensemanager/clock_state.json)可被删除;从未在本机验证过的过期许可证回拨后仍可通过。高价值场景建议配合在线激活。
- machineid可被伪造:Windows注册表/部分平台文件型machine-id可被篡改或克隆。如需更强设备指纹,可自行组合硬件序列号、MAC等,或在
AppLicenseInfo.ObjUUID 中使用自采指纹。
- 公钥分发:内嵌在客户端二进制中的公钥可能被连锅端替换(攻击者同时替换许可证与公钥常量并重打包);混淆/加壳可提高门槛,根因解决需在线验证。
📁 项目结构
LicenseManager/
├── cmd/
│ └── licensemanager/ # 统一命令行工具
│ ├── main.go # 主程序入口
│ └── README.md # 工具使用说明
├── utils/
│ ├── aes.go # AES加密解密工具
│ └── aes_test.go # AES单元测试
├── examples/
│ └── license_info/ # 使用示例
├── lic.go # 核心许可证功能(v1 AES格式)
├── sign.go # Ed25519签名功能(v2签名格式)
├── clock.go # 系统时间回拨防护
├── lic_test.go # v1单元测试
├── lic_sign_test.go # v2签名单元测试
├── clock_test.go # 回拨防护单元测试
├── go.mod # Go模块定义
├── LICENSE # MIT许可证
└── README.md # 本文档
🧪 测试
# 运行全部单元测试(自包含,自动适配本机UUID,无需预先准备文件)
go test ./... -v
# 测试完整工作流程
go build -o licensemanager ./cmd/licensemanager
# 生成签名密钥对并测试v2签名流程
./licensemanager genkeys -o ./keys
./licensemanager create -f config.json -privkey keys/license_private_key.pem -o app.lic
./licensemanager verify -f app.lic -pubkey keys/license_public_key.pem
./licensemanager show -f app.lic -pubkey keys/license_public_key.pem -format json
# 测试v1 AES流程(兼容旧版,需显式传密钥)
./licensemanager create -f config.json -k "your-16-byte-key" -o v1.lic
./licensemanager verify -f v1.lic -k "your-16-byte-key"
🔄 迁移指南
从v1.x升级到v1.2.0
- API兼容:原有函数保持不变;
EncryptLic 已标记 Deprecated,请改用 EncryptLicToFile
- 文件兼容:v1许可证文件(含旧格式固定IV)仍可验证;
LicenseDisplayInfo 移除了内部字段 EncryptSalt,LicenseID/LicenseQuantity 已正式收进 AppLicenseInfo
- CLI变化:
-k 不再有默认弱密钥,必须显式传入;错误信息统一输出到 stderr;genkeys 默认拒绝覆盖已存在的密钥文件(需 -force 确认)
- 错误判定:验证失败的错误文案有调整(如"授权文件不适用于此设备"改为由
ErrDeviceMismatch 包装的新文案);请勿对错误做字符串匹配,改用 errors.Is 判定哨兵错误(ErrLicenseExpired/ErrDeviceMismatch/ErrInvalidSignature/ErrClockRollback 等)
- 推荐迁移:新签发许可证一律使用 v2 Ed25519 签名格式(
genkeys + -privkey/-pubkey),客户端只需内嵌公钥
从v1.0.x分散工具升级
# v1.0.x → v1.1.0+
cmd/CreateLic/main.go → licensemanager create
cmd/VerifyLic/main.go → licensemanager verify
cmd/ShowLicenseInfo/main.go → licensemanager show
cmd/checkuuid/main.go → licensemanager checkuuid
🤝 贡献
欢迎提交Issue和Pull Request!
📄 许可证
本项目使用MIT许可证。详见LICENSE文件。