我正在实现一个包含 DES 和 3DES 功能的支持库。作为验证步骤,我使用它openssl
来检查我的结果。对于一个简单的测试,我这样做了:
echo -n "AAAAAAAA" | openssl enc -e -des-ecb -nosalt -pass pass:AABBCCDD | xxd
00000000: 2976 3faf 5e27 7187 5897 c640 c38c cf8b )v?.^'q.X..@...
即 ECB mod、plaintext AAAAAAAA
、keyAABBCCDD
和 no salting。但是这里有两件事让我觉得很奇怪。
(1) 对于 64 位输入和 64 位块大小,我希望输出具有与输入相同的长度
(2) 如果我将输入加倍为 128 位(所有 A,块长度的两倍),我实际上得到 24 字节的密文
$ echo -n "AAAAAAAAAAAAAAAA" | openssl enc -e -des-ecb -nosalt -pass pass:AABBCCDD | xxd
00000000: 2976 3faf 5e27 7187 2976 3faf 5e27 7187 )v?.^'q.)v?.^'q.
00000010: 5897 c640 c38c cf8b X..@....
在进行最后一个实验时,我注意到密文确实在块大小边界上重复(参见2976 3faf 5e27 7187
重复)。但是当达到这个阈值时,我们会得到额外的 8 个字节的密文。即,使用 7 个 A 给出 8 个字节的密文,但 8 个 A 给我们 16 个字节。所以对这个边界条件似乎有一些奇怪的处理?
无论如何,我检查了一些随机的在线 DES 工具,但没有一个与 openssl 提供的相匹配——但它们都相互匹配。从这些工具中,我认为上面第一个示例的正确密文是
54 55 ab a4 a2 b0 83 38
或者他们都以同样的方式错了。这里发生了什么?
您要求
openssl
将您的密钥用作密码,这是一个需要通过 KDF 函数才能用作所需长度的加密密钥的字符串。该-nosalt
选项仅禁用对 KDF 的盐输入,而不是一般 KDF 的使用。(OpenSSL 使用迭代的 MD5 算法作为其默认 KDF,尽管最近的版本支持标准 PBKDF2,并且通常会在未启用时警告您。)
要
AABBCCDD
用作文字 64 位密钥,必须通过以下方式指定-K
:现在你有两个密文块,加上一个填充块。该
openssl enc
工具应用PKCS#5 填充以确保它始终具有完整数量的输入块 - 例如,如果您的输入只有 15 个字节,最后一块将是 7 个字节,无法按原样加密。它不能简单地补零,因为接收者不知道它可以安全删除多少个零字节——毕竟,输入不一定是文本;它也可以是最后带有一定数量合法零字节的二进制数据。此处使用的特定填充形式使用最后一个字节来指示添加了多少填充,因此在解密后要删除多少填充。例如,如果最后一个明文块只有 6 个字节,它将被填充
0x02 0x02
(在加密之前),以便接收者知道它需要从解密的块中删除 2 个字节。但是当你的明文的最后一个块已经是全尺寸时,就没有空间来表明这个事实了——你不能有零长度的填充,因为接收者仍然会查看解密后的明文的最后一个字节并且会得到因为最后一个字节是一些任意数据而感到困惑。出于这个原因,添加了一个完整的填充块,由八个
0x08
字节组成。在 ECB 模式下,您可以通过尝试将填充解密为数据来看到这一点:
同样对于较短的输入:
如果您保证输入始终是密码块大小的精确倍数,那么您可以告诉 OpenSSL 使用该
-nopad
选项禁用填充并仅获取 16 个字节的密文:请注意,在解密时也必须指定填充的类型(或缺少填充)。
另请注意,如果您使用 加密
-nopad
但最后一个块未满,OpenSSL 将简单地将其丢弃并报告错误,因为它无能为力。