在Swift(iOS)和PHP中使用AES256加密得到不同的结果

12

我正在使用AES256进行加密/解密,以便在iOS和PHP之间使用不安全的通道进行通信。

我看到了许多类似的问题,这些问题围绕着密钥大小、模式(CBC或ECB)、使用随机iv等方面。但在这种情况下,我发现了一个奇怪的行为,如下所示。

两个环境中的配置: - 密钥:32字节(256位) - 块大小:128位(标准) - iv:16字节(用于测试的静态值) - 模式:CBC

如果我加密一个16或32字节的文本(以匹配AES块大小),那么Swift和PHP中的结果相似但不完全相同:

key = "12345678901234567890123456789012" plainText = "12345678901234567890123456789012" iv = "1234567890123456"

Swift cipher = e5RnnlJkv4QGnGhkMwfvgMHr80NWUVhbvvfCdPQ5V2KyKJTx4KfWmn4HXi4dG0b8 PHP cipher = e5RnnlJkv4QGnGhkMwfvgMHr80NWUVhbvvfCdPQ5V2I=

正如您所看到的,密码的长度以及PHP Base64字符串的最后2个字符存在差异。

但是,如果我使用的文本不是AES128块大小的倍数,比如“Hello World”,则两个环境会报告不同(但大小相同)的密码,如下所示:

Swift cipher = bdwO/5C8a+pliIoIXtuzfA==

PHP cipher = oPotHCkxpOwQhIaCz6hNMw==

在这两种情况下(Swift和PHP),无论明文的大小如何,密码都可以正确解密。此外,Swift的结果与Objective-C版本的代码一致。

以下是使用的简化代码:

PHP

    $key = "12345678901234567890123456789012"; 
    $iv = "1234567890123456";
    $plaintext = "Hello World";
    $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plaintext, MCRYPT_MODE_CBC, $iv);
    $ciphertext_base64 = base64_encode($ciphertext);
    echo  "ciphertext: ".$ciphertext_base64."</br>";

快捷

let keyData: NSData! = (key as NSString).dataUsingEncoding(NSUTF8StringEncoding) as NSData!
let keyBytes         = UnsafePointer<UInt8>(keyData.bytes)
let keyLength        = size_t(kCCKeySizeAES256)

let plainData = (plainText as NSString).dataUsingEncoding(NSUTF8StringEncoding) as NSData!
let dataLength    = UInt(plainData.length)
let dataBytes     = UnsafePointer<UInt8>(plainData.bytes)

var bufferData    = NSMutableData(length: Int(dataLength) + kCCBlockSizeAES128)
var bufferPointer = UnsafeMutablePointer<UInt8>(bufferData.mutableBytes)
let bufferLength  = size_t(bufferData.length)

let operation: CCOperation = UInt32(kCCEncrypt)
let algoritm:  CCAlgorithm = UInt32(kCCAlgorithmAES128)
let options = UInt32(kCCOptionPKCS7Padding)

let ivData: NSData! = (iv as NSString).dataUsingEncoding(NSUTF8StringEncoding) as NSData!
let ivPointer = UnsafePointer<UInt8>(ivData.bytes)

var numBytesEncrypted: UInt = 0

var cryptStatus = CCCrypt(operation, algoritm, options, keyBytes, keyLength, ivPointer, dataBytes, dataLength, bufferPointer, bufferLength, &numBytesEncrypted)

bufferData.length = Int(numBytesEncrypted)
let base64cryptString = bufferData.base64EncodedStringWithOptions(.Encoding64CharacterLineLength)
println(base64cryptString)

为什么它们不同呢?


你如何计算IV? - Jayprakash Dubey
1个回答

9
这是由于填充模式不同所致。
如果明文不是块大小的 N 倍,则 PHP 使用“零填充”。因此,对于 128 位块密码(如 AES),PHP 对 0..15 字节使用值为 00 来填充。对于以块边界结束的明文,它将不添加任何填充字节。
大多数其他语言使用 PKCS#7 填充,该填充会填充到下一个块边界,其中填充字节反映了添加的字节数。因此,这将是 1..16 字节,其值为 1..16(或十六进制中的 01 到 10)。对于以块边界结束的明文,它将添加 16 字节的填充。
PKCS#7 填充是确定性的,并且不取决于明文值(它可以由任何值字节组成,而不仅仅是文本); 换句话说,它始终可以独立于内容应用和删除。
零填充存在这样的问题,即以 00 字节结尾的纯文本可能在取消填充时删除这些 00 字节。对于与 ASCII 兼容的字符串,这通常不是问题,因为 00 是控制字符,通常表示文件结束符(EOF)。
请查看有关如何在 PHP 中应用 PKCS#7 填充的“mcrypt_encrypt”注释。

1
请注意,自2007年以来,底层的mcrypt库尚未更新。PHP mcrypt维护不良,示例和文档非常糟糕(我设法重写了mcrypt_encrypt示例代码,但现在代码上的注释已经不同步了)。 - Maarten Bodewes
1
不是这样的。如果大小为16字节,则PHP不会添加任何填充。在PKCS#7填充的情况下,它将添加16个字节。请注意,我无法使用您指定的密钥和IV解密任何数据,因此我无法检查Swift实现。 - Maarten Bodewes
2
@owlstead。在PHP中,只需2行即可完成PKCS#7填充。$padding = 16 - (strlen($plaintext) % 16); $data = $plaintext.str_repeat(chr($padding), $padding); - eharo2
1
@fancoolo 在将明文(消息)提供给 mcrypt_encrypt 之前,您需要对其进行处理,并在 mcrypt_decrypt 之后直接执行相反的操作(从结果中删除 X 字节,其中 X 是最后一个字节的值)。 - Maarten Bodewes
2
@fancoolo。在调用crypt_encrypt()函数之前,您需要添加以下2行代码:$padding = 16 - (strlen($text) % 16); $data = $text.str_repeat(chr($padding), $padding); $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv); 希望这可以帮到您..... - eharo2
显示剩余3条评论

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