【问题标题】:Decrypting AES CTR Little Endian with PHP用 PHP 解密 AES CTR Little Endian
【发布时间】:2013-05-05 13:35:04
【问题描述】:

我在使用 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-native 解决方案或其他一些 PHP 扩展,只要这是一个常见的问题。但是,在 iOS 端进行更改不是一种选择。

请帮忙!

【问题讨论】:

标签: php cryptography aes mcrypt commoncrypto


【解决方案1】:

看起来 PHP 的 mcrypt 使用其他 CTR 模式(大端而不是小端)。密钥调度和 IV 都可以(正如您在解密第一个块时看到的那样),但 IV 递增函数出现问题。

请参阅以下 PHP 代码,它通过手动递增 IV 并在 ECB 模式下加密 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);
?>

【讨论】:

  • 关于如何使用 Little Endian 的任何指示?是否有 PHP 替代 MCrypt 可以做到这一点?
  • 改iOS兼容代码会更方便,不知道能不能改。
  • 至少您可以为每个块手动增加 IV 并使用它。但是,这会很慢。正如 owlstead 建议的那样,更容易切换到 CBC 模式,或在 iOS 端更改字节序。
  • 不幸的是,在 iOS 端更改加密不是一个选项。但速度并不是什么大问题,因为我的 PHP 代码将作为后台进程执行此操作。 @NickolayOlshevsky - 有关如何以所需方式实现 IV 递增的任何指针?我不太了解字节顺序和 CTR 块递增。
  • 查看更新后的答案。我不知道我为什么这样做(在 PHP 中编码字节操作是一件非常痛苦的事情),但它确实有效。
【解决方案2】:

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

这是针对您的特定案例的 CTR 实现:

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 = 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 既可用作加密函数,也可用作解密函数。

【讨论】:

  • + 很好......但你不认为你应该反对ECB吗?
  • @Baba 这实现了一个CTR加密模式。 ECB 仅用于实现它;)
  • 我指的不是CTR实现,而是ECB一般......如果不重用密钥和IV,他可能会很安全,但iOS 5.x的CommonCrypto仍然有更好的选择......但我想问题是关于Decrypting AES CTR Little Endian ......无论如何都很好:)
  • 好吧,看看那个。 NikiC,您的计数器递增是“正确的”,并且您的函数适用于不会触发字节溢出的较短字符串+IV 组合。但是对于那些这样做的人,结果最终与 CommonCrypto 不兼容。看起来 CommonCrypto 不能正确处理溢出,并且只会增加 $iv[0],因为当我修改你的函数以这样做时它起作用了。诡异的。无论如何,你仍然应该得到赏金 NikiC。再次感谢!
  • @curtisdf 这听起来很奇怪。如果他们真的只增加第一个字符,那么使用的 xor 密钥将在 16*256 个字符后重复(因此使用频率分析可以轻松破解任何更长的密码)。你能尝试加密一个只有 As 的长字符串(比如 100000 个字符)并将生成的密码(以 hex/base64 格式)转储到某个地方吗?
猜你喜欢
  • 2015-06-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-26
  • 1970-01-01
  • 1970-01-01
  • 2020-01-20
相关资源
最近更新 更多