openssl_encrypt 和 decrypt 降低 webm 视频的质量

问题描述 投票:0回答:2

我有一个简单的微型网站,用户可以在其中使用内置的 JS MediaRecorder (https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder) 录制自己的视频并进行加密使用 openssl_encrypt.

当他们想要分享/观看该视频时,它会使用 openssl_decrypt 进行解密。

问题是,一旦出现解密功能,质量就会降低到您看到伪影的程度。 WebM 视频仍然可以观看,但 MP4 视频(从 iPhone 捕获)无法观看 - 视频有 1FPS ......可能导致质量急剧下降的原因是什么?

在我们实施此加密之前,该解决方案一直运行良好。

我们直接把录制的文件传进去

encryptFile($_FILES["file"]['tmp_name'], $dir . $filename, 'secret-key');
decryptFile("videos/" . $id . "." . $mime, $decrypted_video, 'secret-key');

加密文件();

/**
 * @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);
}

解密文件();

/**
 * @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);
}
php encryption webm
2个回答
2
投票

解密与加密不一致,判断IV错误

错误的 IV 导致每个解密的明文块中的第一个块被损坏。块大小越大,即块越少,错误越少,这解释了增加

FILE_ENCRYPTION_BLOCKS
.

时的改进

要加密解密一致,必须在

decryptFile()
:

$iv = substr($ciphertext, 0, $ivLenght);

通过此更改,解密有效。


如果逻辑模仿 CBC 模式,那么代码会更健壮,这样在不知道块大小的情况下就可以解密(即加密和解密可以使用不同的块大小)。要实现这一点:

  • 只有最后一个明文块必须被填充(当前:所有块都被填充)。修复(在加密示例中,类似于解密示例):

    if (!feof($fpSource)) {
        $ciphertext = openssl_encrypt($plaintext, $cipher, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv); // don't pad 
    } else {
        $ciphertext = openssl_encrypt($plaintext, $cipher, $key, OPENSSL_RAW_DATA, $iv); // pad
    }
    
  • 前一个密文块的最后一个块必须用作IV(当前:使用第一个块)。修正:

    $iv = substr($ciphertext, -$ivLenght);
    

完全避免填充的另一种方法是使用像 CTR 这样的流密码模式(但是,这需要不同的 IV 确定,因为两种模式的 IV 处理不同)。


1
投票

好吧,我被这个问题搞得一头雾水,陷入了很深的问题。

总结是:

  1. 如果您希望您的代码完全在您自己的代码范围内加密/解密,并且具有固定的
    FILE_ENCRYPTION_BLOCKS
    ,那么@Topaco 的答案中的 IV 修复可能就足够了。否则,您将需要应用建议的其余修复程序。
  2. 更改现有代码中的
    FILE_ENCRYPTION_BLOCKS
    可能只是将兼容性破坏进一步向下移动,可能超过文件末尾,此时流加密代码实际上没有任何意义。
  3. 如果您希望您的代码生成与 OpenSSL 和其他库兼容的输出,那么...这里有龙...

最简单的答案是使用一个现有的库,它已经做到了,而且做得很好。即:jeskew/php-encrypted-streams,在我遇到问题后,我在使自己的代码工作时大量引用了它。

更复杂的答案是:

  • 块大小不一定与 IV 大小相同,检测起来有点麻烦。
  • 下一个块的IV是前一个块的last
    $iv_size
    字节。
  • 除了最后一个块之外的所有块都需要`OPENSSL_ZERO_PADDING.
//define('DEBUG', true);
function debug(...$args) {
    if( defined('DEBUG') && DEBUG === true ) {
        printf(...$args);
    }
}

if( ! function_exists('openssl_cipher_block_length') ) {
    function openssl_cipher_block_length($cipher) {
        return strlen(openssl_encrypt(
            "\x00", $cipher, "", OPENSSL_RAW_DATA, str_repeat("\x00", openssl_cipher_iv_length($cipher))
        ));
    }
}

function openssl_encrypt_stream($stream_in, $stream_out, $key, $iv, $cipher, $block_multiplier=256) {
    $cipher_block_length = openssl_cipher_block_length($cipher);
    $buffer_size = $cipher_block_length * $block_multiplier;
    $iv_size = openssl_cipher_iv_length($cipher);

    debug("Cipher: %s, Block Size: %d, IV Size: %d, Block Multiplier: %d\n", $cipher, $cipher_block_length, $iv_size, $block_multiplier);

    while( ! feof($stream_in) ) {
        $buffer = fread($stream_in, $buffer_size);
        debug("Plaintext Buffer Length: %d, Content: %s\n", strlen($buffer), bin2hex($buffer));

        $options = OPENSSL_RAW_DATA;
        if( feof($stream_in) ) {
            debug("Final block.\n");
        } else {
            $options |= OPENSSL_ZERO_PADDING;
        }
        $e_buffer = openssl_encrypt($buffer, $cipher, $key, $options, $iv);

        debug("IV: %s, Ciphertext: %s\n", bin2hex($iv), bin2hex($e_buffer));

        if( $e_buffer === false ) {
            throw new \Exception(openssl_error_string());
        }

        $iv = substr($e_buffer, -1 * $cipher_block_length);
        fwrite($stream_out, $e_buffer);
        debug(PHP_EOL);
    }
}

function openssl_decrypt_stream($stream_in, $stream_out, $key, $iv, $cipher, $block_multiplier=256) {
    $cipher_block_length = openssl_cipher_block_length($cipher);
    $buffer_size = $cipher_block_length * $block_multiplier;
    $iv_size = openssl_cipher_iv_length($cipher);

    debug("Cipher: %s, Block Size: %d, IV Size: %d, Block Multiplier: %d\n", $cipher, $cipher_block_length, $iv_size, $block_multiplier);

    $next_buffer = fread($stream_in, $buffer_size);
    do {
        $buffer = $next_buffer;
        $next_buffer = fread($stream_in, $buffer_size);
        debug("Ciphertext Buffer Length: %d, Content: %s\n", strlen($buffer), bin2hex($buffer));

        $options = OPENSSL_RAW_DATA;
        if( feof($stream_in) && $next_buffer === '' ) {
            debug("Final block.\n");
        } else {
            $options |= OPENSSL_ZERO_PADDING;
        }
        $p_buffer = openssl_decrypt($buffer, $cipher, $key, $options, $iv);

        debug("IV: %s, Plaintext: %s\n", bin2hex($iv), bin2hex($p_buffer));

        if( $p_buffer === false ) {
            throw new \Exception(openssl_error_string());
        }

        $iv = substr($buffer, -1 * $cipher_block_length);
        fwrite($stream_out, $p_buffer);
        debug(PHP_EOL);
    } while( !( feof($stream_in) && $next_buffer === '') );
}

测试代码:

// encrypt/decrypt normally as a control
function e_control($file, $key, $iv, $cipher) {
    $begin = hrtime(true);
    $enc = openssl_encrypt(file_get_contents($file), $cipher, $key, OPENSSL_RAW_DATA, $iv);
    $dur = hrtime(true) - $begin;
    return [ $enc, $dur ];
}

function d_control($enc, $key, $iv, $cipher) {
    $begin = hrtime(true);
    $plain = openssl_decrypt($enc, $cipher, $key, OPENSSL_RAW_DATA, $iv);
    $dur = hrtime(true) - $begin;
    return [ $plain, $dur ];
}

// encrypt test
function e_test($file, $key, $iv, $cipher, $block_multiplier=256) {
    $stream_out = fopen('php://memory', 'rwb');

    $begin = hrtime(true);
    $stream_in  = fopen($file, 'rb'); // fairness with control
    openssl_encrypt_stream($stream_in, $stream_out, $key, $iv, $cipher, $block_multiplier);
    $dur = hrtime(true) - $begin;

    fclose($stream_in);
    rewind($stream_out);
    return [ stream_get_contents($stream_out), $dur ];
}

// decrypt test
function d_test($enc, $key, $iv, $cipher, $block_multiplier=256) {
    $stream_in  = fopen('php://memory', 'rwb');
    $stream_out = fopen('php://memory', 'rwb');
    fwrite($stream_in, $enc);
    rewind($stream_in);

    $begin = hrtime(true);
    openssl_decrypt_stream($stream_in, $stream_out, $key, $iv, $cipher, $block_multiplier);
    $dur = hrtime(true) - $begin;

    fclose($stream_in);
    rewind($stream_out);
    return [ stream_get_contents($stream_out), $dur ];
}

// dd if=/dev/random of=./test.bin bs=1024 count=10240
$file   = 'test.bin';
$cipher = 'aes-256-cbc';
$key    = str_repeat("\x00", 32);
$iv     = str_repeat("\x00", openssl_cipher_iv_length($cipher));
$mult   = 256;

// warm the FS cache for fairness
file_get_contents($file);

list($c, $c_dur) = e_control( $file, $key, $iv, $cipher);
list($e, $e_dur) = e_test(    $file, $key, $iv, $cipher, $mult);
list($x, $x_dur) = d_control( $c,    $key, $iv, $cipher);
list($d, $d_dur) = d_test(    $c,    $key, $iv, $cipher, $mult);

printf("Control - Hash: %s, Duration: %0.3f ms\n", md5($c), $c_dur / 1000000);
printf("Encrypt - Hash: %s, Duration: %0.3f ms\n", md5($e), $e_dur / 1000000);
printf("Control - Hash: %s, Duration: %0.3f ms\n", md5($x), $x_dur / 1000000);
printf("Decrypt - Hash: %s, Duration: %0.3f ms\n", md5($d), $d_dur / 1000000);

示例输出:

Control - Hash: 5f5db49554de8fa6a7195c4d0cbc0ad8, Duration: 30.788 ms
Encrypt - Hash: 5f5db49554de8fa6a7195c4d0cbc0ad8, Duration: 75.244 ms
Control - Hash: 8eee2cf0ee0444cafa9280a89821f1ff, Duration: 9.054 ms
Decrypt - Hash: 8eee2cf0ee0444cafa9280a89821f1ff, Duration: 61.600 ms

值得注意的是,虽然此代码中的

$cipher
似乎可配置,但此方法仅适用于 CBC 模式的密码,并且可能仅适用于 AES。上面链接的 jeskew/php-encrypted-streams 库实现了更广泛的密码模式,但仍然只有 AES。

© www.soinside.com 2019 - 2024. All rights reserved.