在国密改造项目里,最容易出问题的地方不是算法选择,而是实现是否正确。同一份密钥、同一段明文,两个声称都支持 SM4 的系统算出的密文不一样,这种情况相当常见。原因可能是 S 盒抄错了、密钥扩展的实现有偏差、或者填充方式不一致。要快速定位问题,最有效的工具是官方测试向量。
什么是测试向量
测试向量是一组预先确定好的输入和期望输出。对于 SM4,它包含四项内容:密钥、明文、模式与填充方式、以及期望的密文。
只要把密钥和明文按指定方式输入,选择指定的模式与填充,算出的密文就应当与期望值逐字节一致。如果不一致,说明实现有问题。
GB/T 32907-2016 的附录 A.1 给出了官方的标准测试向量:
密钥:0123456789abcdeffedcba9876543210
明文:0123456789abcdeffedcba9876543210
模式:ECB
填充:NoPadding
密文:681edf34d206965e86b3e94f536e4246
注意明文和密钥是相同的 16 字节,而且正好是 16 字节,所以不需要填充,可以直接用 NoPadding 模式验证。这排除了填充实现带来的干扰,让测试只关注算法核心。
为什么 ECB 加 NoPadding 最适合验证
测试向量选择 ECB 和 NoPadding 是有讲究的。
ECB 是最简单的模式,每个分组独立加密,不涉及 IV、不涉及前一块密文的参与。这意味着如果密文不对,问题一定出在算法核心(S 盒、轮函数、密钥扩展),而不是模式实现。
NoPadding 排除了填充逻辑的影响。明文恰好 16 字节,不需要补齐,所以结果只反映算法本身。
这两点结合起来,把测试范围压缩到了最小。如果这组向量能通过,说明算法核心是对的;如果通不过,就可以确定问题在核心实现,不需要再去怀疑模式或填充。
通过向量之后还要测什么
通过了标准向量,只能说明算法核心正确,还有几类问题需要单独验证。
第一是模式实现。用 CBC 模式加密一段多块数据,与一个可信实现(例如 OpenSSL 的 sm4-cbc)比对结果。这一步会同时验证 IV 的处理和块串联逻辑。
第二是填充实现。用 PKCS#7 加密一段长度不是 16 整数倍的数据,再解密回来,确认能正确还原。特别要测试长度恰好是 16 整数倍的情况——这时按 PKCS#7 规则会补满一整块,解密后应当去掉这一整块,而不是留下 16 个多余的字节。
第三是流式模式。用 CTR 加密一段长度不是 16 整数倍的数据,确认密文长度与明文完全相同(不需要填充)。这一步能验证计数器管理和密钥流生成的正确性。
第四是中文与特殊字符。用一段包含中文、emoji、换行的文本做加解密往返测试。这一步验证的是编码处理——明文应当先按 UTF-8 编码成字节再加密,如果某一步用了其他编码,往返就会失败或得到乱码。
跨实现对接时的验证方法
当两个不同系统需要互通时,不要假设「参数一样结果就一样」。
正确的做法是准备一组固定的测试数据:一段确定的明文、一把确定的密钥、一个确定的 IV。在对端算一遍密文,在本地用相同参数算一遍,逐字节比对。
如果一致,说明参数和实现都对上了,可以继续联调。
如果不一致,按顺序排查:先确认算法是不是同一个(SM4 与 AES 的密文毫无关系);再确认密钥和 IV 的字节序列是否相同(可能一方按文本输入、另一方按十六进制输入,导致实际字节不同);然后确认模式和填充是否一致;最后才怀疑实现有 bug。
这个排查顺序的价值在于从最常见的错误开始排除,而不是一上来就怀疑算法实现。实际经验中,参数不一致导致的失败远多于实现错误。
一个容易忽略的细节
测试向量里的密钥和明文都是用十六进制表示的。在工具或代码里输入时,要确认输入模式选的是「Hex」而不是「文本」。
如果选了文本模式,那 32 个字符会被当作 UTF-8 文本编码成 32 字节,而 SM4 的密钥只需要 16 字节,结果要么报长度错误,要么被截断成前 16 个字符的字节——算出来的密文当然与期望值不同。
这类「输入模式选错」的问题是排查时最容易被忽略的,因为它不会报错,只是结果不对。所以在比对测试向量之前,先确认输入模式。
检查清单
- 测试向量包含密钥、明文、模式与填充、期望密文四项
- GB/T 32907-2016 附录 A.1 提供了 SM4 的官方向量,可用于核心验证
- 用 ECB + NoPadding 测试能把问题范围压缩到算法核心
- 通过向量后仍需单独验证模式、填充、流式模式和中文编码
- 跨实现对接时用固定测试数据双向比对,不要假设参数相同就结果相同
- 输入测试向量时确认选择的是 Hex 模式,文本模式会导致字节数不符