PHP实现兼容OpenSSL命令行的AES-256-CTR大文件加密问题
适配OpenSSL命令行的AES-256-CTR分块加密PHP实现
问题背景
需要编写PHP分块加密代码,复刻OpenSSL命令行的AES-256-CTR加密逻辑,确保PHP加密后的文件可通过以下命令解密:
openssl enc -p -d -aes-256-ctr -in php-encrypted.bin -out decrypted.txt -pass file:my.key
现有分块加密代码无法兼容命令行解密,核心问题出在CTR模式的IV/计数器处理逻辑上。
核心问题分析
OpenSSL的AES-256-CTR模式遵循以下规则:
- IV总长度为16字节(128位),由8字节随机nonce + 8字节大端序计数器组成,初始计数器值为0
- 每加密一个16字节的块,计数器自动递增1
- PHP的
openssl_encrypt函数在分块调用时不会自动维护计数器状态,必须手动跟踪并更新计数器值 - CTR模式属于流加密,不需要填充,OpenSSL命令行默认不启用填充,因此PHP代码中不应添加
OPENSSL_ZERO_PADDING
修正后的代码实现
// 生成8字节随机salt $salt = random_bytes(8); $saltPrefix = "Salted__" . $salt; // 通过EVP_BytesToKey生成密钥和初始IV(与OpenSSL命令行逻辑一致) $keyIV = EVP_BytesToKey($salt, file_get_contents('my.key')); $key = substr($keyIV, 0, 32); // AES-256需要32字节密钥 $initialIV = substr($keyIV, 32, 16); // CTR模式IV长度为16字节 $FILE_ENCRYPTION_BLOCKS = 10000; $blockSize = 16; // AES块大小固定16字节 $currentCounter = unpack('J', substr($initialIV, 8, 8))[1]; // 提取初始计数器(大端64位无符号整数) if ($fpOut = fopen('php-encrypted.bin', 'wb')) { fwrite($fpOut, $saltPrefix); if ($fpIn = fopen('confidential.txt', 'rb')) { while (!feof($fpIn)) { $plaintext = fread($fpIn, $blockSize * $FILE_ENCRYPTION_BLOCKS); $plaintextLen = strlen($plaintext); if ($plaintextLen === 0) break; // 生成当前块对应的IV:nonce + 更新后的计数器 $currentIV = substr($initialIV, 0, 8) . pack('J', $currentCounter); // CTR模式不需要填充,仅用OPENSSL_RAW_DATA $encrypted = openssl_encrypt($plaintext, 'AES-256-CTR', $key, OPENSSL_RAW_DATA, $currentIV); fwrite($fpOut, $encrypted); // 计算本次加密的块数,更新计数器 $blockCount = (int)ceil($plaintextLen / $blockSize); $currentCounter += $blockCount; } fclose($fpIn); } fclose($fpOut); } // 实现OpenSSL兼容的EVP_BytesToKey算法 function EVP_BytesToKey($salt, $password) { $keyIV = ''; $data = ''; while (strlen($keyIV) < 32 + 16) { // 32字节密钥 + 16字节IV $data .= $password . $salt; $data = md5($data, true); $keyIV .= $data; } return $keyIV; }
关键细节说明
- EVP_BytesToKey实现:严格遵循OpenSSL默认的密钥派生逻辑(MD5哈希、无迭代次数),确保生成的密钥和初始IV与命令行完全一致
- 计数器维护:从初始IV中提取8字节计数器(大端64位),每次加密后根据处理的块数递增计数器,确保后续分块使用正确的IV
- 无填充处理:CTR模式为流加密,无需填充,因此去掉
OPENSSL_ZERO_PADDING,避免与OpenSSL命令行的默认行为冲突 - 分块加密性能:保持原有的分块读取逻辑,避免一次性加载大文件到内存,适合加密大体积文件
内容的提问来源于stack exchange,提问作者Bilal H
相关产品推荐
相关产品推荐

