Шифрующие фильтры

Внимание

Данная возможность была объявлена УСТАРЕВШЕЙ начиная с PHP 7.1.0. Крайне не рекомендуется полагаться на эту возможность в будущем.

Шифрующие фильтры очень хорошо подходят для шифрования файлов и потоков.

mcrypt.* и mdecrypt.* обеспечивают симметричное шифрование и дешифрование при помощи libmcrypt. Оба набора фильтров поддерживают те же алгоритмы, что и расширение mcrypt в виде mcrypt.ciphername, где ciphername - это название шифра, как если бы оно передавалось функции mcrypt_module_open(). Также доступны следующие пять параметров:

Параметры фильтра mcrypt
Параметр Обязателен? По умолчанию Пример значения
mode Нет cbc cbc, cfb, ecb, nofb, ofb, stream
algorithms_dir Нет ini_get('mcrypt.algorithms_dir') Путь к модулям алгоритмов
modes_dir Нет ini_get('mcrypt.modes_dir') Путь к модулям режимов
iv Да N/A Обычно 8, 16 или 32 байта бинарных данных. Зависит от шифра
key Да N/A Обычно 8, 16 или 32 байта бинарных данных. Зависит от шифра

Пример #1 Шифрование/расшифровка используя Blowfish

<?php
// примем, что $key  уже сформирован
$iv_size mcrypt_get_iv_size(MCRYPT_BLOWFISHMCRYPT_MODE_CBC);
$iv mcrypt_create_iv($iv_sizeMCRYPT_DEV_URANDOM);
$fp fopen('encrypted-file.enc''wb');
fwrite($fp$iv);
$opts = array('mode'=>'cbc','iv'=>$iv'key'=>$key);
stream_filter_append($fp'mcrypt.blowfish'STREAM_FILTER_WRITE$opts);
fwrite($fp'message to encrypt');
fclose($fp);

//расшифровка...
$fp fopen('encrypted-file.enc''rb');
$iv fread($fp$iv_size mcrypt_get_iv_size(MCRYPT_BLOWFISHMCRYPT_MODE_CBC));
$opts = array('mode'=>'cbc','iv'=>$iv'key'=>$key)
stream_filter_append($fp'mdecrypt.blowfish'STREAM_FILTER_READ$opts);
$data rtrim(stream_get_contents($fp));//trims off null padding
fclose($fp);
echo 
$data;
?>

Пример #2 Шифрование файла с помощью AES-128 CBC с SHA256 HMAC в PHP 5.5+

<?php
AES_CBC
::encryptFile($password"plaintext.txt""encrypted.enc");
AES_CBC::decryptFile($password"encrypted.enc""decrypted.txt");

class 
AES_CBC
{
    protected static 
$KEY_SIZES = array('AES-128'=>16,'AES-192'=>24,'AES-256'=>32);
    protected static function 
key_size() { return self::$KEY_SIZES['AES-128']; } //default AES-128
    
public static function encryptFile($password$input_stream$aes_filename){
        
$iv_size mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128MCRYPT_MODE_CBC);
        
$fin fopen($input_stream"rb");
        
$fc fopen($aes_filename"wb+");
        if (!empty(
$fin) && !empty($fc)) {
            
fwrite($fcstr_repeat("_"32) );//placeholder, SHA256 HMAC will go here later
            
fwrite($fc$hmac_salt mcrypt_create_iv($iv_sizeMCRYPT_DEV_URANDOM));
            
fwrite($fc$esalt mcrypt_create_iv($iv_sizeMCRYPT_DEV_URANDOM));
            
fwrite($fc$iv mcrypt_create_iv($iv_sizeMCRYPT_DEV_URANDOM));
            
$ekey hash_pbkdf2("sha256"$password$esalt$it=1000self::key_size(), $raw=true);
            
$opts = array('mode'=>'cbc''iv'=>$iv'key'=>$ekey);
            
stream_filter_append($fc'mcrypt.rijndael-128'STREAM_FILTER_WRITE$opts);
            
$infilesize 0;
            while (!
feof($fin)) {
                
$block fread($fin8192);
                
$infilesize+=strlen($block);
                
fwrite($fc$block);
            }
            
$block_size mcrypt_get_block_size(MCRYPT_RIJNDAEL_128MCRYPT_MODE_CBC);
            
$padding $block_size - ($infilesize $block_size);//$padding is a number from 1-16
            
fwrite($fcstr_repeat(chr($padding), $padding) );//perform PKCS7 padding
            
fclose($fin);
            
fclose($fc);
            
$hmac_raw self::calculate_hmac_after_32bytes($password$hmac_salt$aes_filename);
            
$fc fopen($aes_filename"rb+");
            
fwrite($fc$hmac_raw);//overwrite placeholder
            
fclose($fc);
        }
    }
    public static function 
