GMKit 跨语言互操作向量
2025/12/4大约 6 分钟协议与标准互操作
GMKit 跨语言互操作向量
根目录 vectors/interop.json 是 Java 与 TypeScript 共享的协议数据。它用于验证摘要、密文、MAC、往返结果和格式边界,不表示两端具有相同 API 或 ABI,也不能代替外部标准向量。
信任级别
| 级别 | source | 可证明的内容 |
|---|---|---|
| 外部固定向量 | 标明标准及测试集 | 项目输出与该标准测试集的确定性结果一致 |
| 项目互操作向量 | project 或缺省 | Java 与 TypeScript 在已固定参数下字节级一致 |
| 随机算法回环 | 项目测试代码 | 生成结果可解密或验签,且篡改会被拒绝 |
项目互操作向量由仓库实现产生,不能用于证明实现本身正确。算法正确性需要外部标准向量、独立实现复核和负向测试共同支撑。
自动化消费方
| 实现 | 测试 |
|---|---|
| TypeScript | packages/ts/test/interop-compliance.test.ts |
| Java | packages/java/gmkit/src/test/java/cn/gmkit/InteropComplianceTest.java |
# 两端共享向量
npm run parity
# 单独验证
npm test -w packages/ts -- interop-compliance
mvn -f packages/java/pom.xml -B -ntp -pl gmkit -Dtest=InteropComplianceTest test数据约定
- 没有
source或写明source: "project"的值是项目互操作回归向量,用于 Java/TS 字节级对齐。 - 写明标准来源的值才是外部固定向量。当前 ZUC 包含 3GPP TS 35.221 EEA3 和 TS 35.222 EIA3 关键向量。
- SM2 加密和签名含随机数。未固定随机源时不比较完整密文或签名字面值,只验证解密结果、验签结果及篡改拒绝。
- 字符串统一使用 UTF-8,二进制字段使用偶数长度的小写 hex,不带
0x前缀。
顶层 meta 描述向量格式版本和编码;defaults 只用于减少测试数据重复;cases 中每一项必须有稳定 id、algo、op 和期望结果。消费者不得根据未声明字段猜测编码或模式。
向量门禁采用 fail-closed:文件缺失、JSON 结构错误、空 cases、重复 id、未知操作、必填字段缺失或某算法分组零匹配都会失败。Java 与 TypeScript 必须消费全部 case;用例数量以测试运行输出为准,不在文档中写死。
字段边界
| 算法 | 必须固定的字段 |
|---|---|
| SM2 | mode、userId、raw/DER、hex/base64、公钥表示;密钥交换还需固定双方角色、静态/临时密钥、派生长度和 S1/S2 |
| SM3 | UTF-8/原始字节输入与 hex/base64 输出 |
| SM4 | mode、padding、key、IV/nonce、AAD、tag 长度 |
| ZUC | key、IV、COUNT、BEARER、DIRECTION、bitLength,以及 eea3/eea3-encrypt 操作语义 |
当前共享 JSON 中的 SM4 case 覆盖 ECB/CBC。CTR/CFB/OFB/GCM/CCM 使用 TypeScript 与 Java/Bouncy Castle 中相同的固定输入和输出做双端测试,但尚未并入 vectors/interop.json,文档不得把项目往返测试描述成共享 AEAD 向量。
ZUC 的 count 在 JSON 中以十进制数保存,含义是 API 接收的 32-bit 整数,不是按宿主机端序解释的四字节数组。
TypeScript 固定断言
sm4Encrypt 返回对象,比较固定密文时必须读取 result.ciphertext:
import {
CipherMode,
PaddingMode,
eia3,
hexToBytes,
sm3Digest,
sm4Encrypt,
} from 'gmkitx';
const sm3 = sm3Digest('abc');
if (sm3 !== '66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0') {
throw new Error(`SM3 vector mismatch: ${sm3}`);
}
const sm4 = sm4Encrypt(
'0123456789abcdeffedcba9876543210',
hexToBytes('0123456789abcdeffedcba9876543210'),
{ mode: CipherMode.ECB, padding: PaddingMode.NONE },
);
if (sm4.ciphertext !== '681edf34d206965e86b3e94f536e4246') {
throw new Error(`SM4 vector mismatch: ${sm4.ciphertext}`);
}
const mac = eia3(
'000102030405060708090a0b0c0d0e0f',
0x01234567,
0x0a,
0,
hexToBytes('5bad724710ba1c56'),
64,
);
if (mac !== '1b3d0f74') {
throw new Error(`EIA3 vector mismatch: ${mac}`);
}这里使用 throw 而不是只打印结果或调用 console.assert,以便样例在不符合预期时以非零状态退出。
Java 固定断言
import cn.gmkit.core.HexCodec;
import cn.gmkit.sm3.SM3Util;
import cn.gmkit.zuc.ZUC;
String sm3 = SM3Util.digestHex("abc");
if (!"66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0".equals(sm3)) {
throw new IllegalStateException("SM3 vector mismatch: " + sm3);
}
String mac = ZUC.eia3(
"000102030405060708090a0b0c0d0e0f",
0x01234567,
0x0a,
0,
HexCodec.decodeStrict("5bad724710ba1c56", "EIA3 message"),
64);
if (!"1b3d0f74".equals(mac)) {
throw new IllegalStateException("EIA3 vector mismatch: " + mac);
}修改规则
- 增加标准向量时填写准确
source。 id一经发布即视为稳定测试标识;改变语义时新增 case,不复用旧id掩盖不兼容变化。- 字段新增应保持消费者向后兼容;重命名或删除必须同步修改 Java、TypeScript 测试和 CHANGELOG。
- 任何确定性期望值变更都应先由外部标准或独立成熟实现复核,不能只用本项目实现自证。
- SM2 等随机算法不把一次随机输出固化成“标准值”,应固定输入并验证解密、验签与篡改拒绝。
- 提交前运行
npm run parity,并运行 Java 与 TypeScript 完整测试,防止只更新一侧断言。 - 不得捕获向量加载或算法断言异常后仅打印 warning;互操作失败必须让进程以非零状态退出。