离线激活这件事并不新。Windows 时代的光盘序列号、电话激活、硬件绑定;工业软件的加密狗;再到今天嵌入式 / Android 端算法 SDK 的 .reg 授权文件——核心问题始终是同一个:
在没有稳定联网校验的前提下,如何证明「这台设备有权使用这份软件」?
在线激活很简单:客户端上报身份,服务端查库,返回 token。离线场景则多了几道约束:
于是几乎所有离线方案都会收敛成三步:
采集设备指纹 → 生成设备身份(Device Code)→ 用私钥/密钥签发授权(License)
↑ ↓
运行时本地重算,与 License 内绑定身份比对
服务端(或厂商工具)只在「发卡」时出现一次;之后设备完全离线,靠本地校验。
早期 Windows / Office 的激活,表面上是「输入序列号」,背后同样是离线身份绑定:
| 环节 | Windows 类方案 | 现代离线算法 SDK |
|---|---|---|
| 身份 | 安装 ID(由硬件特征哈希得到) | Device Code / uu_code |
| 凭证 | 确认 ID / 产品密钥衍生数据 | .reg / .lic 授权文件 |
| 绑定 | 主板、磁盘、网卡等 | 包名、ANDROID_ID、机型、目录节点等 |
| 校验 | 本地算法 + 可选电话/联网 | 本地解析 License,比对设备码并验签/验摘要 |
| 迁移痛点 | 换硬件要重新激活 | 换包名、换签名、系统变更可能导致设备码变化 |
光盘序列号解决的是「你是否买过」;安装 ID 解决的是「你是否在同一台机器上」。
离线算法授权把「买过」固化成文件,把「同一台机器」固化成设备指纹——思路一脉相承。
下面描述的是一类常见实现(JNI 引擎 + 本地 .reg),便于理解「设备码从哪来、授权怎么验」。
应用在 Application 中异步预初始化 SDK:
getDeviceCode(context)。init(runtimePath, context, licenseContent);licenseContent 为 null 时,从约定路径读取 .reg(例如应用外部文件目录下的 .reg)。初始化结果用错误码表达:成功、授权为空、设备码不匹配、过期等。
设备码不是随意字符串,而是「指纹材料 → 结构化文本 → 混淆 → Base64」的结果。
指纹材料(示例)通常包括:
applicationId(Context.getPackageName())Settings.Secure.ANDROID_ID(Android 8+ 往往与签名密钥、用户相关)Build.MODEL / Build.PRODUCT 等机型信息lstat 节点信息(如 /system/bin、/vendor/bin、/data/local)这些材料被拼成中间串,形如:
SOB_DEVICEcom.SOB.health#2#2#2#3#9#1327105@version:2.0
可读含义大致是:
# 分隔的数字:目录节点等离散指纹@version:指纹协议版本再包一层 JSON:
{
"fileattr": "",
"uu_code": "YF_...#...@version:2.0"
}
随后做字符串混淆(按字符下标加减常量一类可逆变换),再 Base64。于是日志里会看到「很长一串像 Base64 的设备码」——无论激活前后,编码形态都一样;有的带 == 只是长度对齐填充,并不代表「激活态」。
.reg 里有什么.reg 同样是「混淆 + Base64」后的结构化数据,解码后类似:
{
"auth_days": 29,
"dkey": "501",
"device_code": "<当初发卡时绑定的设备码>",
"auth_date_time": "1775120635",
"md5": "<完整性摘要>"
}
要点:
device_code 是发卡那一刻的设备身份快照,写死在文件里。auth_days / auth_date_time 约束有效期。dkey、md5 以及 native 层可能存在的 AES / RSA 校验,用来防止「手改 JSON 再重新编码」。没有厂商侧密钥,只能做出「格式像」的文件,过不了完整校验——这和伪造 Windows 确认 ID 一样。
本地校验的关键日志形态往往是:
verifyDeviceCode local <本机现算设备码> user <License 内设备码> failed
逻辑很直白:
.reg 解出 user 设备码。因此:
把上述实现抽象一下,离线激活可以落成下面这张图:
┌──────────────┐ 指纹材料 ┌─────────────────┐
│ 硬件 / OS │ ───────────────► │ Device Fingerprint│
│ 应用身份 │ └────────┬────────┘
└──────────────┘ │
▼
┌─────────────────┐
│ Device Code │
│ (可展示给用户) │
└────────┬────────┘
用户把设备码发给厂商 │
▼
┌─────────────────┐
│ License Issuer │
│ (私钥 / 卡密) │
└────────┬────────┘
│ 下发 .reg / .lic
▼
┌──────────────┐ 本地重算并比对 ┌─────────────────┐
│ 运行时校验 │ ◄─────────────── │ License Store │
└──────────────┘ └─────────────────┘
设计时建议明确几件事:
指纹稳定性策略
applicationId、签名摘要纳入指纹。设备码展示形态
License 最小字段
错误码语义
迁移与售后
同一台物理设备上,常见坑位:
#52#... vs #2#...)已变,local ≠ user。lstat 节点一类因子时,系统更新可能导致数字段变化。排查建议:解码(或日志输出)当前 uu_code,与 .reg 内 device_code 解码结果并排对比,看是包名问题还是数字指纹段问题。
| 方案 | 优点 | 代价 |
|---|---|---|
| 纯离线文件授权 | 无网可用、集成简单 | 指纹漂移要重新发卡;防破解依赖混淆与密钥 |
| USB / 加密狗 | 与具体 App 包名解耦,换机插狗即可 | 硬件成本、驱动与丢失风险 |
| 在线激活 + 短期票据 | 吊销方便、可计费 | 依赖网络与服务可用性 |
| 混合(首次在线,日常离线缓存) | 较均衡 | 实现复杂,需处理时钟回拨等 |
工业现场、医疗设备上的算法 SDK,之所以仍大量采用「设备码 + .reg」,是因为部署环境天然离线,且售后可以接受「导出设备码 → 厂商发卡 → 拷回文件」的人工闭环——这和当年打电话念安装 ID、听确认 ID 是同一类业务。
角色:
人:身份特征的确定,也就是指纹,前面所谓的设备ID
签发机构:把人的指纹给到机构,机构给你颁发证书,内容包括:你的指纹,允许出关时长,盖章(防伪)
海关:校验签名,也就是看章是不是伪造的;不是伪造的对比你的指纹信息,发现指纹一致,按上面所显示的内容去执行允许出关








