← 返回

工程正确性(二):Java、TypeScript 与 Native 的密码互操作

系列导航:11 | 12 | 13 | 14

Java、TypeScript 和 Native 各自完成“加密后能解密”,不能证明它们能互相解密。真正的差异经常出现在模式、填充、IV、Tag、Base64、ASN.1、签名输入和错误处理。GMKit 的跨语言验证把 vectors/interop.json 作为共享输入,再用双向矩阵检查每个实现的字节结果。

向量文件是协议边界

每条向量至少写明算法套件、密钥字节、明文字节、AAD、IV 或 nonce、密文、Tag、签名和编码方式。算法名不能只写 AES,要写模式和参数,例如 GCM 的 Tag 长度、CBC 的填充规则或椭圆曲线名称。JSON 的 UTF-8、字段顺序和 Base64 变体也属于协议。

flowchart TD V[vectors/interop.json] --> Java[Java 实现] V --> TS[TypeScript 实现] V --> Native[Native 实现] Java --> Matrix[跨端矩阵] TS --> Matrix Native --> Matrix Matrix --> Negative[篡改与格式负例] Negative --> Gate[Parity 与发布门禁]

项目生成的向量只能证明实现之间采用了同一套错误或正确的规则,不能自动证明符合外部标准。标准向量、独立库结果和固定边界样本要在来源字段中区分。

双向矩阵怎么列

以信封加密为例,至少覆盖:Java 生成,TypeScript 解封;TypeScript 生成,Java 解封;Native 生成,Java 解封;Java 生成,Native 解封。签名也做同样的方向矩阵。固定向量使用固定 nonce 便于复现,生产接口通过安全随机数生成器提供 nonce,测试入口和生产入口分离。

矩阵中记录运行时、密码库版本和提供者。依赖升级、Native 编译选项变化或浏览器基线变化都会触发完整矩阵。失败报告保存向量 ID、字段摘要和错误枚举,不保存生产密钥、完整明文或私密内容。

字节格式要逐字段冻结

签名输入是原始 JSON、规范化 JSON 还是排序后的 query string,必须写成函数契约。字段顺序、空格、换行和时间单位一项不同,签名就不同。Base64 标准字母表和 URL Safe 字母表不能混用,Hex 是否大小写敏感也要写进测试。

密钥容器要区分 PKCS#1、PKCS#8 和 SubjectPublicKeyInfo。椭圆曲线签名还要固定曲线、点编码和 r || s 与 ASN.1 DER 的转换。Native API 使用缓冲区和长度,不能按零结尾字符串传递二进制;输出长度查询、容量不足和释放责任都进入 FFI 契约。

ZUC parity 漂移的处理顺序

当 ZUC 的 TypeScript 测试和 Java 结果不一致时,先检查共享向量和常量定义,再改算法。一次审计中,TypeScript 使用标准的 15 位 D_128 常量,Java 却使用压缩后的错误数组;如果只改测试期望值,两端会共同保留错误行为。正确流程是确认标准来源、修复实现、保留失败向量,再运行全方向 parity。

SM9 的能力范围

GMKit 的 gmkit-sm9 是 Java/Native 的 SM9 入口,Native 运行时随同一 JAR 打包。TypeScript 包不支持 SM9,文档和 API 能力矩阵要明确写出。跨语言矩阵不能为了对齐名称而声称 TypeScript 具备未实现算法。

负例同样需要互操作

篡改 ciphertext、Tag、AAD、签名、密钥 ID 或编码后,所有实现都必须拒绝。未知协议版本、未知套件、重复 JSON 键、超长字段和错误 DER 进入解析层后先限制长度,再映射为稳定错误。对外错误可以统一为认证失败,内部指标保留格式错误、未知密钥和不支持套件,避免泄露攻击有用信息。

密钥轮换测试覆盖旧信封可读、新信封使用新键、旧键过期、未知 key ID 和回滚发送端。发送端只使用当前写版本,接收端在保留期内支持历史读版本。轮换结果写入审计,日志只记录套件、key ID 和错误大类。

互操作不等于安全评估

向量通过只能说明各端理解同一协议。随机数质量、密钥托管、侧信道、权限和密钥轮换仍需独立审查。发布门禁应把标准向量、跨端生成验证、负例和打包产物一起检查,不能以单端单测绿灯代替。

参考资料