注:php 的mcrypt_簇
在 7.1.0 版本中開始 deprecated,並在 7.2.0 版本中完全廢棄。其實在 2015 就已經開始建議你們使用openssl_encrypt/openssl_decrypt
來代替mcrypt_encrypt/mcrypt_decryp
t,緩衝了 N 久,這一天終於在 7.2.0 版本上到來了。
爲何要說起遷移,好比 A & B 兩套系統使用AES
加密作數據傳輸,A 做爲客戶端,B 則是第三方的服務,且 A 已經在使用 7.2.0+ 版本,而 B 做爲長期運行的服務仍在使用 7.1.0-,那咱們能作的就是在 A 上使用 openssl_簇 原樣的實現mcrypt_簇
的加解密功能,以便兼容 B 服務,且 mcrypt_簇
是有一些須要多加註意的地方,不然遷移之路略微坎坷。php
mcrypt_簇
雖然說被遺棄了,但 文檔頁 上依然有不少值得注意的文檔貢獻,有助於咱們將 mcrypt_簇
遷移至openssl_簇
,你們應該仔細看一下。java
1.If you're writing code to encrypt/encrypt data in 2015, you should use openssl_encrypt() and openssl_decrypt(). The underlying library (libmcrypt) has been abandoned since 2007, and performs far worse than OpenSSL (which leverages AES-NI on modern processors and is cache-timing safe).2.Also, MCRYPT_RIJNDAEL_256 is not AES-256, it's a different variant of the Rijndael block cipher. If you want AES-256 in mcrypt, you have to use MCRYPT_RIJNDAEL_128 with a 32-byte key. OpenSSL makes it more obvious which mode you are using (i.e. 'aes-128-cbc' vs 'aes-256-ctr').算法
3.OpenSSL also uses PKCS7 padding with CBC mode rather than mcrypt's NULL byte padding. Thus, mcrypt is more likely to make your code vulnerable to padding oracle attacks than OpenSSL.安全
一、即刻起,應儘量的使用openssl_簇
代替mcrypt_簇
來實現數據的加密功能。oracle
二、MCRYPT_RIJNDAEL_256
並非AES-256
,若是想使用mcrypt_簇
實現AES-256
,則你應該使用 MCRYPT_RIJNDAEL_128
算法 + 32
位的 key,openssl_簇
則更爲清晰的明確了各類模式。這裏我整理了一下對應關係供你們參考:dom
MCRYPT_RIJNDAEL_128 & MCRYPT_MODE_CBC + 16位Key = openssl_encrypt(AES-128-CBC, 16位Key) = AES-128 MCRYPT_RIJNDAEL_128 & MCRYPT_MODE_CBC + 24位Key = openssl_encrypt(AES-192-CBC, 24位Key) = AES-192 MCRYPT_RIJNDAEL_128 & MCRYPT_MODE_CBC + 32位Key = openssl_encrypt(AES-256-CBC, 32位Key) = AES-256
(注:AES-128, 192 and 256 的加密 key 的長度分別爲 16, 24 and 32 位)函數
openssl_簇
的確更爲準確,並且mcrypt_get_key_size
獲得的key
長度都是 32 位,因此不太靠譜。工具
iv
到是會根據cipher
變動 16 、2四、32,但openssl_簇
的AES cipher
的iv
長度固定
爲16
位。編碼
因此,咱們爲了最大的適配,即使如今不會再用,也要知道mcrypt_簇
實現 AES-128/192/256 的標準方式
爲:加密
三、這一點其實蠻重要的,涉及加密算法數據塊
&填充算法PKCS7
的概念。我在支付寶alipay SDK
中有看到此算法的實現,雖然 sdk 中仍然使用的mcrypt_簇
,但已結合了PKCS7
填充算法,爲何要這樣作呢?其一是爲了安全&兼容,php mcrypt
會默認使用null('\000')
對數據塊進行填充,java/.net
則默認使用PKCS7
。其二則是爲後期遷移至openssl_簇
的準備,openssl
的默認填充算法也是PKCS7
(固然也能夠指定使用null('\000')
填充模式,但極力不推薦的)。
// 支持的算法 rijndael-128|rijndael-192|rijndael-256(此算法並不是AES-256,需使用rijndael-128 + key32byte實現) mcrypt_list_algorithms() // 支持的模式 cbc ecb 等 mcrypt_list_modes() // 算法所對應的 key 長度:AES-128, 192 and 256 的加密 key 的長度分別爲 16, 24 and 32 位 mcrypt_get_key_size(string $cipher , string $mode) // 算法所對應的加密向量 iv 的長度 mcrypt_get_iv_size(string $cipher , string $mode) // 生成 iv mcrypt_create_iv(mcrypt_get_iv_size(string $cipher , string $mode)) // 加密算法數據塊的大小 主要用於填充算法 mcrypt_get_block_size(string $cipher , string $mode)
/** * 填充算法 * @param string $source * @return string */ function addPKCS7Padding($source, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC) { $source = trim($source); // 獲取加密算法數據塊大小 用於計算須要填充多少位 $block_size = mcrypt_get_block_size($cipher, $mode); $pad = $block_size - (strlen($source) % $block_size); if ($pad <= $block_size) { $char = chr($pad); $source .= str_repeat($char, $pad); } return $source; } /** * 移去填充算法 * @param string $source * @return string */ function stripPKCS7Padding($source) { $source = trim($source); $char = substr($source, -1); $num = ord($char); if ($num == 62) { return $source; } $source = substr($source, 0, -$num); return $source; }
簡單講解一下平常開發中用到的參數
/** * $data 待加密內容 * $method 加密算法 * $key 加密key * $options 數據塊填充模式 * $iv 加密向量 **/ openssl_encrypt(string $data, string $method, string $key[, int $options = 0[, string $iv = "" [, string &$tag = NULL[, string $aad = ""[, int $tag_length = 16 ]]]]]): string openssl_decrypt(string $data, string $method, string $key[, int $options = 0[, string $iv = "" [, string $tag = "" [, string $aad = "" ]]]] ) : string
這裏須要特別注意的就是 options 選項,不少人 mcrypt_簇 遷移至 openssl_簇 時兩者加密結果內容不一致,大都是此處沒有搞清楚的緣由。options 共 3 個值可選
0 默認值 使用 PKCS7 填充算法,不對加密結果進行 base64encode
1 OPENSSL_RAW_DATA 使用 PKCS7 填充算法,且對加密結果進行 base64encode
2 OPENSSL_ZERO_PADDING 使用 null('0') 進行填充,且對加密結果進行 base64encode
因此要注意填充算法及對結果是否進行了 base64encode 編碼。
mcrypt_簇
/** * 加密算法 * @param string $content 待加密數據 * @param string $key 加密key * @param string $iv 加密向量 * @param string $cipher 加密算法 * @param string $mode 加密模式 * @return string 加密後的內容且base64encode */ function encrypt($content, $key, $iv, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC) { //AES, 128 模式加密數據 CBC $content = addPKCS7Padding($content); $content_encrypted = mcrypt_encrypt($cipher, $key, $content, $mode, $iv); return base64_encode($content_encrypted); } /** * 解密算法 * @param [type] $content [description] * @param [type] $key [description] * @param [type] $iv [description] * @param [type] $cipher [description] * @param [type] $mode [description] * @return [type] [description] */ function decrypt($content_encrypted, $key, $iv, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC) { //AES, 128 模式加密數據 CBC $content_encrypted = base64_decode($content_encrypted); $content = mcrypt_decrypt($cipher, $key, $content_encrypted, $mode, $iv); $content = stripPKSC7Padding($content); return $content; } /** * PKCS7填充算法 * @param string $source * @return string */ function addPKCS7Padding($source, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC) { $source = trim($source); $block = mcrypt_get_block_size($cipher, $mode); $pad = $block - (strlen($source) % $block); if ($pad <= $block) { $char = chr($pad); $source .= str_repeat($char, $pad); } return $source; } /** * 移去PKCS7填充算法 * @param string $source * @return string */ function stripPKSC7Padding($source) { $source = trim($source); $char = substr($source, -1); $num = ord($char); if ($num == 62) { return $source; } $source = substr($source, 0, -$num); return $source; }
openssl_簇
以 AES-128 爲例
// 固定使用此算法 而後經過 key 的長度來決定具體使用的是何種 AES $cipher = MCRYPT_RIJNDAEL_128; $mode = MCRYPT_MODE_CBC; // openssl_簇 iv 固定爲 16 位,mcrypt_簇 MCRYPT_RIJNDAEL_128 是 16位 // 但改成 MCRYPT_RIJNDAEL_192/256 就是 24/32 位了,會不兼容 openssl_簇 // 因此務必注意向量長度統一固定 16 位方便兩套算法對齊 // $iv = mcrypt_create_iv(mcrypt_get_iv_size($cipher, $mode), MCRYPT_RAND); // 根據須要自行定義相應的 key 長度 aes-128=16 aes-192=24 aes-256=32 $key = '0123456789012345'; // 固定爲 16 位 $iv = '0123456789012345'; $content = "hello world"; // mcrypt 加解密 $mcrypt_data = encrypt($content, $key, $iv, $cipher, $mode); var_dump($mcrypt_data); $content = decrypt($mcrypt_data, $key, $iv, $cipher, $mode); var_dump($content); // mcrypt 時使用了 PKCS7 填充 並對結果 base64encode // 若是 +PKCS7 +base64encode 則 option = 0 // 若是 +PKCS7 -base64encode 則 option = 1 // 若是 -PKCS7 +base64encode 則 option = 2 $openssl_data = openssl_encrypt($content, "AES-128-CBC", $key, 0, $iv) var_dump($openssl_data); $content = openssl_decrypt($openssl_data, "AES-128-CBC", $key, 0, $iv) var_dump($content); // 相互轉換 $content = openssl_decrypt($mcrypt_data, "AES-128-CBC", $key, 0, $iv) var_dump($content); $content = decrypt($openssl_data, $key, $iv, $cipher, $mode); var_dump($content);
一、PKCS7 填充算法。
二、openssl_encrypt / openssl_decrypt 三種模式所表示的 PKCS7/base64encode。
三、mcrypt_簇 的 cipher/mode 同 openssl_簇 的轉換。
<?php /** * MCRYPT_RIJNDAEL_128 & CBC + 16位Key + 16位iv = openssl_encrypt(AES-128-CBC, 16位Key, 16位iv) = AES-128 * MCRYPT_RIJNDAEL_128 & CBC + 24位Key + 16位iv = openssl_encrypt(AES-192-CBC, 24位Key, 16位iv) = AES-192 * MCRYPT_RIJNDAEL_128 & CBC + 32位Key + 16位iv = openssl_encrypt(AES-256-CBC, 32位Key, 16位iv) = AES-256 * ------------------------------------------------------------------------------------------------------ * openssl_簇 options * 0 : 自動對明文進行 pkcs7 padding, 返回的數據通過 base64 編碼. * 1 : OPENSSL_RAW_DATA, 自動對明文進行 pkcs7 padding, 但返回的結果未通過 base64 編碼 * 2 : OPENSSL_ZERO_PADDING, 自動對明文進行 null('0') 填充, 同 mcrpty 一致,且返回的結果通過 base64 編碼, openssl 不推薦 0 填充的方式, 即便選擇此項也不會自動進行 padding, 仍需手動 padding * -------------------------------------------------------------------------------------------------------- * mcrypt 默認是用 0 填充,爲保持良好的兼容性建議使用 pkcs7 填充數據 openssl 0|1 都使用的 pkcs7 * pkcs7 填充 * 加密工具類 */ // 隨機字符串 function get_random_str($length = 16) { $char_set = array_merge(range('a', 'z'), range('A', 'Z'), range('0', '9')); shuffle($char_set); return implode('', array_slice($char_set, 0, $length)); } // 固定使用此算法 而後經過 key 的長度來決定具體使用的是何種 AES $mcrypt_cipher = MCRYPT_RIJNDAEL_128; $mcrypt_mode = MCRYPT_MODE_CBC; // openssl_簇 AES iv 固定爲 16 位,mcrypt_簇只有在 MCRYPT_RIJNDAEL_128 爲 16 位 需注意保持一致 $iv = mcrypt_create_iv(mcrypt_get_iv_size($mcrypt_cipher, $mcrypt_mode), MCRYPT_RAND); // aes-128=16 aes-192=24 aes-256=32 $key_size = 16; $key = get_random_str($key_size); // openssl_ AES 向量長度固定 16 位 這裏爲 $iv = get_random_str(16); /** * 加密算法 * @param string $content 待加密數據 * @param string $key 加密key * @param string $iv 加密向量 * @param string $cipher 加密算法 * @param string $mode 加密模式 * @return string 加密後的內容且base64encode */ function encrypt($content, $key, $iv, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC) { //AES, 128 模式加密數據 CBC $content = addPKCS7Padding($content); $content_encrypted = mcrypt_encrypt($cipher, $key, $content, $mode, $iv); return base64_encode($content_encrypted); } /** * 解密算法 * @param [type] $content [description] * @param [type] $key [description] * @param [type] $iv [description] * @param [type] $cipher [description] * @param [type] $mode [description] * @return [type] [description] */ function decrypt($content_encrypted, $key, $iv, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC) { //AES, 128 模式加密數據 CBC $content_encrypted = base64_decode($content_encrypted); $content = mcrypt_decrypt($cipher, $key, $content_encrypted, $mode, $iv); $content = stripPKSC7Padding($content); return $content; } /** * PKCS7填充算法 * @param string $source * @return string */ function addPKCS7Padding($source, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC) { $source = trim($source); $block = mcrypt_get_block_size($cipher, $mode); $pad = $block - (strlen($source) % $block); if ($pad <= $block) { $char = chr($pad); $source .= str_repeat($char, $pad); } return $source; } /** * 移去PKCS7填充算法 * @param string $source * @return string */ function stripPKSC7Padding($source) { $source = trim($source); $char = substr($source, -1); $num = ord($char); if ($num == 62) { return $source; } $source = substr($source, 0, -$num); return $source; } $content = "hello world"; var_dump($data = encrypt($content, $key, $iv, $mcrypt_cipher, $mcrypt_mode)); var_dump(decrypt($data, $key, $iv, $mcrypt_cipher, $mcrypt_mode)); var_dump($openssl_data = openssl_encrypt($content, "AES-128-CBC", $key, 0, $iv)); var_dump(openssl_decrypt($openssl_data, "AES-128-CBC", $key, 0, $iv)); // var_dump(openssl_cipher_iv_length('AES-256-CBC')); // var_dump(mcrypt_get_key_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));