iOS中的AES解密:PKCS5填充和CBC

10

我正在iOS上实现一些解密代码,以解密来自我无法控制的服务器上的消息。先前在另一个平台上的实现记录了解密要求AES256,指定了密钥和初始化向量,并指出:

 * Cipher Mode: CBC
 * Padding: PKCS5Padding
创建CCCryptor对象的选项仅包括kCCOptionPKCS7Padding和kCCOptionECBMode,其中CBC是默认值。就我对加密填充的理解,我不明白如何同时使用它们;我认为它们是互斥的。在为解密创建CCCryptor时,我尝试使用0和kCCOptionPKCS7Padding作为选项,但两者都会在解密后产生无意义的结果。
我已将此解密的转储与其他平台上解码的字节缓冲区的转储进行了比较,并确认它们确实是不同的。因此,在此实现中,我正在执行某些明显不同的操作,只是我不知道是什么...也不知道如何处理。由于平台之间的差异很大,因此很难从以前的实现中推断出太多内容,因为它基于非常不同的平台。当然,以前的实现作者已经离开了。
还有其他可能不兼容的问题或如何进行故障排除?
4个回答

7
PKCS#5填充和PKCS#7填充实际上是相同的 (在这种情况下,向数据块大小添加字节01、0202或0303等,长度为16字节)。官方上应该只使用PKCS#5填充来处理8个字节的块,但在许多运行时中,这两者可以互换而不会出现问题。填充始终发生在密文的末尾,因此如果你只获得了一堆无意义的乱码,那么它很可能不是填充造成的。ECB是一种操作块模式(不应用于加密可以与随机数区分开的数据):它需要填充,因此两者并不是互斥的。

最后,如果你只进行解密(没有进行MAC或其他形式的完整性控制),并将去掉填充的结果返回到服务器(解密失败),由于填充预言攻击,你的明文数据是不安全的。


PKCS#5填充与PKCS#7填充不同。虽然两者都可填充到块长度,但PKCS#5填充仅适用于具有8字节块的加密算法,而PKCS#7则将其扩展到任意块长度。当填充要求少于8个字节时,它们可能会表现相同,在Java PKCS库中,它们使用PKCS#7填充并将其称为PKCS#5,但它们是不同的。 - Dave Durbin
@DaveDurbin 是的,我知道,但在Java中 - 使用与服务器配置给定的确切"PKCS5Padding"字符串 - 它实际上与PKCS#7填充完全相同。这适用于大多数允许不同块密码使用PKCS#5填充的应用程序。在答案中澄清了这一点... - Maarten Bodewes

5
首先,您可以稍后担心填充问题。像您所做的那样提供 0 意味着使用无填充的AES CBC,以该配置,您应该可以看到您的消息。虽然可能在末尾有一些填充字节。所以剩下的是:
  1. 您没有正确加载密钥。
  2. 您没有正确加载IV。
  3. 您没有正确加载数据。
  4. 服务器正在执行您不希望的操作。

要调试此问题,您需要隔离您的系统。您可以通过实现回环测试来完成这项工作,在其中加密和解密数据以确保您正确加载了所有内容。但是这可能会产生误导。即使您做错了某些事情(例如,反向加载密钥),您仍然可以解密您已经加密的内容,因为您在两侧都以完全相同的错误方式进行操作。

因此,您需要根据已知答案测试(KAT)进行测试。您可以在AES维基百科条目上查找官方KAT。但恰巧我已经在SO上发布了另一个答案,我们可以使用它。

给定此输入:

KEY: 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
     0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
     0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
     0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
IV:  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
PLAIN TEXT:   encrypt me
CIPHER TEXT:  338d2a9e28208cad84c457eb9bd91c81

使用第三方程序验证您是否可以解密密码文本并获取明文。

$ echo -n "encrypt me" > to_encrypt
$ openssl enc -in to_encrypt -out encrypted -e -aes-256-cbc \
> -K 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f \
> -iv 0000000000000000
$ hexdump -C encrypted
00000000  33 8d 2a 9e 28 20 8c ad  84 c4 57 eb 9b d9 1c 81  |3.*.( ....W.....|
00000010
$ openssl enc -in encrypted -out plain_text -d -aes-256-cbc \
> -K 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f \
> -iv 0000000000000000
$ hexdump -C plain_text 
00000000  65 6e 63 72 79 70 74 20  6d 65                    |encrypt me|
0000000a

现在尝试在您的程序中解密这个已知答案测试。请确保启用PKCS7填充,因为这是我在此示例中使用的填充方式。作为练习,尝试不使用填充进行解密,并查看结果是否相同,只是在“加密我”文本后面有填充字节。
实施KAT是一个重要的步骤。它表示您的实现是正确的,但是您对服务器行为的假设是错误的。然后就是时候开始质疑这些假设了...
(顺便说一下,您提到的那些选项并不是互斥的。ECB表示没有IV,而CBC表示您有一个IV。与填充没有关系。)
好吧,我知道我说过这是一个练习,但我想证明即使您使用填充进行加密,不使用填充进行解密,也不会得到垃圾数据。因此,给定使用PKCS7填充的KAT,我们使用“无填充”选项进行解密,并获得可读的消息,后面跟随着用作填充字节的06
$ openssl enc -in encrypted -out plain_text -d -aes-256-cbc \
-K 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f \
-iv 0000000000000000 -nopad
$ hexdump -C plain_text
00000000  65 6e 63 72 79 70 74 20  6d 65 06 06 06 06 06 06  |encrypt me......|
00000010
$ 

3
Paul, PKCS#5填充是用于识别解密数据中的填充内容。对于CBC模式,输入缓冲区必须是密码块大小的倍数 (AES为16)。因此,需要在要加密的缓冲区中添加额外的字节。请注意,在加密后,原始数据的大小会丢失。 PKCS#5填充允许检索该大小。这是通过使用重复的字节填充扩展数据缓冲区来完成的,其值等于填充大小。例如,如果您的明文缓冲区是12个字节,则需要添加4个字节使其成为16的倍数。(如果数据是16,则需要添加16个字节以使其成为32)。然后,将这4个字节填充为'0x4'以符合PKCS#5填充。解密时,只需查找解密数据中的最后一个字节,并从解密缓冲区的长度中减去该数字即可。
你正在使用'0'进行填充。虽然你似乎对结果感到满意,但当你的原始数据以一个或多个'0'结尾时,你会得到一个惊喜。

0
原来我所遇到的问题的解释非常简单而尴尬:我曲解了以前实现中读到的某些内容,认为它使用了256位密钥,但实际上它使用的是128位密钥。进行更改后,突然间原本晦涩难懂的东西变得清晰易懂了。 :-)
对于选项参数而言,使用0来调用CBC确实是正确的。之前实现中提到的PKCS5填充仍然是个谜,但这并不重要,因为现在我拥有的能够工作。
感谢你的帮助,indiv。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接