DES 的奇偶校验位到底管不管用

工具相关 ·

在配置 DES 密钥时,可能会遇到一个让人困惑的现象:用 0123456789abcdef 和用 0022446688aaccee 这两组看起来完全不同的密钥,加密同一段明文,得到的结果竟然一模一样。这不是工具出了 bug,而是 DES 密钥里那 8 位奇偶校验位在起作用。

校验位是怎么安排进去的

DES 的密钥是 8 个字节,也就是 64 位。规范规定,每个字节的第 8 位(最低位)是奇偶校验位,其余 7 位才是真正的密钥位。

8 个字节 × 7 位 = 56 位有效密钥。这就是「64 位密钥、56 位强度」这个说法的来源。

奇偶校验的规则是:每个字节里 1 的个数应当是奇数(也有实现用偶数,规范里 DES 用的是奇校验)。如果输入密钥的某个字节不满足这个条件,理论上应当被判定为非法密钥。

为什么实际实现不校验

理论上应该有校验,但实际工程中几乎所有实现都不做这件事。

OpenSSL 不校验,Java 的 JCE 不校验,crypto-js 不校验,浏览器里的在线工具也不校验。原因是校验位对加密安全没有任何贡献——它只是用来检测人工输入错误,而在程序化使用密钥的场景里,密钥来自配置文件或密钥管理系统,不会出现「手抖输错一位」的情况。

不校验的后果是:密钥的第 8 位被直接忽略,无论填 0 还是填 1,参与运算的只有前 7 位。

这就解释了开头的现象。把 0123456789abcdef 和 0022446688aaccee 写成二进制对比,会发现它们的每个字节里,前 7 位是相同的,只有最低位不同。既然最低位不参与运算,两组密钥自然是等价的。

0x01 = 0000 0001
0x00 = 0000 0000
        ↑ 只有最低位不同

0x23 = 0010 0011
0x22 = 0010 0010
        ↑ 只有最低位不同

这意味着什么

从安全角度看,这个现象提醒我们一件事:DES 的密钥空间比表面数字更小。

输入密钥是 16 个十六进制字符,看起来有 16^16 种可能,但实际上每个字节只有 7 位有效,等价密钥的数量是 2^8 = 256 倍——也就是说,平均每 256 个不同的输入密钥,对应同一个实际密钥。

这一点在穷举破解时没有帮助(攻击者本来就可以只穷举 56 位空间),但在评估「密钥熵」时需要注意。如果密钥是从某个空间随机生成的,而生成方式没有考虑这个冗余,实际的熵会比预期低。

从工程角度看,这个现象也有一个实用价值:它可以用来验证实现是否符合规范。如果你用两组「只差校验位」的密钥加密同一段数据,得到的结果应该完全相同。如果不同,说明该实现做了校验位处理(或者密钥扩展有 bug),在与老系统对接时可能造成不一致。

实践中的建议

第一,生成 DES 密钥时,不必刻意去计算正确的校验位。既然主流实现都不校验,随便填 0 或者填 1 都可以。但如果密钥要交给一个可能校验校验位的旧系统,就需要按规范把校验位算对。

第二,如果需要与老系统对接,最好用一组已知的测试向量验证两边结果一致,而不是假设「密钥填一样结果就一样」。

第三,DES 的密钥冗余是一个历史包袱,使用现代算法时不会遇到。AES 的密钥长度是 128、192、256 位,没有校验位设计,每一位都是有效密钥位。

第四,如果系统里在用 DES 并且需要迁移,注意密钥的存储形式——如果旧系统存的是带校验位的 16 位十六进制,迁移到新算法时需要先确定哪些位是有效的,避免把冗余位当成密钥的一部分。

检查清单

  • DES 密钥每个字节的最低位是奇偶校验位,只有 7 位参与运算
  • 主流实现(OpenSSL、Java、crypto-js)都不校验校验位,直接忽略
  • 只差校验位的两组密钥会产生完全相同的密文,这是正常现象
  • 等价密钥使实际密钥空间看起来比输入空间小 256 倍
  • 可以用「只差校验位」的密钥对来验证一个实现是否符合规范
  • AES 没有校验位设计,每一位都是有效密钥位,不存在这个问题
阅读 14