在 PHP 的 mcrypt 中解密加密 AES-CTR 文件的随机块

Decrypt random chunk of an encrypted AES-CTR file in PHP's mcrypt

我有一个 1MB 的测试文件,我想从 500KB 开始解密它,而不是从头开始。它不需要正好从文件的 500KB 开始,它可以从任何块的开头开始,只要它不是第一个,我只是想学习如何做。

用这个脚本我可以解密文件,只要它从 0KB 开始。

$file = file_get_contents("file.dat");
$aeskey = base64_decode("sVv2g7boc/pzCDepDfV1VA==");
$iv = base64_decode("A5chWWE3D4cAAAAAAAAAAA==");

$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', 'ctr', '');
mcrypt_generic_init($td, $aeskey, $iv);

echo mdecrypt_generic($td, $file);

至少有人可以解释一下吗?

CTR mode中,一个计数器(AES 为 128 位)被加密以产生一个密钥流,然后与明文或密文进行异或运算。通常,假设 IV 为 64 位或 96 位,其余位实际上设置为 0。最初的 64 位或 96 位称为 nonce

nonce 的大小决定了在不创建多次填充的情况下一次可以加密多少数据:nonce 越大,安全消息长度越小,但两个 nonce 发生冲突的概率也越低它们是随机生成的。由于没有指定 nonce 的大小,因此许多框架不会将 nonce 的大小限制为特定大小。

您可以在 mcrypt 中为随机数使用完整的块大小。

你可以

  1. 取一开始使用的IV,
  2. 将 IV 解析为一个大整数(它不适合 PHP 整数类型),
  3. 向其添加一个数字,它代表您想要的块数(AES 为 16 字节块)跳过
  4. 将数字转换回二进制表示并
  5. 从后面的字节开始解密。

步骤 2-4 由以下代码中的 add 函数完成。

假设您有一个大文件,但想从字节 512(为简单起见,块大小的倍数)开始解密。您可以将 512/16=32 添加到 IV.

下面是一些示例代码:

<?php
$d = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
$k = "k0k1k2k3k4k5k6k7"; // 16 byte AES key
$bs = 16; // 16 byte block size

$iv = mcrypt_create_iv($bs);
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', 'ctr', '');
mcrypt_generic_init($td, $k, $iv);
$ct = mcrypt_generic($td, $d);

$dec_offset = 32;
$ct_slice = substr($ct, $dec_offset);

$iv_slice = add($iv, $dec_offset / $bs);

$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', 'ctr', '');
mcrypt_generic_init($td, $k, $iv_slice);
$d_slice = mdecrypt_generic($td, $ct_slice);

var_dump($d_slice);

function add($big_num_str, $to_add){
    $big_num = str_split(strrev($big_num_str));
    for($i = 0; $i < count($big_num) ; $i++){
        $tmp = ord($big_num[$i]) + $to_add;
        $big_num[$i] = $tmp % 256;
        $to_add = floor( $tmp / 256 );
    }
    while($to_add){
        $big_num[$i++] = $to_add % 256;
        $to_add = floor( $to_add / 256 );
    }
    for($i = 0; $i < count($big_num) ; $i++){
        $big_num[$i] = chr($big_num[$i]);
    }
    return strrev(implode('', $big_num) );
}

输出:

string(32) "101112131415161718191a1b1c1d1e1f"

对于 PHP 中的 OpenSSL 扩展,这也以完全相同的方式工作。 Here是代码。


当然,如果你想获得一个不在块边界上开始的块,这会稍微复杂一些。您必须更早开始一个块并删除多余的字节。