Safe Haven 签名锁定机制,防止 APK 被偷偷替换
为什么要锁签名
安卓系统在安装时会检查同包名应用的签名是否一致——签名变了就拒绝覆盖安装。这是安卓的基础安全机制。但这个机制有一个盲区:如果开发者合法轮换了签名密钥,或者攻击者替换了 APK 但签名变了,用户在安装前是看不到签名变化的。
Safe Haven 的签名锁定(Signing-Key Pinning)把这个检查前置到了上架流程里。它不只是让安卓系统去检查,而是在服务端就记录下每个应用的签名哈希,之后每次复扫都比对,任何变化都会被记录和标记。
工作流程
第一步:首次扫描时锁定签名
任何一个新应用或新版本的 APK 进入 Safe Haven 扫描管线时,扫描器会:
- 从 APK 里提取签名证书
- 计算证书的 SHA-256 哈希(整个证书,不是 APK 文件)
- 把这个哈希值存储到应用条目的
signingKeyHash字段里
这个存储只做一次——首次扫描哪个版本,就以那个版本的签名哈希作为基准。
第二步:每次复扫都比对
Safe Haven 有定期复扫管线,对已上架应用每轮重新扫描时:
- 提取当前 APK 的签名哈希
- 从数据库读出应用条目的历史基准哈希
- 做比对
一致 → 一切正常,更新扫描时间戳。
不一致 → 把 signing_key_changed 标志置为 true,这个标志会在应用条目的 trust metrics 里体现出来。标志会一直保留,直到管理员手动处理。
第三步:管理员处理变更
当签名变更标志被置位后,管理员有两个处理选项:
选项 A:合法轮换密钥,手动更新哈希
开发者可能因为密钥丢失、证书过期等原因合法轮换签名密钥。管理员通过专用 endpoint 覆盖应用条目的 signingKeyHash,把基准换成新的哈希值。
每次覆盖操作都会被记录到审计日志里,包含:
- 旧的哈希值
- 新的哈希值
- 管理员填写的变更理由(比如"开发者密钥轮换")
- 操作的管理员账号
选项 B:可疑变更,标记为 retired 或 rejected
如果签名变更不是来自合法轮换,扫描管线可能检测到其他可疑迹象(比如哈希变了 + 文件大小变了 + manifest versionCode 没变),管理员可以将该版本标记为 retired(已退役)或 rejected(拒绝),并触发进一步调查。
安卓原生签名机制和 Safe Haven 的关系
安卓系统在安装时做签名比对:同包名 + 同签名 = 允许覆盖;同包名 + 不同签名 = 拒绝。
Safe Haven 的签名锁定和这个机制是两层检查,不冲突:
| 层级 | 做了什么 | 什么时候做 |
|---|---|---|
| Safe Haven 服务端 | 比对签名哈希,变更时标记 | 上架前 + 定期复扫(用户看得到) |
| 安卓系统 | 比对签名证书,不同就拒绝 | 安装时(用户看不到哈希值) |
Safe Haven 的额外价值是把签名信息公开给用户——你在安装前就能在详情页上看到"上次扫描的签名哈希",甚至可以自己对照本地已安装版本的签名,发现签名早就变了。如果 Safe Haven 没有这个机制,你直到安装失败才会知道签名有问题。
官方客户端的签名指纹
Safe Haven 自己的客户端(com.colourswift.safehaven)由 ColourSwift 官方签名,SHA-256 证书指纹:
9c67f4224888f60e093cf7eab9b194e6d4cd73bb11313638c47b17f0d5f34ec4
如果你从非官方渠道下载了 Safe Haven 的 APK,可以用 Android SDK 的 apksigner verify --print-certs 命令比对。
F-Droid / IzzyOnDroid 来源的特殊情况
F-Droid 同步进来的 APK 是用F-Droid 自己的签名密钥重打包的,不是开发者原生签名。所以:
- 同步进来时,Safe Haven 记录的基准哈希是 F-Droid 的签名哈希
- 如果开发者后来认领了这个应用并切换到原生签名,Safe Haven 会清除 F-Droid 的版本记录,重新记录开发者的签名哈希
- 同包名但不同来源的签名不一致,是混装失败的主要原因
IzzyOnDroid 来源保留开发者原生签名,所以和开发者自己提交的包签名通常一致。
签名锁定的局限
签名锁定期防得住被动篡改(比如有人换包但签名没换掉),但防不住主动滥用(比如开发者自己用恶意密钥签了新版本发布)。后者只能靠扫描管线和社区监督来发现。
签名锁定期也不处理非签名层面的篡改——比如攻击者在 APK 里偷偷加了跟踪器但没换签名。这种情况 VX-TITANIUM 引擎的字节结构分析可能检出,但不保证。