【问题标题】:AES 128 bit CTR partial file decryption with PHP使用 PHP 的 AES 128 位 CTR 部分文件解密
【发布时间】:2014-07-18 00:05:31
【问题描述】:

这不是重复的帖子,因为我到处寻找,但找不到答案。它关于部分解密。未满。

我对 PHP 有很好的了解,但对密码学知之甚少。 我知道加密文件的密钥和 iv。该文件整体解密良好,但当我尝试从中间解密部分文件时出现真正的问题。

当我尝试解密文件的前 128kb 或 256kb 或文件开头的任何长度时,它解密得很好。 但是当我从中间开始时,它不会解密而是给出乱码输出。

我将在此处发布文件前 100 个字节的示例。

加密为 AES 128 位 CTR 模式。

我已经使用了 PHP 的 mdecrypt_generic 和 mcrypt_decrypt 函数,但没有成功。

使用的代码:

$chunk2 = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, base64_decode($mega_key), $data, 'ctr', base64_decode($mega_iv));
echo base64_encode($data) .'<br />'. base64_encode($chunk2);

结果:

9PX2fU83NF3hLc+HFdyHkqfxC4bHWKUQwQHJkNVnYbKCIQrhlHvTKtz8T3Bb0TgBkyBoGHnDCzZs3bu54KLQ8Bv0lzrTVJbzJY5msBfcy7Zi2Z/fLoMm+nvqdGPTNR0uwv45xJ8=
MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTE=

如您所见。解密后,结果是包含 digit 1 的文件的前 100 个字节。 该文件由 Mega.co.nz 使用 JavaScript 加密。根据 Mega 的文档,该文件已被分块加密以进行部分加密/解密。

文件加密

MEGA 使用客户端加密/解密来端到端保护文件传输和存储。从客户端接收的数据被逐字存储和传输; 服务器既不解密也不重新加密,也不验证传入用户文件的加密。所有加密处理都在最终用户的控制之下。 为了允许进行完整性检查的部分读取,文件被视为一系列块。为了简化服务器端处理,部分上传只能开始和 在块边界上结束。此外,部分下载只有在满足相同标准时才能进行完整性检查。块边界位于 以下位置:0 / 128K / 384K / 768K / 1280K / 1920K / 2688K / 3584K / 4608K /。 (每 1024 KB)/EOF

我正在用这个函数计算文件的块边界:

public function get_chunks($size)
{
    $chunks = array();
    $p = $pp = 0;

    for ($i = 1; $i <= 8 && $p < $size - $i * 0x20000; $i++) {
        $chunks[$p] = $i * 0x20000;
        $pp = $p;
        $p += $chunks[$p];
    }

    while ($p < $size) {
        $chunks[$p] = 0x100000;
        $pp = $p;
        $p += $chunks[$p];
    }

    $chunks[$pp] = ($size - $pp);
    if (!$chunks[$pp])
    {
        unset($chunks[$pp]);
    }

    return $chunks;
}

我曾尝试单独解密第二个块,但失败了。 由于显而易见的原因,我无法在此处发布 128kb 的块,但我将向您展示从第 2 个字节到第 100 个字节的块。 这是相同代码的结果:

使用的代码:

$chunk2 = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, base64_decode($mega_key), $data, 'ctr', base64_decode($mega_iv));
echo base64_encode($data) .'<br />'. base64_encode($chunk2);

结果:

9fZ9Tzc0XeEtz4cV3IeSp/ELhsdYpRDBAcmQ1WdhsoIhCuGUe9Mq3PxPcFvROAGTIGgYecMLNmzdu7ngotDwG/SXOtNUlvMljmawF9zLtmLZn98ugyb6e+p0Y9M1HS7C/jnEnw==
MDK6A0kyWI3903mj+GokBGfLvHCuzITg8flodIM34gGSGtpE3pnIxxGCDhq72AijgnlBUIv5DGuAVzNoc0MR2t5SnNi281TnmtnnlvomTOWKd3HAnJTtsKCvJoHXGQLdDfbMag==

结果与mcrypt_module_open('rijndael-128', '', 'ctr', '');相同

我需要对文件进行部分解密,因为我正在尝试为支持并行连接/恢复支持的 Mega 编写开源下载管理器。

我需要即时解密文件以允许文件流式传输,因此下载后解密是不可能的。

在 mega 的网站上,他们自己的下载界面使用多个连接并分块下载文件,还有另一个网络服务允许从 Mega 下载多个连接并支持恢复。

