深入理解Python到PHP的AES文件解密移植

深入理解Python到PHP的AES文件解密移植

本文详细探讨了如何将python中的AES-ECB文件解密逻辑成功移植到php环境。重点解决了密钥生成、分块读取以及在处理流式数据时,尤其是PKCS#7填充机制在中间块和最终块上的差异处理。通过正确使用PHP的`openssl_decrypt`函数及其`OPENssl_ZERO_PADDING`标志,确保了加密文件的完整解密。

Python AES文件解密机制概述

在进行文件解密时,Python示例代码(如samloader项目中的crypt.py)展示了一种常见的AES-ECB模式解密流程。其核心逻辑包括:

  1. 密钥获取: 通过特定算法(如getv4key)生成AES密钥。
  2. 分块解密: 使用AES.new(key, AES.MODE_ECB)初始化密码器,然后以固定大小(例如4096字节)分块读取加密数据。
  3. 填充处理: 最关键的一点是,Python代码在解密时,只对最后一个数据块调用了unpad函数。这意味着除了最后一个块外,所有中间块在加密时都没有应用PKCS#7等标准填充,或者说,它们是“零填充”或无填充的。而最后一个块则可能使用了标准的PKCS#7填充。
def decrypt_progress(inf, outf, key, Length):     cipher = AES.new(key, AES.MODE_ECB)     # ...     for i in range(chunks):         block = inf.read(4096)         # ...         decblock = cipher.decrypt(block)         if i == chunks - 1: # 仅对最后一个块进行unpad             outf.write(unpad(decblock))         else:             outf.write(decblock)         # ...

这种分块和填充策略是移植到PHP时需要特别注意的核心。

密钥派生与PHP实现

Python代码中密钥的派生使用了MD5哈希,具体为hashlib.md5(deckey.encode()).digest()。这表示将一个字符串deckey编码字节串后进行MD5哈希,并获取其原始二进制摘要。

立即学习PHP免费学习笔记(深入)”;

在PHP中,对应的实现方式是使用hash()函数,并将其第三个参数设置为true以获取原始二进制输出。

$deckey = "AU77D7K3SAU/D3UU"; // 示例deckey $key = hash('md5', $deckey, true);

这段PHP代码能够正确生成与Python中hashlib.md5().digest()等效的二进制密钥,这是解密成功的首要条件。

深入理解Python到PHP的AES文件解密移植

文心大模型

百度飞桨-文心大模型 ERNIE 3.0 文本理解与创作

深入理解Python到PHP的AES文件解密移植 56

查看详情 深入理解Python到PHP的AES文件解密移植

PHP文件解密的核心挑战:填充处理

将Python的解密逻辑移植到PHP时,常见的错误是直接使用openssl_decrypt而没有正确处理填充。PHP的openssl_decrypt函数默认会尝试使用PKCS#7填充。如果输入数据(尤其是中间块)没有按照这种填充方式加密,就会导致“wrong final block length”等错误。

根据Python的实现,我们知道:

  • 中间块: 在加密时未进行PKCS#7填充(或者说,是零填充/无填充)。
  • 最后一个块: 在加密时可能使用了PKCS#7填充。

为了在PHP中模拟这种行为,我们需要利用openssl_decrypt函数的OPENSSL_ZERO_PADDING标志。值得注意的是,OPENSSL_ZERO_PADDING这个标志的名称具有误导性,它实际上是禁用了PHP/OpenSSL的默认PKCS#7填充机制。

因此,正确的策略是:

  • 对于非最后一个数据块,调用openssl_decrypt时应包含OPENSSL_ZERO_PADDING标志,以禁用默认填充。
  • 对于最后一个数据块,不应包含OPENSSL_ZERO_PADDING标志,允许openssl_decrypt使用默认的PKCS#7填充(如果存在)。

完整的PHP解密实现

以下是基于上述分析,将Python解密逻辑移植到PHP的完整示例代码:

