3. Java Cryptography Architecture (JCA) 简介:Provider 概念与常见算法
说实话,我刚开始接触 Android 密码学时,也跟大多数新人一样——直接调 Cipher.getInstance("AES"),能跑通就行。后来踩了几个坑才明白,这背后藏着一个叫 JCA 的体系。不理解它,你写的加密代码可能就是个定时炸弹。
JCA 是什么?简单讲,它是 Java 官方给的一套加密框架。不是具体算法库,而是个「接口规范」。特点就是:你想用什么算法,它帮你找实现,你不需要关心底层是谁写的。
听起来很抽象?你想想看,假如你写了个 AES 加密,底层实际上是 OpenSSL 实现。你换一台手机,底层可能是 Bouncy Castle。但你的代码不用改,这就是 JCA 的威力。
3.1 Provider 概念——插拔式加密引擎
JCA 里最核心的概念就是 Provider。我习惯把它理解成一个「算法插件包」。每个 Provider 都提供一组算法实现,比如 AES、RSA、SHA-256 等等。
Android 系统默认带了几个 Provider:
| Provider 名称 | 说明 | 我见过的问题 |
|---|---|---|
| AndroidOpenSSL | Android 4.0+ 默认首选,基于 OpenSSL | 有些旧设备版本太低,不支持 |
| BC (Bouncy Castle) | 旧版本 Android 曾广泛使用 | 版本碎片化,不同厂商阉割过 |
| Conscrypt | Android 6.0+ 新增,性能更好 | 部分厂商定制 ROM 有问题 |
嗯,这里要注意:Provider 是有优先级的。你调用 Cipher.getInstance("AES/GCM/NoPadding") 时,系统会按优先级遍历 Provider,找到第一个能提供这个算法的实现返回给你。
我曾经踩过这个坑:在 Android 4.4 上使用 BC Provider 的 AES/GCM,发现加密结果跟 Android 7.0 上不一样。后来排查发现,BC 在 4.4 上实现有 bug,而 AndroidOpenSSL 版本是正常的。从此我养成了一个习惯:指定 Provider。
指定 Provider 的写法很简单:
// 不安全:依赖默认优先级
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// 推荐:明确指定 Provider
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "AndroidOpenSSL");
为什么要这么干?说白了,就是防止厂商魔改。有些定制 ROM 把默认 Provider 换成了弱实现,你的加密强度瞬间打折扣。
小技巧:在 App 启动时打印所有可用的 Provider 及其算法列表,用来做兼容性检测。
Provider[] providers = Security.getProviders();
for (Provider p : providers) {
Log.d("JCA", "Provider: " + p.getName());
for (Provider.Service s : p.getServices()) {
Log.d("JCA", " Algorithm: " + s.getAlgorithm());
}
}
3.2 常见算法一览
JCA 覆盖的算法类型很多。我按使用场景给你分个类,记住这几个就够了:
| 类别 | 常用算法 | 推荐场景 |
|---|---|---|
| 对称加密 | AES (GCM/CTR), ChaCha20 | 数据加密存储,网络传输加密 |
| 非对称加密 | RSA (OAEP), ECDH | 密钥交换,数字签名 |
| 消息摘要 | SHA-256, SHA-512 | 数据完整性校验,密码哈希 |
| 消息认证码 | HmacSHA256 | 防篡改, 校验 |
| 密钥生成 | KeyGenerator, KeyPairGenerator | 生成 AES/RSA 密钥 |
我个人项目里,对称加密无脑选 AES/GCM/NoPadding。为什么?GCM 模式自带认证,能防篡改,而且不需要自己处理 IV。RSA 只用来做密钥交换或数字签名,别拿它直接加密大文件——性能太差。
还有个容易忽视的点:算法名称字符串的写法。JCA 用字符串来标识算法,比如 "AES/GCM/NoPadding"。你写错一个字符,直接抛 NoSuchAlgorithmException。我建议你把这些字符串定义成常量,避免手写。
public static final String AES_GCM = "AES/GCM/NoPadding";
public static final String RSA_OAEP = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
public static final String SHA256 = "SHA-256";
public static final String HMAC_SHA256 = "HmacSHA256";
3.3 JCA 架构图:算法请求是如何被路由的
这部分理解起来有点绕,我画张图帮你梳理整个流程。
这张图展示了算法请求的完整生命周期。你看,从应用代码调 getInstance 开始,JCA 框架按优先级依次问每个 Provider:你有 AES/GCM 吗?第一个说「有的」的 Provider,就把自己的实现返回给你。后面的 Provider 压根不碰。
这也就解释了为什么不同 Android 版本上同一个算法行为可能不同——Provider 优先级变了,或者某个 Provider 被移除了。
3.4 你需要记住的几点
- 永远不要依赖默认 Provider —— 显式指定你最信任的 Provider 名称。
- 常用算法就那么几个 —— 对称加密 AES/GCM,非对称 RSA/ECDH,摘要 SHA-256,MAC HmacSHA256。别整花哨的。
- Provider 是有顺序的 —— 可以通过
Security.insertProviderAt()调整优先级,但 不要 在 Android 上这么做,容易搞崩系统。 - 兼容性测试不能省 —— 不同厂商 ROM 对 Provider 的支持不一样。我建议你在 CI 里跑几台不同品牌的老型号手机。
一句话总结:JCA 就是个中间人。你告诉它你想要什么算法,它从注册的 Provider 列表里挑一个能干的活来干。理解了这个,后面看 Android KeyStore、密钥管理那些章节就轻松多了。