1. 数字签名基础
做移动端安全这么多年,我经常被问到同一个问题: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的抗碰撞性早就被攻破,你想让攻击者轻松伪造你的签名吗?
签名到底怎么签的?
好,现在我们把两样工具凑一块儿:非对称加密 + 哈希算法。完整的签名流程如下:
- 对原始数据(比如你的App包)计算哈希值,得到一个固长的摘要。
- 用私钥对这个摘要进行加密。加密后的结果就是“数字签名”。
- 把原始数据和数字签名打包在一起,发出去。
验签流程反过来:
- 接收方拿到原始数据,用同样的哈希算法算出摘要A。
- 使用公钥对签名进行解密,得到摘要B。
- 比较摘要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。2019年就有个著名案例,某个大厂的签名密钥被内部人员泄露,结果市场上出现一堆“官方”签名的山寨App。怎么防?硬件隔离,密钥不落盘,专人专管。
- 证书过期或吊销:签名证书有有效期。你两年前签名的App,如果证书过期了,系统会怎么处理?Android这边,证书过期不影响已安装的App,但无法发布更新。iOS更严格,过期证书签名的App直接不可用。
- 签名被重打包:攻击者反编译你的App,注入恶意代码,然后用自己的证书重新签名。遇到这种情况,App store的审核机制是第一道防线。但在国内第三方市场,这类重打包App很常见。我遇到过一家游戏公司,他们的App被重打包了200多个版本,全是用别人的证书签的。
血的教训:我曾经为某客户做安全审计,发现他们的release签名证书竟然以文件形式存放在Git仓库里。密码写在README里。是的,你没听错。我当时就问他们CTO:“你们是认真的吗?”后来帮他们重建了整个密钥管理体系。私钥这种东西,连我都不敢随便放。你想想看,一个Git仓库里有几十个开发者有访问权限,这私钥跟公开有什么区别?
签名这件事,说穿了就是“信任问题”。你信任某家银行,所以用它的App。签名就是把这个信任数字化、可验证化。系统不需要“认识”每个开发者,它只需要验证签名是否有效、证书链是否完整。
理解了这个底层的信任模型,后面章节讲Android签名机制、iOS签名差异、自动化签名流水线的时候,你就能更快上手了。前面我提到的那些坑,后面都会逐个展开讲怎么规避。先消化今天的基础,后面的内容会更密集。