1) mcrypt 模块在 PHP 7.1 中已弃用。
这对加密过滤器意味着什么?
2) 从秘密密码中推导出 IV 似乎是错误的。这会泄露有关密码的信息,而没有必要。
3) 为此使用 md5()? 真的? hash() 自 PHP 5.1.2 起就可用。
4) 对密码进行两次哈希不会增加任何安全性。
5) 在二进制数据上使用 substr()(为什么,为什么,true 传递给 md5()?)如果使用 mbstring.func_overload,可能会导致头痛。
加密过滤器对于文件/流加密特别有用。
自 PHP 7.1.0 起,此功能已弃用。强烈建议不要依赖此功能。
mcrypt.*
和 mdecrypt.*
使用 libmcrypt 提供对称加密和解密。这两组过滤器都支持 mcrypt 扩展 中提供的相同算法,形式为 mcrypt.ciphername
,其中 ciphername
是与 mcrypt_module_open() 传递的密码名称相同的名称。以下五个过滤器参数也可用
参数 | 必需? | 默认 | 示例值 |
---|---|---|---|
模式 | 可选 | cbc | cbc, cfb, ecb, nofb, ofb, stream |
算法目录 | 可选 | ini_get('mcrypt.algorithms_dir') | 算法模块的位置 |
模式目录 | 可选 | ini_get('mcrypt.modes_dir') | 模式模块的位置 |
IV | 必需 | N/A | 通常为 8、16 或 32 字节的二进制数据。取决于密码 |
密钥 | 必需 | N/A | 通常为 8、16 或 32 字节的二进制数据。取决于密码 |
示例 #1 使用 Blowfish 加密/解密
<?php
//$key 假设以前生成过
$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, '要加密的消息');
fclose($fp);
//解密...
$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));//修剪掉空填充
fclose($fp);
echo $data;
?>
示例 #2 使用 AES-128 CBC 和 SHA256 HMAC 加密文件
<?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_128, MCRYPT_MODE_CBC);
$fin = fopen($input_stream, "rb");
$fc = fopen($aes_filename, "wb+");
if (!empty($fin) && !empty($fc)) {
fwrite($fc, str_repeat("_", 32) );//placeholder, SHA256 HMAC will go here later
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 is a number from 1-16
fwrite($fc, str_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_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);//skip sha256 hmac and salt
$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 PKCS7 padding
$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']; }
}
1) mcrypt 模块在 PHP 7.1 中已弃用。
这对加密过滤器意味着什么?
2) 从秘密密码中推导出 IV 似乎是错误的。这会泄露有关密码的信息,而没有必要。
3) 为此使用 md5()? 真的? hash() 自 PHP 5.1.2 起就可用。
4) 对密码进行两次哈希不会增加任何安全性。
5) 在二进制数据上使用 substr()(为什么,为什么,true 传递给 md5()?)如果使用 mbstring.func_overload,可能会导致头痛。
此页面上的示例应立即销毁。
奇怪的是,大多数人会因为使用 MD5 生成 IV 和 3DES 密钥而感到困惑,MD5 是一种弱哈希函数,例如前面的注释和 CryptoFails 博客。
http://www.cryptofails.com/post/70059608390/php-documentation-woes
应使用基于密码的密钥派生函数(bcrypt、PBKDF2)。
但是,使用 MD-5 进行密钥派生并非那么糟糕,如果密码足够强大(通常不是这样),那么即使现在生成的 DES ABC 密钥也足够强大。
对每个密码使用相同的 IV 意味着此函数会直接泄露有关加密文件的信息。如果两个加密文件的开头相同,则此函数会直接泄露信息。例如,如果例程加密多个图像,则 JPEG 标头将很容易识别。
总而言之,这些示例使用已弃用的例程(mcrypt)、已弃用的加密函数(MD5/DES),然后不正确地执行实际的加密。足够理由在第一时间将其销毁。