第3章 Keystore基础:数字签名与证书的概念归纳总结
聊到APK签名,绕不开的就是Keystore、数字签名和证书这三兄弟。很多做了两三年的Android开发,可能每天都在用keytool生成签名,但真要问「签名到底签了啥?证书里装的是什么?」—— 能说清楚的真不多。我2016年刚带项目时也吃过这个亏,有一次线上包被篡改,排查半天才发现是签名证书匹配的问题。嗯,从那以后我对这块就格外上心。
1. 数字签名:APK的防伪指纹
数字签名说白了,就是一段「只有你能生成、但谁都能验证」的加密数据。它不等于加密,它的核心目的是防篡改 + 防抵赖。
流程很简单:
- 对APK内容算一个哈希值(比如SHA-256)
- 用你的私钥对这个哈希值加密,得到签名
- 验证时,用你的公钥解密签名,拿到原始哈希
- 跟APK当前内容算出来的哈希对比 —— 一致就说明没被改过
关键认知:数字签名不是「把整个文件加密」,而是「对文件的指纹签名」。所以签名后的APK体积增长很小,主要增加的是签名块(Signing Block)和证书信息。
我在项目中遇到过有人问:「那我把签名文件删了行不行?」—— 行,但APK就变「裸包」了,系统安装时会直接拒绝,提示INSTALL_PARSE_FAILED_NO_CERTIFICATES。你想想看,这就像你寄快递不贴单号,快递员根本不知道是谁寄的。
2. 证书:把公钥包装成「身份证」
公钥是一串无意义的字节,直接发给别人,对方怎么知道它属于谁?证书就是来解决这个问题的。它把公钥、持有者信息、有效期、签发者等信息打包在一起,再由一个权威机构(或者你自己)签名。
一个标准的X.509证书包含:
- 主体信息(Common Name、组织、国家等)
- 公钥(RSA/ECC的公钥部分)
- 签发者信息(谁签发的这个证书)
- 有效期(起止时间)
- 签名算法(如SHA256WithRSA)
- 指纹(证书本身的哈希,用于快速匹配)
Android开发中我们99%的情况用的是自签名证书。自己签发给自己,没有CA背书。为什么可以?因为Android不会去验证你的证书链是否追溯到根CA——它只验证签名是否匹配你内置的公钥。所以自签名完全够用。
我个人的习惯:正式发布的release包,证书的有效期至少设25年,避免中途过期导致无法更新。debug包用默认的Android Debug Certificate就好,一年有效期,过期了重新生成就是。
3. Keystore:密钥材料的保险箱
Keystore就是一个文件,里面装着你的私钥和证书。它用密码保护,防止私钥泄露。Android开发中主要有两种格式:
| 格式 | 扩展名 | 特点 | 推荐场景 |
|---|---|---|---|
| JKS | .jks | Java专有,历史悠久,不支持私钥加密 | 历史项目兼容 |
| PKCS12 | .p12 / .pfx | 行业标准,跨语言,支持私钥加密,更安全 | 新项目首选 |
我记得从AGP 4.2开始,官方默认推荐PKCS12。我自己也从那时起全面切换了。生成命令很简单:
keytool -genkey -v -keystore release.p12 \
-storetype PKCS12 \
-alias mykey \
-keyalg RSA \
-keysize 2048 \
-validity 9125 \
-storepass 123456 \
-keypass 123456
需要注意,一个Keystore里可以包含多个别名(alias),每个别名对应一套「私钥+证书」。我见过有团队把debug和release放在同一个Keystore里,用不同别名区分——我个人不推荐这样,万一密码泄漏,两个环境一起沦陷。分开存储更安全。
4. Android签名机制与包体积的关联
签名本身对包体积的影响其实很小,但签名的实现方式会影响。我们简单对比一下:
| 签名方案 | 引入版本 | 体积增量(典型值) | 说明 |
|---|---|---|---|
| v1(JAR签名) | Android 1.0 | ~3-8 KB | 签名写在META-INF目录下,多个文件 |
| v2(全文件签名) | Android 7.0 | ~8-20 KB | 签名块追加在文件末尾,支持更快的验证 |
| v3(密钥轮换) | Android 9.0 | ~15-35 KB | 在v2基础上增加了证书链信息 |
| v4(流式签名) | Android 11.0 | ~1-5 KB(体外) | 签名单独存为.apk.idsig文件 |
你可能会问:「为什么v3比v2大那么多?」——因为它要额外存储证书链,用于支持密钥轮换。我实测过一个100MB的APK,从v2切到v3,体积增加了约22KB。虽然值不大,但如果你的APK需要频繁OTA更新,积少成多也是成本。
我曾经踩过的坑:为了省那十几KB,尝试只打v1签名。结果在Android 11以上设备安装时,系统提示签名版本过旧,需要额外弹窗确认。用户体验很糟糕。后来我统一改成v2+v3双签名,体积虽然多了点,但兼容性和体验都稳了。
5. 核心概念关系全景
下面这张图梳理了Keystore、私钥、公钥、证书、数字签名和APK签名之间的完整关系。我建议你先看整体框架,再逐层理解细节。
从上图能看到一条清晰的链路:Keystore 存储 私钥 和 公钥,私钥用来生成 数字签名,公钥被包装成 X.509 证书,最后两者一起嵌入 APK 签名块。安装时系统用证书中的公钥验证数字签名,确认APK完整且来自你。
一句话总结:Keystore 是「钥匙柜」,私钥是「印章」,证书是「印鉴证明」,数字签名是「印泥痕迹」。APK 签名就是把这些统统贴在包裹上,让系统能验明正身。
6. 关于包体积的一点直觉
说实话,签名本身对包体积的影响微乎其微(几十KB的量级),真正影响体积的是签名方案中的证书链长度、签名算法位数(比如RSA 2048 vs RSA 4096),以及你是否使用了多方案兼容签名(v1+v2+v3)。
我见过团队为了「极致瘦包」,只保留v2签名,结果在Android 6以下设备安装失败。后来他们统一走了v2+v3的配置,体积多了约15KB,但覆盖了所有Android 7+设备,反而省了很多维护成本。
我的建议:在AGP中配置v2SigningEnabled true和v3SigningEnabled true,v1可以关掉(如果最低API ≥ 24)。v4签名只在需要增量更新时开启,平时保持关闭即可。