ConFoo 2025

Filtros de Criptografia

Os filtros de criptografia são especialmente úteis para criptografia de arquivo/stream.

mcrypt.* and mdecrypt.*

Aviso

Este recurso tornou-se DEFASADA a partir do PHP 7.1.0. O uso deste recurso é fortemente desencorajado.

Os filtros mcrypt.* e mdecrypt.* fornecem criptografia e descriptografia simétricas usando libmcrypt. Ambos os conjuntos de filtros suportam os mesmos algoritmos disponíveis na extensão mcrypt na forma de mcrypt.ciphername onde ciphername é o nome da cifra como seria passado para mcrypt_module_open(). Os cinco parâmetros de filtros a seguir também estão disponíveis:

Parâmetros para os filtros mcrypt
Parâmetro Obrigatório? Valor Padrão Exemplos de Valores
mode Opcional cbc cbc, cfb, ecb, nofb, ofb, stream
algorithms_dir Opcional ini_get('mcrypt.algorithms_dir') Localização dos módulos de algoritmos
modes_dir Opcional ini_get('mcrypt.modes_dir') Localização dos módulos de modos
iv Obrigatório N/A Tipicamente 8, 16 ou 32 bytes de dados binários. Depende da cifra.
key Obrigatório N/A Tipicamente 8, 16 ou 32 bytes de dados binários. Depende da cifra.

Exemplo #1 Criptografando/Descriptografando com Blowfish

<?php
// assume-se que $key foi gerada anteriormente
$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_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, 'mensagem para criptografar');
fclose($fp);

// descriptografando...
$fp = fopen('encrypted-file.enc', 'rb');
$iv = fread($fp, $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_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)); // retira o preenchimento com null
fclose($fp);
echo
$data;
?>

Exemplo #2 Criptografando arquivo usando CBC AES-128 com HMAC SHA256

<?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']; } // padrão AES-128
public static function encryptFile($password, $input_stream, $aes_filename){
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$fin = fopen($input_stream, "rb");
$fc = fopen($aes_filename, "wb+");
if (!empty(
$fin) && !empty($fc)) {
fwrite($fc, str_repeat("_", 32) ); // marcador, HMAC SHA256 será usado aqui mais tarde
fwrite($fc, $hmac_salt = mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM));
fwrite($fc, $esalt = mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM));
fwrite($fc, $iv = mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM));
$ekey = hash_pbkdf2("sha256", $password, $esalt, $it=1000, self::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($fin, 8192);
$infilesize+=strlen($block);
fwrite($fc, $block);
}
$block_size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$padding = $block_size - ($infilesize % $block_size); // $padding é um número de 1 a 16
fwrite($fc, str_repeat(chr($padding), $padding) ); // realiza um preenchimento PKCS7
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); // sobrescreve o marcador
fclose($fc);
}
}
public static function
decryptFile($password, $aes_filename, $out_stream) {
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$hmac_raw = file_get_contents($aes_filename, false, NULL, 0, 32);
$hmac_salt = file_get_contents($aes_filename, false, NULL, 32, $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($fc, 32+$iv_size); // ignora hmac e salt do sha256
$esalt = fread($fc, $iv_size);
$iv = fread($fc, $iv_size);
$ekey = hash_pbkdf2("sha256", $password, $esalt, $it=1000, self::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($fc, 8192);
if (
feof($fc)) {
$padding = ord($block[strlen($block) - 1]); // assume preenchimento PKCS7
$block = substr($block, 0, 0-$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=1000, 24, $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']; }
}
adicione uma nota

Notas Enviadas por Usuários (em inglês) 2 notes

up
3
Uwe Ohse
7 years ago
1) The mcrypt module has been deprecated with PHP 7.1.
What does that mean for the encryption filters?

2) deriving the IV from the secret passphrase seems wrong. This leaks information about the passphrase without need.

3) using md5() for this? Really? hash() has been available since PHP 5.1.2.

4) hashing the passphrase twice does not add any security.

5) using substr() on binary data (why, oh why, is true passed to md5()?) might lead to headaches if mbstring.func_overload is used.
up
0
Maarten Bodewes
7 years ago
The examples on this page should be destroyed with utmost urgency.

Strangely most people will fall over the use of generating the IV and 3DES key using MD5, a weak hash function, e.g. the previous note and CryptoFails blog.

http://www.cryptofails.com/post/70059608390/php-documentation-woes

A password based key derivation function should be used (bcrypt, PBKDF2).

However the use of MD-5 for key derivation however isn't that bad and if the password is strong enough (it usually isn't) then the generated DES ABC key is strong enough even now.

Using an identical IV for each password means that this function directly leaks information about the encrypted file. If the start of two encrypted files is identical then this function will directly leak information. For instance, if the routine encrypts multiple images then the JPEG header would be easily distinguished.

All in all these examples use deprecated routines (mcrypt), deprecated cryptographic functions (MD5 / DES) and then incorrectly perform the actual encryption. Enough reason to destroy them in the first place.
To Top