注: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_簇
是有一些需要多加注意的地方,否则迁移之路略微坎坷。
mcrypt_簇
虽说被遗弃了,但 文档页 上依然有很多值得注意的文档贡献,有助于我们将 mcrypt_簇
迁移至openssl_簇
,大家应该仔细看一下。
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.
1、即刻起,应尽可能的使用openssl_簇
代替mcrypt_簇
来实现数据的加密功能。
2、MCRYPT_RIJNDAEL_256
并不是AES-256
,如果想使用mcrypt_簇
实现AES-256
,则你应该使用 MCRYPT_RIJNDAEL_128
算法 + 32
位的 key,openssl_簇
则更为清晰的明确了各种模式。这里我整理了一下对应关系供大家参考:
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 、24、32,但openssl_簇
的AES cipher
的iv
长度固定
为16
位。
所以,我们为了最大的适配,即便现在不会再用,也要知道mcrypt_簇
实现 AES-128/192/256 的标准方式
为:
- cipher 固定选用 MCRYPT_RIJNDAEL_128
- 根据 cipher 和 mode 生成 iv(mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128 , MCRYPT_MODE_CBC))),因 cipher 我们固定为 MCRYPT_RIJNDAEL_128 所以 iv 固定为 16 位,同 openssl 兼容。
- 根据实际业务来确定 key 长度: AES-128 16位 / AES-192 24位 / AES-256 32位,而不是使用 mcrypt_get_key_size(MCRYPT_RIJNDAEL_128 下使用此方法获取的 key 长度固定为 16 位,不符合要求)
3、这一点其实蛮重要的,涉及加密算法数据块
&填充算法PKCS7
的概念。我在支付宝alipay SDK
中有看到此算法的实现,虽然 sdk 中仍然使用的mcrypt_簇
,但已结合了PKCS7
填充算法,为什么要这样做呢?其一是为了安全&兼容,php mcrypt
会默认使用null('\000')
对数据块进行填充,java/.net
则默认使用PKCS7
。其二则是为后期迁移至openssl_簇
的准备,openssl
的默认填充算法也是PKCS7
(当然也可以指定使用null('\000')
填充模式,但极力不推荐的)。
mcrypt_encrypt / mcrypt_decrypt
相关的支持函数
// 支持的算法 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)
PKCS7 填充算法的实现
/**
* 填充算法
* @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;
}
openssl_encrypt/openssl_decrypt
简单讲解一下日常开发中用到的参数
/**
* $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_簇 迁移至 openssl_簇
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);
总结
1、PKCS7 填充算法。
2、openssl_encrypt / openssl_decrypt 三种模式所表示的 PKCS7/base64encode。
3、mcrypt_簇 的 cipher/mode 同 openssl_簇 的转换。