Java SM2 + SM4 混合加密
Java SM2 + SM4 混合加密
SM2Sm4Hybrid 为每条消息生成随机 16 字节 SM4 会话 key,用 SM4-GCM 加密业务数据,再用接收方 SM2 公钥以 C1C3C2 保护会话 key。它适合大于 SM2 直接加密范围的业务报文。
完整流程
package cn.gmkit;
import cn.gmkit.core.Base64Codec;
import cn.gmkit.core.GmkitException;
import cn.gmkit.core.SM4CipherMode;
import cn.gmkit.core.SM4Padding;
import cn.gmkit.integration.SM2Sm4Hybrid;
import cn.gmkit.integration.SM2Sm4HybridPayload;
import cn.gmkit.sm2.SM2;
import cn.gmkit.sm2.SM2KeyPair;
import cn.gmkit.sm4.SM4Options;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ManualJavaHybridTest {
@Test
void serializesFieldsAndRejectsTampering() {
// 1. 准备参数:生成接收方 SM2 密钥,并固定订单明文、AAD 和 GCM 选项。
SM2KeyPair recipient = new SM2().generateKeyPair();
String plaintext = "order=GMKIT-DEMO-0001&amount=88.00";
byte[] aad = "tenant=demo;schema=1".getBytes(StandardCharsets.UTF_8);
SM4Options options = SM4Options.builder()
.mode(SM4CipherMode.GCM)
.padding(SM4Padding.NONE)
.aad(aad)
.tagLength(16)
.build();
// 2. 混合加密:随机 SM4 会话 key 加密订单,SM2-C1C3C2 加密会话 key。
SM2Sm4Hybrid hybrid = new SM2Sm4Hybrid();
SM2Sm4HybridPayload payload = hybrid.encrypt(
recipient.publicKey(),
plaintext,
StandardCharsets.UTF_8,
options);
// 3. 载荷字段断言:会话 key 密文、业务密文、nonce、AAD、tag、mode 和 padding 必须齐全。
assertNotNull(payload.encryptedKey());
assertNotNull(payload.ciphertext());
assertTrue(payload.hasIv());
assertTrue(payload.hasAad());
assertTrue(payload.hasTag());
assertEquals(SM4CipherMode.GCM, payload.mode());
assertEquals(SM4Padding.NONE, payload.padding());
// 4. 编码传输字段:二进制统一转为 Base64,枚举和 schema 使用独立字段。
String encryptedKeyBase64 = payload.encryptedKeyBase64();
String ciphertextBase64 = payload.ciphertextBase64();
String nonceBase64 = Base64Codec.encode(payload.iv());
String aadBase64 = Base64Codec.encode(payload.aad());
String tagBase64 = payload.tagBase64();
// 5. 重建载荷:接收方按 schema 显式解码每个字段,不反序列化 Java 对象。
SM2Sm4HybridPayload received = new SM2Sm4HybridPayload(
Base64Codec.decode(encryptedKeyBase64, "encryptedKey"),
Base64Codec.decode(ciphertextBase64, "ciphertext"),
Base64Codec.decode(nonceBase64, "nonce"),
Base64Codec.decode(aadBase64, "aad"),
Base64Codec.decode(tagBase64, "tag"),
SM4CipherMode.GCM,
SM4Padding.NONE);
// 6. 混合解密:SM2 私钥恢复会话 key,SM4-GCM 认证后恢复 UTF-8 订单。
String decrypted = hybrid.decryptToString(
recipient.privateKey(),
received,
StandardCharsets.UTF_8);
assertEquals(plaintext, decrypted);
// 7. 构造篡改载荷:只修改 SM4-GCM tag,其他字段保持不变。
byte[] tamperedTag = received.tag();
tamperedTag[0] ^= 0x01;
SM2Sm4HybridPayload tampered = new SM2Sm4HybridPayload(
received.encryptedKey(),
received.ciphertext(),
received.iv(),
received.aad(),
tamperedTag,
received.mode(),
received.padding());
// 8. 失败断言:tag 被修改后,混合解密必须抛出 GmkitException。
assertThrows(
GmkitException.class,
() -> hybrid.decrypt(recipient.privateKey(), tampered));
}
}示例不序列化 SM2Sm4HybridPayload Java 对象,而是逐字段编码并在接收端重建。
加密过程
随机 16-byte SM4 key
├─ SM4-GCM(key, nonce, AAD, plaintext) → ciphertext + tag
└─ SM2-C1C3C2(recipient public key, SM4 key) → encryptedKey解密先用接收方 SM2 私钥恢复会话 key,再执行 SM4-GCM 认证解密。SM2 密钥密文、GCM tag 或其他字段被修改时,流程必须失败。
载荷字段
还要由应用增加 schema、算法名称、接收方 key ID、字段编码和创建时间。SM2Sm4HybridPayload 没有这些字段。
建议的协议对象
{
"schema": 1,
"algorithm": "SM2-C1C3C2+SM4-GCM",
"recipientKeyId": "merchant-sm2-2026-01",
"encryptedKeyBase64": "...",
"ciphertextBase64": "...",
"nonceBase64": "...",
"aadBase64": "...",
"tagBase64": "...",
"mode": "GCM",
"padding": "NONE"
}这只是字段约定示例,不是 0.10.1 自动实现的序列化格式。上线前固定 JSON 规范、字段大小写、未知字段策略和版本升级规则。
默认值与显式选项
hybrid.encrypt(..., options = null) 的确切默认组合是:
- SM4-GCM;
- NONE padding;
- 12 字节随机 nonce;
- 16 字节 tag;
- 无 AAD;
- SM2-C1C3C2 会话 key 密文。
新协议应像可执行样例一样显式传 SM4Options,尤其要固定 AAD 和 tag 长度。调用方省略需要的 IV/nonce 时,组合入口会用安全上下文生成随机值并写入 payload。
失败与边界
- tag、AAD、nonce 或 ciphertext 不匹配:抛
GmkitException。 encryptedKey被修改或 SM2 私钥错误:会话 key 解密失败。- 返回数组 getter 是防御性拷贝,修改副本不会修改原 payload。
- 该组合只提供加密与认证,不提供发送者数字签名;需要不可否认性时在协议层另加 SM2 签名。
- 不把 payload 的 Java 类序列化结果当稳定网络格式。
全部构造器、重载和字段 getter 见 Java SM2 + SM4 混合加密 API。