Java SM2 + SM4 混合加密 API
Java SM2 + SM4 混合加密 API
SM2Sm4Hybrid 每次生成一个随机 SM4 会话 key,用 SM4 加密业务数据,再用接收方 SM2 公钥加密该会话 key。这样避免直接用 SM2 处理较长消息,同时保留“只有指定 SM2 私钥持有方才能恢复会话 key”的语义。
这套 API 适合已经载入内存的订单、消息和文件片段。它是一次性内存接口,不提供流式文件读写,也不负责证书校验、key id、密钥轮换或跨服务序列化。
先确定载荷格式
完整流程、字段落库和篡改失败见 Java SM2 + SM4 使用手册。本页只解释两个公开类型及其成员,不把对象的 toString() 当作协议格式。0.10.1 的默认配置是 SM2-C1C3C2 + SM4-GCM + 12 字节随机 nonce + 16 字节 tag。
两个公开类型
处理流程
加密:随机 SM4 key ──SM2 公钥加密──> encryptedKey
业务明文 ──SM4 + IV/AAD──> ciphertext + tag
解密:encryptedKey ──SM2 私钥解密──> SM4 key
ciphertext + IV/AAD/tag ──SM4──> 业务明文会话 key 只在方法内部以 16 字节数组存在,不包含在返回对象的明文字段中。encryptedKey 固定为 SM2 C1C3C2 raw 密文,而不是 ASN.1 DER。
构造器与安全上下文
public SM2Sm4Hybrid();
public SM2Sm4Hybrid(GmSecurityContext securityContext);无参构造使用 GmSecurityContexts.defaults()。显式传 null 也回退到默认上下文;非 null 上下文同时绑定给内部 SM2 和 SM4,决定会话 key、SM2 临时随机数和自动 IV/nonce 的随机源。
自定义 SM4Options.securityContext() 不会替换构造器绑定的上下文,因为内部 SM4 实例已经固定使用混合加密对象的上下文。需要定制 Provider 或随机源时,应在构造 SM2Sm4Hybrid 时传入。
对象不保存会话 key 或上一条消息状态,无需 reset() 或 close()。
默认配置
传自定义 SM4Options 时保留 mode、padding、AAD 和 tag 长度。需要 IV/nonce 但未提供时会自动生成:GCM/CCM 为 12 字节,CBC/CTR/CFB/OFB 为 16 字节,ECB 不生成。
加密 options 中不能预先放入非空 tag;底层 SM4 会拒绝这种配置。改用 CBC、CTR、CFB 或 OFB 时,返回载荷没有认证 tag,调用方必须依照既有协议提供完整性保护。
加密 API
完整签名
SM2Sm4HybridPayload encrypt(
String publicKeyHex,
byte[] plaintext);
SM2Sm4HybridPayload encrypt(
String publicKeyHex,
String plaintext);
SM2Sm4HybridPayload encrypt(
String publicKeyHex,
String plaintext,
Charset charset,
SM4Options options);
SM2Sm4HybridPayload encrypt(
String publicKeyHex,
byte[] plaintext,
SM4Options options);publicKeyHex 必须是合法 SM2 压缩或非压缩公钥。plaintext 不能为 null,但空数组和空字符串在默认 GCM 下合法。返回对象包含完成解密所需的算法字段,不包含接收方 key id 或证书信息。
解密 API
完整签名
byte[] decrypt(
String privateKeyHex,
SM2Sm4HybridPayload payload);
String decryptToUtf8(
String privateKeyHex,
SM2Sm4HybridPayload payload);
String decryptToString(
String privateKeyHex,
SM2Sm4HybridPayload payload,
Charset charset);解密先以 SM2 C1C3C2 恢复 16 字节会话 key,再根据 payload 中的 mode、padding、IV、AAD 和 tag 还原 SM4 参数。默认 GCM 下,key、nonce、AAD、ciphertext 或 tag 任一不一致都会抛 GmkitException,不会返回未认证明文。
SM2Sm4HybridPayload
构造器
public SM2Sm4HybridPayload(
byte[] encryptedKey,
byte[] ciphertext,
byte[] iv,
byte[] aad,
byte[] tag,
SM4CipherMode mode,
SM4Padding padding);构造器要求 encryptedKey、ciphertext、mode、padding 非 null,但不在构造阶段验证数组长度与模式组合;解密时由 SM2/SM4 做严格校验。IV、AAD、tag 可以为 null,空数组也会被 has*() 视为不存在。
全部访问器
byte[] encryptedKey();
String encryptedKeyHex();
String encryptedKeyBase64();
byte[] ciphertext();
String ciphertextHex();
String ciphertextBase64();
byte[] iv();
boolean hasIv();
String ivHex();
byte[] aad();
boolean hasAad();
byte[] tag();
boolean hasTag();
String tagHex();
String tagBase64();
SM4CipherMode mode();
SM4Padding padding();所有数组构造参数都会复制,数组 getter 也返回副本。修改 getter 返回的内容不会改变 payload。
默认 GCM:成功与篡改失败
// 1. 准备参数:生成接收方 SM2 密钥对和订单明文。
SM2KeyPair keys = SM2Util.generateKeyPair();
SM2Sm4Hybrid hybrid = new SM2Sm4Hybrid();
String message = "order=GMKIT-DEMO-0001&amount=88.00";
// 2. 混合加密:默认使用随机 SM4 key、12 字节 nonce 和 GCM。
SM2Sm4HybridPayload payload = hybrid.encrypt(keys.publicKey(), message);
// 3. 载荷字段断言:GCM 解密所需的 mode、IV 和 tag 必须齐全。
if (payload.mode() != SM4CipherMode.GCM
|| !payload.hasIv()
|| payload.iv().length != 12
|| !payload.hasTag()
|| payload.tag().length != 16) {
throw new IllegalStateException("hybrid GCM metadata mismatch");
}
// 4. 混合解密:SM2 私钥恢复会话 key,再解密订单明文。
String recovered = hybrid.decryptToUtf8(keys.privateKey(), payload);
// 5. 成功断言:解密结果必须等于订单原文。
if (!message.equals(recovered)) {
throw new IllegalStateException("hybrid round-trip failed");
}
// 6. 构造篡改载荷:复制 tag 后修改第一个字节。
byte[] tamperedTag = payload.tag();
tamperedTag[0] ^= 0x01;
SM2Sm4HybridPayload tampered = new SM2Sm4HybridPayload(
payload.encryptedKey(),
payload.ciphertext(),
payload.iv(),
payload.aad(),
tamperedTag,
payload.mode(),
payload.padding());
// 7. 失败断言:篡改 tag 后必须拒绝解密,不能返回明文。
try {
hybrid.decrypt(keys.privateKey(), tampered);
throw new IllegalStateException("tampered hybrid payload must fail");
} catch (cn.gmkit.core.GmkitException expected) {
// 预期:认证失败,不会得到明文。
}绑定业务上下文 AAD
// 1. 配置 AAD:租户和 schema 可公开,但必须参与 GCM 认证。
SM4Options options = SM4Options.builder()
.mode(SM4CipherMode.GCM)
.padding(SM4Padding.NONE)
.aad(Texts.utf8("tenant=demo;schema=1"))
.tagLength(16)
.build();
// 2. 混合加密:订单明文与固定 AAD 一同进入认证加密流程。
SM2Sm4HybridPayload payload = hybrid.encrypt(
keys.publicKey(),
Texts.utf8("order=GMKIT-DEMO-0001&amount=88.00"),
options);AAD 不会被加密,应只放允许公开但必须防篡改的协议字段。解密端必须还原完全相同的 AAD 字节;JSON 字段重排、大小写变化或字符集变化都会导致认证失败。
序列化边界
SM2Sm4HybridPayload 不定义 JSON、CBOR、Protobuf 或二进制封包格式。跨进程或跨语言传输时,应用应建立带版本的 schema,例如:
{
"version": 1,
"recipientKeyId": "merchant-sm2-2026-01",
"encryptedKey": "<base64>",
"ciphertext": "<base64>",
"iv": "<base64-or-null>",
"aad": "<base64-or-null>",
"tag": "<base64-or-null>",
"mode": "GCM",
"padding": "NONE"
}- 每个二进制字段固定一种编码,解码时禁止自动猜测。
- schema 必须带版本,并在外层增加接收方 key id;payload 本身没有这些字段。
mode、padding、tag 长度和 SM2 密文排列都应进入协议说明。- AAD 按收到的原始字节参与认证,不要重新拼接业务对象后假定结果相同。
- TypeScript 对端按字段调用 SM2/SM4 API,不能把 Java 类名或对象序列化细节当作跨语言协议。
失败行为
可执行案例
JUnit 文档测试覆盖默认 GCM 元数据、往返解密和篡改 tag 失败;集成专项测试覆盖更多算法组合。
查看混合加密文档案例
// 1. 准备参数:生成接收方 SM2 密钥对,并固定订单明文与 AAD。
String message = "order=GMKIT-DEMO-0001&amount=88.00";
byte[] aad = "tenant=demo;schema=1".getBytes(StandardCharsets.UTF_8);
SM2KeyPair keys = new SM2().generateKeyPair();
SM2Sm4Hybrid hybrid = new SM2Sm4Hybrid();
SM4Options options = SM4Options.builder()
.mode(GCM)
.padding(NONE)
.aad(aad)
.tagLength(16)
.build();
// 2. 混合加密:随机 SM4 会话 key 加密明文,SM2 加密会话 key。
SM2Sm4HybridPayload payload = hybrid.encrypt(keys.publicKey(), message, StandardCharsets.UTF_8, options);
// 3. 载荷字段断言:解密所需的密钥密文、数据密文、IV、AAD 和 tag 必须齐全。
assertNotNull(payload.encryptedKey());
assertNotNull(payload.ciphertext());
assertTrue(payload.hasIv());
assertTrue(payload.hasAad());
assertTrue(payload.hasTag());
assertEquals(GCM, payload.mode());
// 4. 混合解密:使用接收方 SM2 私钥恢复会话 key 和订单明文。
byte[] decrypted = hybrid.decrypt(keys.privateKey(), payload);
// 5. 成功断言:解密字节必须与原始订单 UTF-8 字节一致。
assertArrayEquals(
message.getBytes(StandardCharsets.UTF_8),
decrypted);
// 6. 构造篡改载荷:只修改 SM4-GCM tag。
byte[] changedTag = payload.tag();
changedTag[0] ^= 0x01;
SM2Sm4HybridPayload tampered = new SM2Sm4HybridPayload(
payload.encryptedKey(),
payload.ciphertext(),
payload.iv(),
payload.aad(),
changedTag,
payload.mode(),
payload.padding());
// 7. 失败断言:tag 被篡改后必须拒绝混合解密。
assertThrows(GmkitException.class, () -> hybrid.decrypt(keys.privateKey(), tampered));运行测试:
cd packages/java
mvn -pl gmkit -Dtest=PublicApiManualExamplesTest,SMIntegrationTest test公共项覆盖
本页覆盖 SM2Sm4Hybrid、SM2Sm4HybridPayload 两个公开顶层类型,以及它们的全部构造器、加解密重载、字段 getter 和编码 getter。