做 licence 防破解踩的几个坑
给一个 Java 工具做了套 licence,要解决两个现实问题:一份 licence 不被全公司共用、不被改系统时间无限续期。先说清楚一点,客户端 licence 本质是抬高破解成本,不是绝对防得住——私钥不泄露的前提下,能做到”普通用户懒得搞”的程度,真遇上专业逆向一样会破。
licence 本体是一张 JWT,RSA 私钥签发,客户端内嵌公钥验签。下面几个是做的时候要注意的点。
时间回拨
JWT 的过期判断(exp)依赖系统时钟,所以最简单的破解就是把系统时间往回调,过期就判不出来了。
办法是本地存一个单调不降的时间记录:每次校验通过,把当前时间写进 ~/.myapp/.lastseen;下次校验前先比一下,now 比 lastseen 早超过一个容差就拒掉:
long lastSeen = Long.parseLong(readLastSeen()); |
写入取 Math.max(now, 已记录值),保证记录值只涨不跌。容差给 5 分钟,吸收 NTP 自动校时和跨时区出差这种正常抖动。
顺序上要先 check 再 record,先写后查的话回拨那一刻反而把回拨时间写进去了(max 逻辑兜得住,但顺序对了更清晰)。另外校验通过才写,没通过别写,免得帮攻击者刷新基线。
局限也明显:.lastseen 就是个普通文件,删掉就重置了,防的是随手调时间,防不住刻意破坏。要再收紧得靠联网校时或在线续期。
指纹覆盖的后门
机器指纹是把 os、user、hostname、MAC 集合、硬件序列号这些拼一起做 SHA-256,塞进 JWT 的 aud 字段做机器绑定。为了 CI 方便,我留了个后门:设环境变量 MYAPP_MACHINE_FINGERPRINT 就能直接覆盖指纹,CI 里固定一个值,省得每台机器都签。
但这个后门有问题:攻击者拿到任意一份真实 licence(自己买一份就行),Base64 解开 JWT 的 payload 就能读到 aud,那就是目标机器指纹的 SHA-256,然后 export MYAPP_MACHINE_FINGERPRINT=<那个值>,任意机器都能过校验。机器绑定就失效了,一份 licence 全网通用。
修法是加双开关:光设指纹 env 不够,还得同时设 MYAPP_ALLOW_FP_OVERRIDE=true 才允许覆盖,默认拒绝:
String envOverride = System.getenv("MYAPP_MACHINE_FINGERPRINT"); |
平时 ALLOW_FP_OVERRIDE 没人设,那个覆盖 env 就是个摆设;CI 里两个一起设,照样方便。
原则是:凡是能从外部(env、配置文件、JVM 参数)覆盖安全相关判断的开关,都得看这个值攻击者拿不拿得到。JWT 的 payload 是明文 base64,谁都能解,把指纹放 aud 等于公开了”目标指纹长这样”,一旦有覆盖通道就能被投毒。
MAC 老漂
机器指纹里最不稳的是 MAC。一开始遍历网卡、拿第一个物理网卡的 MAC,但 NetworkInterface.getNetworkInterfaces() 的枚举顺序不稳——插拔一次网卡、开关蓝牙、连 VPN,顺序都可能变,”第一个”就漂走了,指纹跟着变,用户就被误拒。
稳住的办法:把所有物理网卡的 MAC 都收上来,滤掉蓝牙、VPN、虚拟网卡这些易变的,剩下的排个序再拼:
List<String> macs = new ArrayList<>(); |
关键是 Collections.sort——只要”稳定网卡集合”没变,不管枚举顺序怎么变,拼出来都一样。黑名单里 bluetooth、pan、vpn、vmware、docker、veth、bridge、wsl、tap 这些都排除,都是会随时增删、不该进指纹的。
指纹的多维特征都做了”缺失用空串占位”,某一项取不到(比如某些 Linux 读不到硬件序列号)不会让整个指纹崩掉,只是那一项贡献为空。要的是常规变更不漂、硬件大改才变。
Gradle 帮倒忙
这个 licence 挂在 Gradle/Maven 构建插件里,每次构建前跑一遍校验。但 Gradle 有增量构建,判断任务要不要重跑看的是输入输出有没有变;而 licence 过期这件事,文件内容一个字没改,只是时间过去了,Gradle 从输入快照里看不出来,直接 UP-TO-DATE 跳过校验。结果就是 licence 过期了,构建照常跑。
修法一行,让这个任务永远别缓存:
public CheckLicenceTask() { |
licence 是时间敏感的,Gradle 的缓存判断是内容敏感的,这俩语义对不上。构建期做时间相关的校验(licence、证书、token 过期),都得显式关掉增量缓存。
这几个点串起来其实就一条:客户端 licence 防的是顺手白嫖,不是专业破解。时间回拨堵上、后门堵上、指纹漂移稳住、构建缓存关对,门槛到这层就够了;再往上要不要联网激活、私钥挪不挪进 KMS,看想防到什么程度。
