第2章 Java加密基础:JCA/JCE架构
做Java加密,绕不开JCA/JCE。说白了,它就是JDK自带的加密框架。我记得刚开始接触国密SDK时,还想过自己从头写SM2/SM3算法——幸好没干那种傻事。后来我发现,用好JCA/JCE这套抽象层,接入国密就跟换一个Provider差不多。
这一章我们就把核心的几个类过一遍。你不用死记硬背,知道它们在哪、怎么用就行。后面章节要封装国密SDK时,会反复用到这些东西。
2.1 JCA/JCE 到底是什么
JCA(Java Cryptography Architecture)是加密架构,JCE(Java Cryptography Extension)是扩展包。早期的JDK把加密限制在128位以内,JCE就是用来突破那个限制的。不过现在JDK 8以上已经默认包含强加密策略,不用再单独下载JCE包了。
这套架构的核心设计理念:Provider 分离。具体实现和接口完全解耦。你写代码时调的都是标准API(比如 Cipher.getInstance("AES")),实际干活的是后台的Provider。这种方式的好处是:想换算法?换个Provider名就行,代码几乎不用改。
2.2 核心类一览
JCA/JCE 提供了一组核心引擎类。我把它们整理成了一张表,你对照着看会更清楚。
| 引擎类 | 功能 | 常用算法举例 |
|---|---|---|
java.security.Provider | 加密服务提供者,管理算法实现 | SunJCE、BC、GMJCE |
javax.crypto.Cipher | 对称/非对称加密、解密 | AES、SM4、RSA、SM2 |
java.security.Signature | 数字签名生成与验证 | SHA256withRSA、SM3withSM2 |
java.security.MessageDigest | 消息摘要(哈希) | MD5、SHA-256、SM3 |
java.security.KeyGenerator | 对称密钥生成器 | AES、SM4 |
java.security.KeyPairGenerator | 非对称密钥对生成器 | RSA、SM2 |
java.security.KeyStore | 密钥库管理 | JKS、PKCS12 |
2.3 Provider —— 加密服务提供者
Provider 是JCA的入口。它维护一个算法名到实际实现的映射。你调用 Cipher.getInstance("SM4") 时,JCA会遍历注册过的Provider列表,找到能处理 "SM4" 的那个Provider,然后把你请求委派给它。
注册Provider
最常用的两种方式:
- 静态注册:修改
$JAVA_HOME/jre/lib/security/java.security配置文件,加入security.provider.N=...。适合全局生效的场景。 - 动态注册:代码里调用
Security.addProvider(new MyProvider())或Security.insertProviderAt()。我更推荐这种方式,因为它不会污染其他JVM实例。我在微服务项目中就吃过静态注册的亏——不同服务需要的Provider版本不一样,动态注册才是正解。
// 动态注册一个Provider
Security.addProvider(new BouncyCastleProvider());
// 查看当前所有已注册的Provider
Provider[] providers = Security.getProviders();
for (Provider p : providers) {
System.out.println(p.getName() + " - " + p.getVersion());
}
// 获取某个Provider支持的算法列表
Provider p = Security.getProvider("BC");
Set<Object> keys = p.keySet();
for (Object key : keys) {
if (key.toString().startsWith("Cipher.")) {
System.out.println(key);
}
}
2.4 Cipher —— 加密/解密
这个类很直接。你用 Cipher.getInstance("算法/模式/填充") 拿到实例,然后初始化模式(ENCRYPT_MODE 或 DECRYPT_MODE),传入密钥,接着调用 doFinal() 完成操作。
我在项目中踩过的坑
国密 SM4 的默认模式和填充跟 AES 不完全一样。SM4 常用的模式是 ECB 或 CBC,填充是 PKCS7Padding。但你直接写 "SM4/ECB/PKCS7Padding" 可能在有些Provider下不认识。实际上BouncyCastle和GMJCE都支持 "SM4/ECB/PKCS5Padding"——PKCS5Padding在Java里等价于PKCS7Padding。为此我专门写了个工具类统一处理算法字符串,免得每个开发都记混。
// SM4 加密示例
public static byte[] sm4Encrypt(byte[] key, byte[] data) throws Exception {
SecretKeySpec spec = new SecretKeySpec(key, "SM4");
Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, spec);
return cipher.doFinal(data);
}
// SM4 解密
public static byte[] sm4Decrypt(byte[] key, byte[] encrypted) throws Exception {
SecretKeySpec spec = new SecretKeySpec(key, "SM4");
Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, spec);
return cipher.doFinal(encrypted);
}
2.5 Signature —— 数字签名
签名流程:生成密钥对 → 用私钥签名 → 用公钥验签。Signature 类同样支持多Provider。
// SM2 签名
public static byte[] sm2Sign(PrivateKey privateKey, byte[] data) throws Exception {
Signature sig = Signature.getInstance("SM3withSM2");
sig.initSign(privateKey);
sig.update(data);
return sig.sign();
}
// SM2 验签
public static boolean sm2Verify(PublicKey publicKey, byte[] data, byte[] signature) throws Exception {
Signature sig = Signature.getInstance("SM3withSM2");
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(signature);
}
你想想看,如果换成RSA,只需要把算法名改成 "SHA256withRSA",其他代码几乎一样。这就是Provider抽象层的好处。
2.6 MessageDigest —— 消息摘要
MessageDigest 是使用最简单的引擎类。它不需要密钥,初始化后直接 digest() 就拿到了哈希值。
// SM3 哈希
public static byte[] sm3(byte[] data) throws Exception {
MessageDigest md = MessageDigest.getInstance("SM3");
return md.digest(data);
}
// 流式更新数据
public static byte[] sm3Stream(InputStream in) throws Exception {
MessageDigest md = MessageDigest.getInstance("SM3");
byte[] buffer = new byte[4096];
int len;
while ((len = in.read(buffer)) != -1) {
md.update(buffer, 0, len);
}
return md.digest();
}
- 所有引擎类都通过
getInstance(String algorithm)获取实例,可以加Provider参数。 - Provider 决定了算法实现的实际性能和安全强度。
- 国密算法(SM2/SM3/SM4)需要依赖第三方Provider(BouncyCastle或GMJCE)。
2.7 JCA/JCE 架构图
下面这张SVG把整个调用链路画出来了。你看左边是应用代码,右边是实际的加密算法实现,中间是Provider抽象层——一切隔离都靠它。
嗯,到这里JCA/JCE的核心类就齐了。你掌握了Provider、Cipher、Signature、MessageDigest这四样,后面封装国密SDK的时候就已经有了地基。下个章节我们会把这些类串起来,写一个完整的国密工具类——你会看到,原来换一种Provider就能让整个应用支持SM2/SM3/SM4,而业务代码几乎不动。