数字签名基础:App签名的那些事

做移动端安全这么多年,我经常被问到同一个问题:App签名到底是个啥?说白了,签名就像一个数字版的“防拆贴”。你买瓶矿泉水,瓶盖上有层塑料膜,膜碎了说明被人动过。App签名也是在干这事儿——但它更厉害,还能告诉你这瓶水是谁灌的。

先从非对称加密聊起

非对称加密,名字听着绕,其实思想很简单。它有两把钥匙:公钥和私钥。私钥你自己藏着,公钥发出去给人人用。用私钥加密的东西,只有公钥能解开。反过来,用公钥加密的,也只有私钥能解。

为什么叫“非对称”?因为两把钥匙不一样。不像你家大门钥匙,开锁关锁是同一把。这个特性非常关键。我在早期做金融App时,就遇到过误用对称加密来做签名的案例。结果呢?密钥一旦泄露,整个系统都得重来。那场面,啧,别提多惨了。

目前主流的非对称加密算法,就这么几种:

算法 密钥长度 安全强度 常见场景
RSA 2048/4096位 高 代码签名、HTTPS证书
ECC 256/384位 更高(同等长度) 移动设备签名、区块链
DSA 1024/2048位 中 数字签名专用(较少用了)

核心要点:App签名用的是RSA或者ECDSA。我建议新项目直接上ECDSA,密钥短、速度快、安全性还高。别问我怎么知道的——早年被迫维护一个1024位RSA签名的项目,每次打包都等半天,活活等出颈椎病。

哈希算法是什么?

你想想看,一个App包可能有几十兆甚至上百兆。直接拿私钥去加密整个文件,费时费力不说,还毫无必要。这时候就需要哈希算法登场了。

哈希算法,说白了就是一个“指纹”生成器。不管你的文件是1KB还是1GB,它都会算出一个固定长度的“摘要”。这个摘要唯一标识了原始数据。哪怕你只改了一个字节,生成的哈希值也会完全不一样。

常见的哈希算法有这些:

  • SHA-1:已不推荐,有碰撞风险。2017年Google就演示过两个不同PDF文件撞出了相同SHA-1值。
  • SHA-256:目前行业标配。Android签名和iOS签名都用它。
  • SHA-512:更安全,但开销也更大。后端签名用得多,移动端稍少见。

注意避坑:我曾经接手一个第三方SDK集成项目,发现他们用MD5做签名校验。当时我只说了一句话:“MD5不是用来做签名的,它是用来算文件校验和的。”那个项目被我直接否掉了。为什么?MD5的抗碰撞性早就被攻破,你想让攻击者轻松伪造你的签名吗?

签名到底怎么签的?

好,现在我们把两样工具凑一块儿:非对称加密 + 哈希算法。完整的签名流程如下:

  1. 对原始数据(比如你的App包)计算哈希值,得到一个固长的摘要。
  2. 用私钥对这个摘要进行加密。加密后的结果就是“数字签名”。
  3. 把原始数据和数字签名打包在一起,发出去。

验签流程反过来:

  1. 接收方拿到原始数据,用同样的哈希算法算出摘要A。
  2. 使用公钥对签名进行解密,得到摘要B。
  3. 比较摘要A和摘要B。一致 -> 数据没被篡改,且确实由私钥持有者签名。

代码实现其实不复杂。Android端使用java.security包,一段签名代码大概长这样:

private byte[] sign(byte[] data, PrivateKey privateKey) throws Exception {
    Signature signature = Signature.getInstance("SHA256withRSA");
    signature.initSign(privateKey);
    signature.update(data);
    return signature.sign();
}

private boolean verify(byte[] data, byte[] signBytes, PublicKey publicKey) throws Exception {
    Signature signature = Signature.getInstance("SHA256withRSA");
    signature.initVerify(publicKey);
    signature.update(data);
    return signature.verify(signBytes);
}

经验之谈:记得指定“SHA256withRSA”,不要只写“RSA”。我在项目中见过有人直接写Signature.getInstance("RSA"),结果不同厂商的Java实现默认摘要算法不一样,跨平台验签就炸了。细节决定成败,签名的算法全名一定要写全。

为什么App需要签名?

