使用PHP解密AES CTR Little Endian

时间:2013-05-10 19:27:20

标签: php cryptography aes mcrypt commoncrypto

我在使用PHP解密使用iOS 5.x的CommonCrypto库加密的字符串时遇到问题。以下是参数:

Algorithm: AES-128
Mode: CTR
Mode options: CTR Little-Endian
Padding: None

以下是我最好的尝试样本:

<?php
$encrypted = base64_decode('MlNFlnXE1sqIsmKZRtjChBvUMgiJlXgdjHVxQJ6JK24Id4uaN9NK/nBtY+cgrMJR/PRJRCmIUx0boQO5XqJYZ8VJ0w==');
$key = base64_decode('HB+dD1Irj2rXQ/nO+IuqSiK9xVE3PD9cZGIGzrMtwtA=');
$iv = base64_decode('2gxxKYU/G4lj7174e5wj+g==');

$cryptor = mcrypt_module_open('rijndael-128', '', 'ctr', '');
mcrypt_generic_init($cryptor, $key, $iv);
echo mdecrypt_generic($cryptor, $encrypted);

mcrypt_generic_deinit($cryptor);
mcrypt_module_close($cryptor);

输出如下:

Lorem ipsum dolo?N??]ѕȢ?+?
                                             ????x??k????}??'???Ŧ??;t

但它应该是“ Lorem ipsum dolor sit amet,consectetur adipisicing elit,sed do ... ”(包括尾随省略号。)

块大小为16,正好是前16个字符。这似乎表明Mcrypt和CommonCrypto之间的AES CTR反向递增过程不匹配。到目前为止,我听到的每个人都认为这是Big Endian与Little Endian的问题。

我花了好几天时间试图弄清楚所有这些字数和反对递增的东西,但它仍然是我的伏都教。 :-(我只需要一些正确解密我的字符串的PHP代码。我不在乎算法的工作速度有多快。我愿意放弃Mcrypt,支持PHP原生解决方案或其他一些PHP扩展,只要这是一个常见的。但是,改变iOS方面的东西是不是一个选项。

请帮忙!

2 个答案:

答案 0 :(得分:3)

分组密码模式非常简单,如果两个实现不兼容,您可以自己实现它们。

以下是针对您的特定情况的点击率实施:

function ctr_crypt($str, $key, $iv) {
    $numOfBlocks = ceil(strlen($str) / 16);

    $ctrStr = '';
    for ($i = 0; $i < $numOfBlocks; ++$i) {
        $ctrStr .= $iv;

        // increment IV
        for ($j = 0; $j < 16; ++$j) {
            $n = ord($iv[$j]);
            if (++$n == 0x100) {
                // overflow, set this one to 0, increment next
                $iv[$j] = "\0";
            } else {
                // no overflow, just write incremented number back and abort
                $iv[$j] = chr($n);
                break;
            }
        }
    }

    return $str ^ mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $ctrStr, MCRYPT_MODE_ECB);
}

算法非常简单:您总是追加并递增IV,直到您有一个比输入字符串更长(或长度相等)的字符串。然后使用ECB模式加密此字符串,并使用输入字符串对其进行异或。

增量是这里复杂的部分,因为我们正在处理二进制数。 Little Endian意味着我们从左向右递增(j = 0,j <16,j ++)。 Big Endian意味着我们从右向左递增(j = 15,j> = 0,j - )。

尝试一下:

$encrypted = base64_decode('MlNFlnXE1sqIsmKZRtjChBvUMgiJlXgdjHVxQJ6JK24Id4uaN9NK/nBtY+cgrMJR/PRJRCmIUx0boQO5XqJYZ8VJ0w==');
$key = base64_decode('HB+dD1Irj2rXQ/nO+IuqSiK9xVE3PD9cZGIGzrMtwtA=');
$iv = base64_decode('2gxxKYU/G4lj7174e5wj+g==');

var_dump(ctr_crypt($encrypted, $key, $iv));
// string(67) "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do..."

注意:ctr_crypt既可用作加密,也可用作解密功能。​​

答案 1 :(得分:0)

看起来PHP的mcrypt使用其他CTR模式(big endian而不是little endian)。关键时间表和IV都可以(正如您在解密第一个块时所看到的那样),但IV递增功能出现了问题。

请参阅以下PHP代码,该代码通过在ECB模式下手动递增IV和加密gamma来解决您的问题:

<?php
$encrypted = base64_decode('MlNFlnXE1sqIsmKZRtjChBvUMgiJlXgdjHVxQJ6JK24Id4uaN9NK/nBtY+cgrMJR/PRJRCmIUx0boQO5XqJYZ8VJ0w==');
$key = base64_decode('HB+dD1Irj2rXQ/nO+IuqSiK9xVE3PD9cZGIGzrMtwtA=');
$iv = base64_decode('2gxxKYU/G4lj7174e5wj+g==');

$ivarr = array_values(unpack('C*', $iv));
$ivdata = array_merge($ivarr);
$blocklen = count($ivarr);

for ($i = 1; $i < strlen($encrypted)/strlen($iv); $i++)
{
    // incrementing IV
    $ivarr[0] += 1;
    if ($ivarr[0] == 256)
    $ivarr[0] = 0;

    $j = 0;
    while ($ivarr[$j] == 0)
    {   
    $j++;
    if ($j == $blocklen)
        break;
    $ivarr[$j]++;
    }
    // appending to array
    var_dump($ivarr);
    $ivdata = array_merge($ivdata, $ivarr);
}

// now ivdata contains the full CTR gamma. Encrypting it.

$cryptor = mcrypt_module_open('rijndael-128', '', 'ecb', '');
$res = mcrypt_generic_init($cryptor, $key, "");

$ivdatastr = implode(array_map("chr", $ivdata));
$ivdecr = mcrypt_generic($cryptor, $ivdatastr);

$ivdecr = array_values(unpack('C*', $ivdecr));
$decrypted = array_values(unpack('C*', $encrypted));
$i = 0;

for ($i = 0; $i < count($decrypted); $i++)
{
    $decrypted[$i] = $decrypted[$i] ^ $ivdecr[$i % count($ivdecr)];
}    

$string = implode(array_map("chr", $decrypted));

var_dump($string);

mcrypt_generic_deinit($cryptor);
mcrypt_module_close($cryptor);
?>