You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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模式的工作原理存在误解,导致视频二进制数据被破坏:

  1. 加密时错误截取密文片段作为下一个IV
    CBC模式下,下一个块的IV应该是当前完整的密文块,但你只取了密文的前IV长度字节($iv = substr($ciphertext, 0, $ivLenght);),这会导致后续所有块的加密IV完全错误,数据乱序。

  2. 解密时错误从明文提取IV
    解密循环中,你试图从解密后的明文里截取IV($iv = substr($plaintext, 0, $ivLenght);),这完全不符合逻辑。正确的做法是用当前块的完整密文作为下一个块的IV,而非明文片段。

  3. 文件打开模式隐患:加密时使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 00:00:08