这个问题我问过不少刚入行的开发者。有人说“防破解”,有人说“系统要求的”。都对,但不全面。我把它总结成三个层次:

  • 身份认证:签名确认了这个App是谁开发的。AndroidManifest里的package + 签名证书,唯一标识一个应用。你开发者账号丢了可以找回,签名证书丢了?对不起,你永远无法以原身份更新这个App了。
  • 完整性保护:前面讲了,签名能发现文件是否被篡改。用户从应用商店下载的App,系统会做完整性校验。如果发现签名对不上,直接拒绝安装。
  • 信任链建立:签名证书由CA(证书颁发机构)签发,逐级向上追溯,最终到根证书。通过这条信任链,用户不用认识每一个开发者,也能信任他开发的软件。

举个例子你就明白了。你在Google Play或App Store上下载一个银行App。系统怎么确认这个App真的是那家银行的?靠签名。银行的签名证书经过审核,和它的开发者账号绑定。你下载时,系统会校验证书是否被吊销、是否过期、是否由可信CA签发。全部通过,才给安装。

核心结论:没有签名的App,在iOS上根本装不了。在Android上,虽然可以开启“允许安装未知来源应用”绕过,但系统会给你弹一屏警告:“该应用签名无效或有风险”。用户看到这个提示,90%的人会取消安装。签名不是可选项,是必须项。

直观了解签名全流程

说了这么多,咱们看看签名与验签的完整流程。这个SVG把核心环节和参与者都画出来了:

数字签名与验签全流程 签名方(开发者) 验证方(系统/用户) ① App原始文件(APK/IPA) ② SHA-256哈希运算 ③ 私钥加密哈希 → 签名 ① 接收原始文件 + 签名 ② 对文件做SHA-256哈希 ③ 公钥解密签名 → 得摘要 传输:App + 签名文件 ④ 比较两个摘要 → 一致则通过 私钥仅签名方持有。公钥内置于操作系统或由CA证书链提供。 摘要不一致 = 文件被篡改 或 签名非原开发者所为。 密钥管理要点 🔑 私钥: - 存储在硬件安全模块(HSM)或安全区域(Secure Enclave) - 不能硬编码在代码中,不能出现在日志里 - 团队内严格权限管控,专人保管 🔓 公钥: - 随App分发给所有用户 - Android端可从签名证书中提取 - iOS端由Apple统一管理验证

签名背后的风险

讲到这里,你可能觉得签名挺靠谱的。但我在安全这行干了十几年,必须跟你说句实话:签名不是万能的。常见的坑就有:

  • 私钥泄漏:这是最致命的。私钥一旦被偷,攻击者就能用你的身份签名恶意App。2019年就有个著名案例,某个大厂的签名密钥被内部人员泄露,结果市场上出现一堆“官方”签名的山寨App。怎么防?硬件隔离,密钥不落盘,专人专管。
  • 证书过期或吊销:签名证书有有效期。你两年前签名的App,如果证书过期了,系统会怎么处理?Android这边,证书过期不影响已安装的App,但无法发布更新。iOS更严格,过期证书签名的App直接不可用。
  • 签名被重打包:攻击者反编译你的App,注入恶意代码,然后用自己的证书重新签名。遇到这种情况,App store的审核机制是第一道防线。但在国内第三方市场,这类重打包App很常见。我遇到过一家游戏公司,他们的App被重打包了200多个版本,全是用别人的证书签的。

血的教训:我曾经为某客户做安全审计,发现他们的release签名证书竟然以文件形式存放在Git仓库里。密码写在README里。是的,你没听错。我当时就问他们CTO:“你们是认真的吗?”后来帮他们重建了整个密钥管理体系。私钥这种东西,连我都不敢随便放。你想想看,一个Git仓库里有几十个开发者有访问权限,这私钥跟公开有什么区别?

签名这件事,说穿了就是“信任问题”。你信任某家银行,所以用它的App。签名就是把这个信任数字化、可验证化。系统不需要“认识”每个开发者,它只需要验证签名是否有效、证书链是否完整。

理解了这个底层的信任模型,后面章节讲Android签名机制、iOS签名差异、自动化签名流水线的时候,你就能更快上手了。前面我提到的那些坑,后面都会逐个展开讲怎么规避。先消化今天的基础,后面的内容会更密集。