iOS 中的 AES 解密:PKCS5 padding 和 CBC
Posted
技术标签:
【中文标题】iOS 中的 AES 解密:PKCS5 padding 和 CBC【英文标题】:AES decryption in iOS: PKCS5 padding and CBC 【发布时间】:2011-06-24 12:04:54 【问题描述】:我正在为 ios 实现一些解密代码,用于源自我无法控制的服务器上的消息。之前在另一个平台上的实现记录了 AES256 的解密要求,指定了密钥和初始化向量,并且还说:
* Cipher Mode: CBC
* Padding: PKCS5Padding
用于创建 CCCryptor 对象的选项仅包括 kCCOptionPKCS7Padding 和 kCCOptionECBMode,注意 CBC 是默认设置。根据我对加密填充的了解,我不明白如何同时使用这两种方法;我以为它们是相互排斥的。在为解密创建 CCCryptor 时,我尝试使用 0 作为选项和 kCCOptionPKCS7Padding,但解密后都给我乱码。
我已经将这个解密的转储与另一个平台上的解码字节缓冲区的转储进行了比较,并确认它们确实不同。所以在这个实现中我做的一些不同的事情有很大的不同,我只是不知道是什么......而且不知道如何处理它。这些平台非常不同,很难从以前的实现中推断出很多,因为它基于一个非常不同的平台。当然,之前实现的作者已经离开了。
你猜猜还有什么不兼容的或者如何解决这个问题?
【问题讨论】:
【参考方案1】:首先,您可以稍后再考虑填充。像您所做的那样提供0
意味着没有填充的AES CBC,并且通过该配置,您应该可以很好地看到您的消息。 Albiet 最后可能带有一些填充字节。这样就离开了:
-
您没有正确加载密钥。
您没有正确加载 IV。
您没有正确加载数据。
服务器正在做一些你意想不到的事情。
要调试它,您需要隔离您的系统。您可以通过实施一个环回测试来做到这一点,在该测试中,您都加密然后解密数据,以确保您正确加载所有内容。但这可能会产生误导。即使您做错了什么(例如,向后加载密钥),您仍然可以解密您加密的内容,因为您在双方都以完全相同的错误方式进行操作。
因此,您需要针对 Known Answer Tests
(KAT) 进行测试。您可以在AES wikipedia entry 上查找官方 KAT。但碰巧我在这里发布了another answer,我们可以使用。
鉴于此输入:
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 是一大步。它说您的实现是正确的,但是您对服务器行为的假设是错误的。然后是时候开始质疑这些假设了......
(和 PS,你提到的那些选项不是相互排斥的。ECB 表示没有 IV,CBC 表示你有 IV。与填充无关。)
好的,我知道我说这是一个练习,但我想证明即使你使用填充加密
并在没有填充的情况下解密,您不会得到垃圾。因此,鉴于使用 PKCS7 填充的 KAT,我们使用 no padding 选项对其进行解密,并获得一条可读消息,后跟 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
$
【讨论】:
【参考方案2】:事实证明,对我所经历的事情的解释非常简单:我误解了我在之前的实现中读到的内容,暗示它使用的是 256 位密钥,但实际上它使用的是 128 位密钥钥匙。做出改变,突然之间,晦涩的东西变成了明文。 :-)
0 用于调用 CBC 的 options 参数实际上是正确的。以前的实现中对 PKCS5 填充的引用仍然是个谜,但这并不重要,因为我现在所拥有的可以工作。
感谢拍摄,个人。
【讨论】:
【参考方案3】:PKCS#5 padding and PKCS#7 padding are practically the same(添加字节 01,或 0202,或 0303 等直到算法的块大小的长度,在这种情况下为 16 个字节)。正式的 PKCS#5 填充应该只用于 8 字节块,但在许多运行时,两者可以毫无问题地互换。填充总是出现在密文的末尾,所以如果你只是胡言乱语,那就不是填充。 ECB 是一种块操作模式(不应该用于加密可以与随机数区分开的数据):它需要填充,因此两者不是互斥的。
最后,如果您只是执行解密(不是 MAC'ing 或其他形式的完整性控制),并且您将 unpadding 的结果返回给服务器(解密失败),那么您的纯文本数据是不安全的,因为 padding oracle攻击。
【讨论】:
PKCS#5 填充不与 PKCS#7 填充相同。虽然两者都填充到块长度,但 PKCS#5 填充仅适用于具有 8 字节块的密码算法,PKCS#7 将其扩展到任意块长度。在所需的填充少于 8 个字节的情况下,它们的行为可能相同,并且在 Java PKCS 库中,它们使用 PKCS#7 填充并调用它是 PKCS#5,但它们不同 @DaveDurbin 是的,I know,但在 Java 中 - 它使用为服务器配置提供的确切"PKCS5Padding"
字符串 - 它实际上与 PKCS#7 填充相同。大多数允许 PKCS#5 填充不同分组密码的应用程序就是这种情况。在答案中澄清了这一点......【参考方案4】:
保罗, 需要 PKCS#5 填充来识别解密数据中的填充。对于 CBC,输入缓冲区必须是密码块大小的倍数(AES 为 16)。因此,要加密的缓冲区使用额外的字节进行扩展。请注意,加密后,数据的原始大小会丢失。 PKCS#5 填充允许检索该大小。这是通过用重复字节填充扩展数据缓冲区来完成的,其值等于填充大小。例如,如果您的明文缓冲区是 12 个字节,要使其成为 16 的倍数,您将需要再添加 4 个字节。 (如果数据是 16,您将再添加 16 以使其成为 32)。然后用 '0x4' 填充这 4 个字节以符合 PKCS#5 填充。解密时,只需查找解密数据中的最后一个字节,然后从解密缓冲区的长度中减去该数字。
你正在做的是用'0'填充。尽管您似乎很高兴看到结果,但当您的原始数据以多个“0”之一结尾时,您会感到惊讶。
【讨论】:
以上是关于iOS 中的 AES 解密:PKCS5 padding 和 CBC的主要内容,如果未能解决你的问题,请参考以下文章
AES/ECB/PKCS5Padding-128在JAVA,PHP,JavaScript, Python,Golang五种语言中的相互加解密
Java实现AES ECP PKCS5Padding加解密工具类
AES对称加解密工具类(AES/GCM/PKCS5Padding)