我需要解密部分文件以支持来自浏览器/下载管理器的 HTTP Range 标头请求。如果范围请求落在第二个或第三个块中,我需要能够解密该块并将其发送给客户端,而无需从头开始解密文件。

有可能吗?应该是有些网站已经做了。

【问题讨论】:

  • 提供的 MEGA 文档并不清楚是每个块单独加密还是所有块都被视为单个数据流。您能否提供此类详细信息的链接?
  • 我同意文档非常模糊。这是链接:mega.co.nz/#doc
  • 您以大量字符的形式向我们展示您的数据。不要那样做。以 Base-64 或 Hex (=Base-16) 显示它们。尝试将字节数据表示为原始字符串时,有很多事情可能会出错。可能出错的事情之一是字符“丢失”,例如:\r\n -> \n。非常清楚您处理的是字节,而不是字符,并在字节和 Base-64 之间小心转换。
  • @rossum 感谢您指出这一点,您是对的。但是问题还是一样,文件可以部分解密吗?根据我从 MEGA 的文档中了解到的情况,该文件已被逐块加密,并且还给出了块边界,但解密未按预期进行。有没有其他方法可以实现这一目标?我也尝试在 python 的库中解密它,以及在 PHP 中使用其他一些定制的 AES 解密算法。
  • 使用 CTR 加密的文件可以逐块或逐块解密。块中的块应在计数器上按顺序运行。您需要联系加密端以确定如何在每个块的开头设置加密:计数器(重新)从哪里开始?使用什么随机数?是否使用了新密钥?如果解密要成功,则需要逐字节精确地再现所有块开始条件。

标签: php encryption cryptography aes http-range


【解决方案1】:

好的,我找到了动态解密部分内容的解决方案。 当我尝试从 Range 解密部分数据时(为了创建 Web 代理),我遇到了这个问题。 我发现如果我下载整个文件(从位置 0 开始),没有问题。 示例:

while($_total_dled != $content_length) {
        $raw = fgets($socket, 1024);
        $data = mdecrypt_generic($this->cipher, $raw);
        echo $data;
}

但是,如果我开始从 pos x 读取文件(就像我收到 : Range x-) 一样,mdecrypt_generic 将失败,因为它之前没有被执行 (x-1) 次. 所以我试试这个:

for($i=0;$i<$range["start"];$i++) {
     mdecrypt_generic($this->cipher, "0");
}

只需要解密一个字符(x-1)次。 然后是带有while循环的部分。它有效。 也许我们可以将此迭代与相关计数器联系起来。

希望这对人们有所帮助。

【讨论】:

    【解决方案2】:

    在我看来,您正在对块边界进行大量计算(以表格形式给出 + 对 4608Ki 的简单计算)并且没有计算得到新的 IV。

    IV 只是随机数加上一个块计数器。因此,要为特定块计算新的“IV”,您必须这样做:

    1. 通过移动高(左)字节中的随机数来创建初始计数器
    2. 获取块下界
    3. 将下边界除以块大小
    4. 将结果数添加或异或(前 2^64 块相同)到初始计数器
    5. 用它初始化你的密码

    那么你应该可以解密了。

    【讨论】:

    • 好的,这个答案很有帮助,但不幸的是并没有解决问题。就像我说的那样,我对密码学知之甚少。但我不是自己创建 IV,它已经由文件密钥中编码的 MEGA 提供给我。我这样做是为了获得 IV。我发现有趣的是,IV 值始终是一个由 0 组成的数组。数组( [0] => 0 [1] => 0 )打包成大端 32 位长字符串。该数组是通过对文件密钥数组进行切片得到的(解包后使用相同的 32 位长大端格式。
    • 在 php 中它是通过使用 unpack() 函数通过传递 N* 作为格式来完成的。文件键从第 4 个偏移量开始切片,切片的长度为 2。但它产生空数组,因为该数组只有 4 个元素。因此我们得到一个长度为 2 且没有元素的数组。然后将该 IV 数组转换为相同的 32 位字符串并用于解密。我了解到您希望我为中间块创建一个新的 IV。但我是空白的。我不明白你的意思:create the initial counter by shifting the nonce in the higher (left) bytes
    • 你能用外行的话解释一下我必须做什么吗?你真是太慷慨了。
    猜你喜欢
    • 1970-01-01
    • 2013-05-05
    • 1970-01-01
    • 1970-01-01
    • 2015-06-09
    • 2020-01-20
    • 2017-03-17
    • 2013-10-21
    • 1970-01-01
    相关资源
    最近更新 更多