php openssl_encrypt & decrypt降低webm视频质量

pxq42qpu  于 2023-05-16  发布在  PHP
关注(0)|答案(2)|浏览(118)

我有一个简单的微型网站,用户使用内置的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');

int count();

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

return();

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

8yparm6h1#

解密与加密不一致,并确定了错误的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处理不同)。

wnavrhmk

wnavrhmk2#

好吧,我被这个问题搞得晕头转向,一头扎了进去。
总结如下:
1.如果你想让你的代码完全在你自己的代码范围内加密/解密 *,并且使用固定的FILE_ENCRYPTION_BLOCKS值 *,那么@Topaco的答案中的IV修复可能就足够了。否则,您需要应用建议的其余修复程序。
1.在现有代码中更改FILE_ENCRYPTION_BLOCKS可能只是将兼容性破坏进一步向下移动,可能超过文件的结尾,此时无论如何都没有任何流式加密代码。
1.如果你想让你的代码产生与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。

相关问题