大多数代码可以靠「这看着对不对」来测。密码学不行。一个几乎正确的实现,吐出来的输出,和一个完全正确的实现在统计上分不出来——两边都是高熵的字节——一直到它悄悄地没法和别人互操作,或者更糟,开始泄漏。所以一个原语的测试,不是「它看着对吗」,而是「在相同输入、相同规则下,它是不是和一个可信参考实现一模一样的字节」。gm-crypto-rs 把每一个输出都按这条线来卡,而它有两半——后一半比前一半微妙。
前一半是已知答案测试(KAT)。标准里带了固定的输入/输出向量,实现得逐位、分毫不差地复现它们。这能抓住那些明摆着的错——一个写反的常量、一个字节序的疏忽、密钥编排里的一个差一错。必要,但不够:KAT 只覆盖了有人想到要写下来的那些输入。
所以后一半,是对着独立实现做差分测试。SM2/SM3/SM4 的输出,逐字节对照 gmssl 3.1.1;可调的磁盘加密模式 SM4-XTS,对照 OpenSSL 3.x。当两个由不同的人、从同一份标准写出来的实现,在一大批输入上对字节都一致,那「它们恰好都中了我这同一个 bug」的概率,就掉得很快。
而「一样的字节」这句话的分寸,正是在这儿挣出来的。同一个名字的算法,可以带着不同的约定;跨着约定去比,你会看到一个不是 bug 的不一致——或者更糟,盖住一个真是 bug 的。OpenSSL 的 SM4-XTS 必须被告知 xts_standard=GB,才会走 GB/T 的约定、而不是 IEEE 那套;指定错了标准,密文就会因为和正确性毫无关系的原因岔开。所以「符合」不是「和 OpenSSL 一样的字节」——而是「和配置到相同模式语义的 OpenSSL 一样的字节」。把比对的 harness 调对,是这件事一半的工作量。
这也正是 1.0.0 能给出一个用户可自查的说法的原因:线上输出和 0.16.0 逐字节一致——一样的 SM2 签名和密文、一样的 SM4 各模式字节——并和 gmssl 在 11 个互操作向量上 11 比 11 对上,所以 1.0 的破坏性改动只是 API 形状,不是行为。你可以升上来,自己 diff 那些字节。
逐字节一致是一个很强的符合性信号,不是正确性的证明:它说明你和你的参考实现一致——包括它们恰好一起错的地方——而且只在你测过的那些输入上。它和常量时间门禁、和 fuzzing 是并肩的,不是替代。那些向量、那些互操作对象、还有比对的配置,都在公开仓库里。