<?php  $sourceFile = 'file.zip.enc4'; // 加密文件路径 $destFile = 'file.zip';       // 解密后文件路径  // 1. 密钥派生 $deckey = "AU77D7K3SAU/D3UU"; // 根据实际情况替换为正确的deckey $key = hash('md5', $deckey, true);  // 2. 文件信息获取与打开 $chunkSize = 4096; // 与Python中读取的块大小一致 $fileSize = filesize($sourceFile); // 获取加密文件总大小  // 以二进制模式打开文件 $sourceHandle = fopen($sourceFile, 'rb'); if (!$sourceHandle) {     die("无法打开源文件: $sourceFile"); } $destHandle = fopen($destFile, 'wb'); if (!$destHandle) {     fclose($sourceHandle);     die("无法创建目标文件: $destFile"); }  $totalBytesRead = 0; // 记录已读取的字节数  // 3. 分块读取、解密和写入 while (!feof($sourceHandle)) {     $chunk = fread($sourceHandle, $chunkSize);     if ($chunk === false || $chunk === '') { // 检查是否读取到数据或文件结束         break;     }      $currentChunkLength = strlen($chunk);     $totalBytesRead += $currentChunkLength;      // 根据当前块是否为最后一个块来决定填充模式     // 如果当前已读取字节数小于文件总大小,说明这不是最后一个块,禁用默认填充     // 否则,是最后一个块,允许默认填充 (0 表示不额外设置标志,即允许默认填充)     $paddingFlags = OPENSSL_RAW_DATA; // 始终获取原始数据     if ($totalBytesRead < $fileSize) {         $paddingFlags |= OPENSSL_ZERO_PADDING; // 禁用默认填充     }      $decryptedChunk = openssl_decrypt($chunk, 'aes-128-ecb', $key, $paddingFlags);      if ($decryptedChunk === false) {         echo "解密错误: " . openssl_error_string() . "n";         // 根据实际需求处理错误,例如跳过、重试或终止         // 这里为了演示,我们仍然尝试写入(可能会写入损坏数据)     }      fwrite($destHandle, $decryptedChunk); }  // 4. 关闭文件句柄 fclose($sourceHandle); fclose($destHandle);  echo "文件解密完成: $destFilen";  ?>

代码解析与注意事项

  1. 文件句柄: 使用fopen($file, ‘rb’)和fopen($file, ‘wb’)以二进制模式打开文件,确保数据传输的完整性。
  2. filesize():循环开始前获取源文件的总大小,这是判断当前块是否为最后一个块的关键。
  3. totalBytesRead: 维护一个计数器,记录到目前为止已从源文件读取的总字节数。
  4. 条件填充:
    • $paddingFlags = OPENSSL_RAW_DATA; 确保openssl_decrypt返回原始解密数据。
    • if ($totalBytesRead zuojiankuohaophpcn $fileSize):这个条件判断至关重要。如果已读取的字节数小于文件总大小,说明当前处理的不是最后一个块,因此需要通过OPENSSL_ZERO_PADDING来禁用默认的PKCS#7填充。
    • 否则($totalBytesRead >= $fileSize),意味着当前处理的是最后一个块,此时不添加OPENSSL_ZERO_PADDING,openssl_decrypt会尝试使用默认的PKCS#7填充进行解密。
  5. 错误处理: 尽管示例代码中加入了openssl_error_string()的输出,但在实际生产环境中,应根据解密失败的具体情况采取更健壮的错误处理机制。
  6. 文件大小差异: 解密后的文件大小通常会比加密文件小1字节(对于PKCS#7填充),这是因为最后一个块的填充字节在解密后被移除。

总结

将Python的AES-ECB文件解密逻辑移植到PHP,核心在于精确模拟Python的密钥派生和填充处理策略。通过使用hash(‘md5’, $data, true)获取二进制密钥,并结合openssl_decrypt的OPENSSL_RAW_DATA和条件性OPENSSL_ZERO_PADDING标志,可以确保PHP解密过程与Python行为一致。理解OPENSSL_ZERO_PADDING的真实作用(禁用默认填充)是解决“wrong final block length”错误的关键。遵循这些原则,即可成功在PHP中实现与Python兼容的文件流解密。

以上就是深入理解Python到PHP的AES文件解密移植的详细内容,更多请关注

上一篇
下一篇
text=ZqhQzanResources