1. 项目背景与核心需求
在金融、政务等对数据安全要求严格的领域,国密算法(SM系列)已成为行业标配。最近在对接某政务平台接口时,对方明确要求使用SM2进行签名验签、SM4进行数据加密。作为Java开发者,我们需要快速实现这套安全体系。
SM2是基于椭圆曲线密码学的非对称加密算法,相比RSA在相同安全强度下密钥更短、性能更高。SM4则是分组对称加密算法,密钥长度和分组长度均为128位,安全性对标AES。两者组合能构建完整的数据传输安全方案:
- 身份认证:SM2签名/验签确保通信双方身份合法
- 数据保密:SM4加密保障传输内容不被窃取
- 完整性校验:SM2签名同时提供防篡改保障
2. 环境准备与依赖配置
2.1 基础环境要求
- JDK 1.8+(推荐JDK11)
- Maven 3.6+
- BC库(Bouncy Castle)1.70+
2.2 关键依赖引入
xml复制<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.70</version>
</dependency>
注意:必须添加BC安全提供者,否则无法识别SM算法
java复制Security.addProvider(new BouncyCastleProvider());
3. SM2密钥对生成与管理
3.1 密钥对生成实现
java复制public static KeyPair generateSM2KeyPair() throws Exception {
ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
kpg.initialize(sm2Spec);
return kpg.generateKeyPair();
}
3.2 密钥存储最佳实践
- 私钥建议使用HSM硬件加密机保管
- 公钥可通过PEM格式分发:
java复制public static String getPEMPublicKey(PublicKey publicKey) {
return "-----BEGIN PUBLIC KEY-----\n" +
Base64.getEncoder().encodeToString(publicKey.getEncoded()) +
"\n-----END PUBLIC KEY-----";
}
4. SM2签名验签完整实现
4.1 签名流程
java复制public static byte[] sign(byte[] data, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("SM3withSM2", "BC");
signature.initSign(privateKey);
signature.update(data);
return signature.sign();
}
4.2 验签流程
java复制public static boolean verify(byte[] data, byte[] signature, PublicKey publicKey) throws Exception {
Signature sig = Signature.getInstance("SM3withSM2", "BC");
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(signature);
}
4.3 关键参数说明
| 参数 | 说明 | 典型值 |
|---|---|---|
| 曲线名称 | 国密标准椭圆曲线 | sm2p256v1 |
| 签名算法 | 采用SM3哈希+SM2签名 | SM3withSM2 |
| 签名长度 | 固定64字节 | 64 |
踩坑记录:部分平台要求签名值为DER编码格式,需使用BC的DER编码器转换
5. SM4加密解密实战
5.1 加密实现
java复制public static byte[] sm4Encrypt(byte[] key, byte[] iv, byte[] plaintext) throws Exception {
Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS5Padding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "SM4"), new IvParameterSpec(iv));
return cipher.doFinal(plaintext);
}
5.2 解密实现
java复制public static byte[] sm4Decrypt(byte[] key, byte[] iv, byte[] ciphertext) throws Exception {
Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS5Padding", "BC");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "SM4"), new IvParameterSpec(iv));
return cipher.doFinal(ciphertext);
}
5.3 工作模式选择建议
| 模式 | 特点 | 适用场景 |
|---|---|---|
| CBC | 需要IV,安全性好 | 通用场景 |
| ECB | 简单,不推荐 | 已废弃 |
| GCM | 带认证,推荐 | 高安全要求 |
6. 三方接口对接实战
6.1 典型交互流程
- 生成SM2密钥对(首次)
- 交换公钥(PEM格式)
- 业务数据SM4加密
- 生成SM2签名
- 发送密文+签名
- 接收方验签解密
6.2 HTTP接口示例
java复制// 请求体结构
public class ApiRequest {
private String encryptedData; // Base64编码的SM4密文
private String signature; // Base64编码的SM2签名
private String publicKey; // 可选,用于密钥协商
}
6.3 调试技巧
- 使用Postman测试时,可先用Node.js生成测试用例
- 在线SM4工具验证加解密结果
- 开启BC的debug日志:
-Dorg.bouncycastle.debug=true
7. 性能优化方案
7.1 线程安全处理
java复制// 使用ThreadLocal缓存Cipher实例
private static final ThreadLocal<Cipher> SM4_CIPHER = ThreadLocal.withInitial(() -> {
return Cipher.getInstance("SM4/CBC/PKCS5Padding", "BC");
});
7.2 批量处理优化
对于大批量数据:
- 使用CTR模式避免串行依赖
- 并行流处理:
java复制List<byte[]> encrypted = dataList.parallelStream()
.map(data -> sm4Encrypt(key, iv, data))
.collect(Collectors.toList());
8. 常见问题排查
8.1 典型错误对照表
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| InvalidKeyException | 密钥长度不符 | 检查是否为128bit(16字节) |
| IllegalBlockSizeException | 填充错误 | 确认双方Padding模式一致 |
| SignatureException | 曲线参数不匹配 | 统一使用sm2p256v1曲线 |
8.2 调试检查清单
- 确认BC提供者已注册
- 检查密钥格式是否符合预期
- 验证加密/签名参数是否一致
- 核对数据编码(Base64/Hex)
9. 进阶开发建议
9.1 密钥安全管理
- 使用KeyStore保管密钥
- 实现密钥轮换机制
- 敏感操作使用HSM
9.2 国密合规扩展
- 添加SM3哈希校验
- 实现SM2密钥交换
- 支持GMT 0009标准
在实际项目中,我们发现国密算法的错误处理需要特别注意。比如SM2签名时,如果数据为空,BC库不会抛出异常但会产生无效签名。建议在业务层添加参数校验:
java复制public void businessMethod(String data, PrivateKey key) {
if(StringUtils.isBlank(data)) {
throw new IllegalArgumentException("签名数据不能为空");
}
// ...调用签名方法
}
