个中 Mcrypt 在 PHP 7.1.0 中被 Deprecated,在 PHP 7.2.0 中被移除,以是即可起你该当利用 OpenSSL 来实现 AES 的数据加解密。

在一些场景下,我们不能担保两套通信系统都利用了相函数簇去实现加密算法,可能 siteA 利用了最新的 OpenSSL 来实现了 AES 加密,但作为第三方做事的 siteB 可能仍在利用 Mcrypt 算法,这就哀求我们必须清楚 Mcrypt 同 OpenSSL 之间的差异,以便担保数据加解密的同等性。

下文中我们将分别利用 Mcrypt 和 OpenSSL 来实现 AES-128/192/256-CBC 加解密,二者同步加解密的要点为:

phpmcryptcbcphp迁徙Mcrypt至OpenSSL加密算法的具体介绍代码示例 RESTful API

1、利用何种添补算法。
Mcrypt 自动利用 NUL(\"大众\\0\公众),OpenSSL 自动利用 PKCS7。

2、否对数据做了base64编码处理。
Mcrypt 默认不该用 base64 编码(虽然我们很建议利用),OpenSSL 默认利用 base64编码。

协同好以上两点,就可以让 Mcrypt 和 OpenSSL 之间同等性的对数据进行加解密。

AES 概述

AES 是当前最为常用的安全对称加密算法,关于对称加密这里就不在阐述了。

AES 有三种算法,紧张是对数据块的大小存在差异:

AES-128:须要供应 16 位的密钥 key

AES-192:须要供应 24 位的密钥 key

AES-256:须要供应 32 位的密钥 key

添补算法

AES 是按数据块大小(128/192/256)对待加密内容进行分块处理的,会常常涌现末了一段数据长度不敷的场景,这时就须要填充数据长度到加密算法对应的数据块大小。

紧张的添补算法有添补 NUL(\"大众0\公众) 和 PKCS7,Mcrypt 默认利用的 NUL(\"大众0\"大众) 添补算法,当前已不被推举,OpenSSL 则默认模式利用 PKCS7 对数据进行添补并对加密后的数据进行了 base64encode 编码,以是建议开拓中利用 PKCS7 对待加密数据进行添补,已担保通用性(alipay sdk 中虽然利用了 Mcrypt 加密簇,但利用 PKCS7 算法对数据进行了添补,这样在一定程度上亲和了 OpenSSL 加密算法)。

NUL(\"大众\0\"大众) 添补算法

Mcrypt 的默认添补算法。
NUL 即为 Ascii 表的编号为 0 的元素,即空元素,转移字符是 \"大众\0\公众,PHP 的 pack 打包函数在 'a' 模式下便是以 NUL 字符对内容进行添补的,当然,利用 \公众\0\"大众 手动拼接也是可以的。

/

NUL(\"大众\0\"大众)添补算法

@param string $source

@return string

/

function addZeroPadding($source, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC)

{

$source = trim($source);

// openssl 并没有供应加密cipher对应的数据块大小的api这点比较坑

$block = mcrypt_get_block_size($cipher, $mode);

$pad = $block - (strlen($source) % $block);

if ($pad <= $block) {

// $source .= str_repeat(\公众\0\"大众, $pad);//KISS写法

// pack 方法 a 模式利用 NUL(\"大众\0\"大众) 对内容进行添补 A 模式则利用空缺字符添补

$source .= pack(\公众a{$pad}\"大众, \"大众\公众); //高端写法

}

return $source;

}

/

NUL(\"大众\0\公众)添补算法移除

@param string $source

@return string

/

function stripZeroPadding($source)

{

return rtrim($source, \"大众\0\公众);

}

PKCS7 添补算法

OpenSSL的默认添补算法。
下面我们给出 PKCS7 添补算法 PHP 的实现:

/

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;

}

Mcrypt

默认利用 NUL(\"大众\0\"大众) 自动对待加密数据进行添补以对齐加密算法数据块长度。

获取 mcrypt 支持的算法,这里我们只关注 AES 算法。

$cipher_list = mcrypt_list_algorithms();

print_r($cipher_list);

// 加密算法名称 对应的 常量标识

// 'rijndael-128' == MCRYPT_RIJNDAEL_128

// 'rijndael-192' == MCRYPT_RIJNDAEL_192

// 'rijndael-256' == MCRYPT_RIJNDAEL_256

把稳:mcrypt 虽然支持 AES 三种算法,但除 MCRYPT_RIJNDAEL_128 外,MCRYPT_RIJNDAEL_192/256 并未遵照 AES-192/256 标准进行加解密的算法,即如果你同其他系统通信(java/.net),利用 MCRYPT_RIJNDAEL_192/256 可能无法被其他严格按照 AES-192/256 标准的系统精确的数据解密。
官方文档页面中也有人在 User Contributed Notes 中提及。
这里给出如何利用 mcrpyt 做标注的 AES-128/192/256 加解密

AES-128-CBC = mcrpyt(cipher MCRYPT_RIJNDAEL_128 + key 16位 + iv 16 位 + mode MCRYPT_MODE_CBC)

AES-192-CBC = mcrpyt(cipher MCRYPT_RIJNDAEL_128 + key 24位 + iv 16 位 + mode MCRYPT_MODE_CBC)

AES-256-CBC = mcrpyt(cipher MCRYPT_RIJNDAEL_128 + key 32位 + iv 16 位 + mode MCRYPT_MODE_CBC)

即算法统一利用 MCRYPT_RIJNDAEL_128,并通过 key 的位数 来选定因此何种 AES 标准做的加密,iv 是建议添加且建议固定为16位(OpenSSL的 AES加密 iv 始终为 16 位,便于统一对齐),mode 选用的 CBC 模式。

mcrypt 在对数据进行加密处理时,如果创造数据长度与利用的加密算法的数据块长度未对齐,则会自动利用 \"大众\0\"大众 对待加密数据进行添补,但 \公众\0\"大众 添补模式已不再被推举,为了与其他系统有更好的兼容性,建议大家手动对数据进行 PKCS7 添补。

OpenSSL

openssl 簇加密方法更为大略明确,mcrypt 还要将加密算法分为 cipher + mode 去指定,openssl 则只须要直接指定 method 为 AES-128-CBC,AES-192-CBC,AES-256-CBC 即可。
且供应了三种数据处理模式,即 默认模式 0 / OPENSSL_RAW_DATA/ OPENSSL_ZERO_PADDING。

openssl 默认的数据添补办法是 PKCS7,为兼容 mcrpty 也供应处理 \公众0\公众 添补的数据的模式,详细为下:

//我们只看部分参数 还有一些默认参数没列出

// 加密

openssl_encrypt(string $data, string $method, string $key, int $options = 0, string $iv = \公众\"大众)

// 解密

openssl_decrypt(string $data, string $method, string $key, int $options = 0, string $iv = \"大众\公众)

options 参数即为主要,它是兼容 mcrpty 算法的关键:

options = 0: 默认模式,自动对明文进行 pkcs7 padding,且数据做 base64 编码处理。

options = 1: OPENSSL_RAW_DATA,自动对明文进行 pkcs7 padding, 且数据未经 base64 编码处理。

options = 2: OPENSSL_ZERO_PADDING,哀求待加密的数据长度已按 \"大众0\"大众 添补与加密算法数据块长度对齐,即同 mcrpty 默认添补的办法同等,且对数据做 base64 编码处理。
把稳,此模式下 openssl 哀求待加密数据已按 \公众0\公众 添补好,其并不会自动帮你填充数据,如果未添补对齐,则会报错。

故可以得出 mcrpty簇 与 openssl簇 的兼容条件如下:

1、如果 A 系统利用了 mcrypt 的默认的 \"大众\0\"大众 自动添补算法进行了数据加密,那 B 系统利用 openssl 进行解密时,须要选择 OPENSSL_ZERO_PADDING 模式。
这里同时要特殊把稳,OPENSSL_ZERO_PADDING 模式因此为数据是做了 base64 编码处理的,如果 A 系统 mcrpty 没有对数据做 base64_encode 处理,则 B 解密数据时还须要将数据 base64_encode 后再通报给 openssl_decrypt。

2、如果 A 系统手动利用了 PKCS7 对待加密数据做了处理,那 B 系统只需根据 A 系统是否对数据做了 base64 编码处理选择 0或 OPENSSL_ZERO_PADDING 模式即可。
如果对 A 利用了 base64 编码,则 B 利用 0 模式;如果 A 没有利用 base64 编码,则利用 OPENSSL_RAW_DATA 模式。

加解密实例

建议将源码复制到本地运行,根据运行结果更好理解。

<?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 添补, 且返回的加密数据经由 base64 编码

1 : OPENSSL_RAW_DATA, 自动对数据做 pkcs7 添补, 且返回的加密数据未经由 base64 编码

2 : OPENSSL_ZERO_PADDING, 处理利用 NUL(\"大众\0\"大众) 的数据,故需手动利用 NUL(\公众\0\"大众) 添补好数据再做加密处理,如未做则会报错

--------------------------------------------------------------------------------------------------------

加密工具类

/

// mcrypt AES 固定利用 MCRYPT_RIJNDAEL_128 通过 key 的长度来决定详细利用的详细何种 AES

$mcrypt_cipher = MCRYPT_RIJNDAEL_128;

$mcrypt_mode = MCRYPT_MODE_CBC;

// aes-128=16 aes-192=24 aes-256=32

$key_size = 16;

$key = get_random_str($key_size);

// openssl AES 向量长度固定 16 位 这里为兼容建议固定长度为 16 位

$iv_size = 16;

$iv = get_random_str($iv_size);

// 随机字符串

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));

}

/

加密算法

@param string $content 待加密数据

@param string $key 加密key 把稳 key 长度哀求

@param string $iv 加密向量 固定为16位可以担保与openssl的兼容性

@param string $cipher 加密算法

@param string $mode 加密模式

@param bool $pkcs7 是否利用pkcs7添补 否则利用 mcrypt 自带的 NUL(\"大众\0\公众) 添补

@param bool $base64 是否对数据做 base64 处理 因加密后数据会有非打印字符 以是推举做 base64 处理

@return string 加密后的内容

/

function user_mcrypt_encrypt($content, $key, $iv, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC, $pkcs7 = true, $base64 = true)

{

//AES, 128 模式加密数据 CBC

$content = $pkcs7 ? addPKCS7Padding($content) : $content;

$content_encrypted = mcrypt_encrypt($cipher, $key, $content, $mode, $iv);

return $base64 ? base64_encode($content_encrypted) : $content_encrypted;

}

/

解密算法

@param [type] $content_encrypted 待解密的内容

@param [type] $key 加密key 把稳 key 长度哀求

@param [type] $iv 加密向量 固定为16位可以担保与openssl的兼容性

@param [type] $cipher 加密算法

@param [type] $mode 加密模式

@param bool $pkcs7 带解密内容是否利用了pkcs7添补 如果没利用则 mcrypt 会自动移除添补的 NUL(\公众\0\公众)

@param bool $base64 是否对数据做 base64 处理

@return [type] [description]

/

function user_mcrypt_decrypt($content_encrypted, $key, $iv, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC, $pkcs7 = true, $base64 = true)

{

//AES, 128 模式加密数据 CBC

$content_encrypted = $base64 ? base64_decode($content_encrypted) : $content_encrypted;

$content = mcrypt_decrypt($cipher, $key, $content_encrypted, $mode, $iv);

// 解密后的内容 要根据添补算法来相应的移除填充数

$content = $pkcs7 ? stripPKSC7Padding($content) : rtrim($content, \"大众\0\公众);

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;

}

/

NUL(\"大众\0\"大众)添补算法

@param string $source

@return string

/

function addZeroPadding($source, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC)

{

$source = trim($source);

// openssl 并没有供应加密cipher对应的数据块大小的api这点比较坑

$block = mcrypt_get_block_size($cipher, $mode);

$pad = $block - (strlen($source) % $block);

if ($pad <= $block) {

// $source .= str_repeat(\"大众\0\"大众, $pad);//KISS写法

// pack 方法 a 模式利用 NUL(\"大众\0\"大众) 对内容进行添补 A 模式则利用空缺字符添补

$source .= pack(\"大众a{$pad}\"大众, \公众\"大众); //高端写法

}

return $source;

}

/

NUL(\公众\0\"大众)添补算法移除

@param string $source

@return string

/

function stripZeroPadding($source)

{

return rtrim($source, \"大众\0\"大众);

}

// 待加密内容

$content = \"大众hello world\"大众;

echo '利用 NUL(\公众\0\"大众) 添补算法 不对结果做 base64 处理:' . PHP_EOL;

echo 'mcrypt 加密:' . PHP_EOL;

var_dump($data = user_mcrypt_encrypt($content, $key, $iv, $mcrypt_cipher, $mcrypt_mode, false, false));

echo 'openssl 解密:' . PHP_EOL;

// 需经由 NUL(\"大众\0\公众) 添补加密后被 base64_encode 的数据 解密后续手动移除 NUL(\公众\0\公众)

var_dump(stripZeroPadding(openssl_decrypt(base64_encode($data), \公众AES-128-CBC\公众, $key, OPENSSL_ZERO_PADDING, $iv)));

echo 'openssl 加密:' . PHP_EOL;

// 需对待处理的数据做 NUL(\"大众\0\"大众) 添补,且返回的数据被 base64_encode 编码了

var_dump($data = base64_decode(openssl_encrypt(addZeroPadding($content), \"大众AES-128-CBC\公众, $key, OPENSSL_ZERO_PADDING, $iv)));

echo 'mcrypt 解密:' . PHP_EOL;

var_dump(user_mcrypt_decrypt($data, $key, $iv, $mcrypt_cipher, $mcrypt_mode, false, false));

echo PHP_EOL;

echo '利用 NUL(\公众\0\公众) 添补算法 对结果做 base64 处理:' . PHP_EOL;

echo 'mcrypt 加密:' . PHP_EOL;

var_dump($data = user_mcrypt_encrypt($content, $key, $iv, $mcrypt_cipher, $mcrypt_mode, false, true));

echo 'openssl 解密:' . PHP_EOL;

var_dump(stripZeroPadding(openssl_decrypt($data, \"大众AES-128-CBC\"大众, $key, OPENSSL_ZERO_PADDING, $iv)));

echo 'openssl 加密:' . PHP_EOL;

var_dump($data = openssl_encrypt(addZeroPadding($content), \"大众AES-128-CBC\"大众, $key, OPENSSL_ZERO_PADDING, $iv));

echo 'mcrypt 解密:' . PHP_EOL;

var_dump(user_mcrypt_decrypt($data, $key, $iv, $mcrypt_cipher, $mcrypt_mode, false, true));

echo PHP_EOL;

echo \公众利用 pkcs7 添补算法 不对结果做 base64 处理\"大众 . PHP_EOL;

echo 'mcrypt 加密:' . PHP_EOL;

var_dump($data = user_mcrypt_encrypt($content, $key, $iv, $mcrypt_cipher, $mcrypt_mode, true, false));

echo 'openssl 解密:' . PHP_EOL;

var_dump(openssl_decrypt($data, \"大众AES-128-CBC\公众, $key, OPENSSL_RAW_DATA, $iv));

echo 'openssl 加密:' . PHP_EOL;

var_dump($data = openssl_encrypt($content, \公众AES-128-CBC\公众, $key, OPENSSL_RAW_DATA, $iv));

echo 'mcrypt 解密:' . PHP_EOL;

var_dump(user_mcrypt_decrypt($data, $key, $iv, $mcrypt_cipher, $mcrypt_mode, true, false));

echo PHP_EOL;

echo \公众利用 pkcs7 添补算法 对结果做 base64 处理(推举):\"大众 . PHP_EOL;

echo 'mcrypt 加密:' . PHP_EOL;

var_dump($data = user_mcrypt_encrypt($content, $key, $iv, $mcrypt_cipher, $mcrypt_mode, true, true));

echo 'openssl 解密:' . PHP_EOL;

var_dump(openssl_decrypt($data, \"大众AES-128-CBC\公众, $key, 0, $iv));

echo 'openssl 加密:' . PHP_EOL;

var_dump($data = openssl_encrypt($content, \公众AES-128-CBC\公众, $key, 0, $iv));

echo 'mcrypt 解密:' . PHP_EOL;

var_dump(user_mcrypt_decrypt($data, $key, $iv, $mcrypt_cipher, $mcrypt_mode, true, true));

总结要点

1、二者利用的何种添补算法。

2、二者对数据是否有 base64 编码哀求。

3、mcrypt 需固定利用 MCRYPT_RIJNDAEL_128,并通过调度 key 的长度 16, 24,32 来实现 ase-128/192/256 加密算法。
同时二者 IV 长度都该当是。