decryptFile($password$aes_filename$out_stream) {
        
$iv_size mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128MCRYPT_MODE_CBC);
        
$hmac_raw file_get_contents($aes_filenamefalseNULL,  032);
        
$hmac_salt file_get_contents($aes_filenamefalseNULL32$iv_size);
        
$hmac_calc self::calculate_hmac_after_32bytes($password$hmac_salt$aes_filename);
        
$fc fopen($aes_filename"rb");
        
$fout fopen($out_stream'wb');
        if (!empty(
$fout) && !empty($fc) && self::hash_equals($hmac_raw,$hmac_calc)) {
            
fread($fc32+$iv_size);//skip sha256 hmac and salt
            
$esalt fread($fc$iv_size);
            
$iv    fread($fc$iv_size);
            
$ekey hash_pbkdf2("sha256"$password$esalt$it=1000self::key_size(), $raw=true);
            
$opts = array('mode'=>'cbc''iv'=>$iv'key'=>$ekey);
            
stream_filter_append($fc'mdecrypt.rijndael-128'STREAM_FILTER_READ$opts);
            while (!
feof($fc)) {
                
$block fread($fc8192);
                if (
feof($fc)) {
                    
$padding ord($block[strlen($block) - 1]);//assume PKCS7 padding
                    
$block substr($block00-$padding);
                }
                
fwrite($fout$block);
            }
            
fclose($fout);
            
fclose($fc);
        }
    }
    private static function 
hash_equals($str1$str2) {
        if(
strlen($str1) == strlen($str2)) {
            
$res $str1 $str2;
            for(
$ret=0,$i strlen($res) - 1$i >= 0$i--) $ret |= ord($res[$i]);
            return !
$ret;
        }
        return 
false;
    }
    private static function 
calculate_hmac_after_32bytes($password$hsalt$filename) {
        static 
$init=0;
        
$init or $init stream_filter_register("user-filter.skipfirst32bytes""FileSkip32Bytes");
        
$stream 'php://filter/read=user-filter.skipfirst32bytes/resource=' $filename;
        
$hkey hash_pbkdf2("sha256"$password$hsalt$iterations=100024$raw=true);
        return 
hash_hmac_file('sha256'$stream$hkey$raw=true);
    }
}
class 
FileSkip32Bytes extends php_user_filter
{
    private 
$skipped=0;
    function 
filter($in$out, &$consumed$closing)  {
        while (
$bucket stream_bucket_make_writeable($in)) {
            
$outlen $bucket->datalen;
            if (
$this->skipped<32){
                
$outlen min($bucket->datalen,32-$this->skipped);
                
$bucket->data substr($bucket->data$outlen);
                
$bucket->datalen $bucket->datalen-$outlen;
                
$this->skipped+=$outlen;
            }
            
$consumed += $outlen;
            
stream_bucket_append($out$bucket);
        }
        return 
PSFS_PASS_ON;
    }
}
class 
AES_128_CBC extends AES_CBC {
    protected static function 
key_size() { return self::$KEY_SIZES['AES-128']; }
}
class 
AES_192_CBC extends AES_CBC {
    protected static function 
key_size() { return self::$KEY_SIZES['AES-192']; }
}
class 
AES_256_CBC extends AES_CBC {
    protected static function 
key_size() { return self::$KEY_SIZES['AES-256']; }
}