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 名称说明我见过的问题
AndroidOpenSSLAndroid 4.0+ 默认首选,基于 OpenSSL有些旧设备版本太低,不支持
BC (Bouncy Castle)旧版本 Android 曾广泛使用版本碎片化,不同厂商阉割过
ConscryptAndroid 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 架构图:算法请求是如何被路由的

这部分理解起来有点绕,我画张图帮你梳理整个流程。

JCA Provider 算法路由流程 JCA 算法请求路由流程 应用代码 Cipher.getInstance("AES/GCM") JCA 框架 按优先级遍历 Provider AndroidOpenSSL 优先级最高 Conscrypt Android 6.0+ Bouncy Castle 降级备用 AES GCM 实现 AES GCM 实现 AES GCM 实现 返回 Cipher 对象 第一个匹配成功的 Provider 若所有 Provider 都不支持该算法,抛出 NoSuchAlgorithmException

这张图展示了算法请求的完整生命周期。你看,从应用代码调 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、密钥管理那些章节就轻松多了。