openssl_encrypt/decrypt导致WebM/MP4视频画质骤降问题排查
问题描述
我开发了一个小型站点,用户通过JS MediaRecorder录制视频后,使用openssl_encrypt进行加密;当用户需要查看或分享视频时,通过openssl_decrypt解密。但解密后的视频画质严重下降:WebM视频出现伪影仍可观看,而iPhone录制的MP4视频帧率仅1FPS,完全无法观看。该站点在未实施加密前功能正常,我们直接将录制的文件传入加密解密函数:
encryptFile($_FILES["file"]['tmp_name'], $dir . $filename, 'secret-key'); decryptFile("videos/" . $id . "." . $mime, $decrypted_video, 'secret-key');
encryptFile()函数实现:
/** * @param $source Path of the unencrypted file * @param $dest Path of the encrypted file to created * @param $key Encryption key */ function encryptFile($source, $dest, $key) { $cipher = 'aes-256-cbc'; $ivLenght = openssl_cipher_iv_length($cipher); $iv = openssl_random_pseudo_bytes($ivLenght); $fpSource = fopen($source, 'rb'); $fpDest = fopen($dest, 'w'); fwrite($fpDest, $iv); while (!feof($fpSource)) { $plaintext = fread($fpSource, $ivLenght * FILE_ENCRYPTION_BLOCKS); $ciphertext = openssl_encrypt($plaintext, $cipher, $key, OPENSSL_RAW_DATA, $iv); $iv = substr($ciphertext, 0, $ivLenght); fwrite($fpDest, $ciphertext); } fclose($fpSource); fclose($fpDest); }
decryptFile()函数实现:
/** * @param $source Path of the encrypted file * @param $dest Path of the decrypted file * @param $key Encryption key */ function decryptFile($source, $dest, $key) { $cipher = 'aes-256-cbc'; $ivLenght = openssl_cipher_iv_length($cipher); $fpSource = fopen($source, 'rb'); $fpDest = fopen($dest, 'w'); $iv = fread($fpSource, $ivLenght); while (!feof($fpSource)) { $ciphertext = fread($fpSource, $ivLenght * (FILE_ENCRYPTION_BLOCKS + 1)); $plaintext = openssl_decrypt($ciphertext, $cipher, $key, OPENSSL_RAW_DATA, $iv); $iv = substr($plaintext, 0, $ivLenght); fwrite($fpDest, $plaintext); } fclose($fpSource); fclose($fpDest); }
请问是什么原因导致了视频画质的急剧下降?
问题原因与解决方案
核心问题:加密解密的CBC模式块处理逻辑完全错误
你的代码对AES-CBC模式的工作原理存在误解,导致视频二进制数据被破坏:
加密时错误截取密文片段作为下一个IV
CBC模式下,下一个块的IV应该是当前完整的密文块,但你只取了密文的前IV长度字节($iv = substr($ciphertext, 0, $ivLenght);),这会导致后续所有块的加密IV完全错误,数据乱序。解密时错误从明文提取IV
解密循环中,你试图从解密后的明文里截取IV($iv = substr($plaintext, 0, $ivLenght);),这完全不符合逻辑。正确的做法是用当前块的完整密文作为下一个块的IV,而非明文片段。文件打开模式隐患:加密时使用
w而非wb,在Windows系统下可能触发换行符转换,破坏二进制视频数据。
修正后的代码
正确的加密函数
define('FILE_ENCRYPTION_BLOCKS', 1000); /** * @param $source Path of the unencrypted file * @param $dest Path of the encrypted file to created * @param $key Encryption key */ function encryptFile($source, $dest, $key) { $cipher = 'aes-256-cbc'; $ivLength = openssl_cipher_iv_length($cipher); $iv = openssl_random_pseudo_bytes($ivLength); $fpSource = fopen($source, 'rb'); $fpDest = fopen($dest, 'wb'); // 写入初始IV fwrite($fpDest, $iv); while (!feof($fpSource)) { // 读取明文块,长度为块大小 * 块数量 $plaintext = fread($fpSource, $ivLength * FILE_ENCRYPTION_BLOCKS); // 加密,OPENSSL_RAW_DATA返回原始二进制数据 $ciphertext = openssl_encrypt($plaintext, $cipher, $key, OPENSSL_RAW_DATA, $iv); // CBC模式下,下一个块的IV是当前完整的密文 $iv = $ciphertext; // 写入密文 fwrite($fpDest, $ciphertext); } fclose($fpSource); fclose($fpDest); }
正确的解密函数
/** * @param $source Path of the encrypted file * @param $dest Path of the decrypted file * @param $key Encryption key */ function decryptFile($source, $dest, $key) { $cipher = 'aes-256-cbc'; $ivLength = openssl_cipher_iv_length($cipher); $fpSource = fopen($source, 'rb'); $fpDest = fopen($dest, 'wb'); // 读取初始IV $iv = fread($fpSource, $ivLength); while (!feof($fpSource)) { // 读取密文块,密文长度等于明文长度 + 块大小(CBC模式加密后长度是块大小的整数倍) $ciphertext = fread($fpSource, $ivLength * (FILE_ENCRYPTION_BLOCKS + 1)); // 解密 $plaintext = openssl_decrypt($ciphertext, $cipher, $key, OPENSSL_RAW_DATA, $iv); // CBC模式下,下一个块的IV是当前完整的密文 $iv = $ciphertext; // 写入明文(openssl_decrypt会自动处理填充) if ($plaintext !== false) { fwrite($fpDest, $plaintext); } } fclose($fpSource); fclose($fpDest); }
额外注意事项
- 确保
FILE_ENCRYPTION_BLOCKS常量已定义,建议设置为1000左右,平衡内存占用和处理效率。 - 始终用
rb/wb模式处理二进制文件,避免系统自动转换换行符破坏数据。 - AES-CBC模式会自动添加填充,解密时
openssl_decrypt会自动去除,无需手动操作。
内容的提问来源于stack exchange,提问作者Natalie
相关产品推荐
相关产品推荐

