最近项目对接合作方接口,接口采用了下面安全机制:
-
发送方将业务数据进行AES加密
-
发送方通过md5对(key + 时间戳 + json报文中的请求节点data节点数据(加密过后)) 进行签名,接收方对报文进行同样签名后与接收到的签名值进行比对。
并双方约定了用于AES加密的AESKey 与 MD5加密的key。
由于我方是接受通知的业务,只需要处理解密。
首先验签:
// 接收的参数全部来源于数组变量 $params
$data = $params['data'];
$timestamp = $params['timestamp'];
// 验签
if ($params['sign'] == md5(self::MD5_KEY . $timestamp . $data)) {
return true;
}
简单,下一步AES解密:
这里有些问题,合作方是Java技术栈,提供的接口文档中是这样写的:
MD5:采用DigestUtils.md5DigestAsHex()
AES:采用AES/CBC/PKCS7Padding
可使用提供的工具类
这样就需要自己实现AES解密了
解密流程是这样的:
- base64解码
- CBC解密
- 删除填充数据
1、2步骤实现:
$data = openssl_decrypt(base64_decode($data), 'AES-128-CBC', self::AES_KEY, OPENSSL_NO_PADDING, self::AES_KEY);
这里为什么IV也是AES_KEY,由于AES-128-CBC下AES_KEY是16bytes,接口约定的AES_KEY也是16bytes,就没有进行处理。
实际上应该是:
self::IV = substr(self::AES_KEY, 0, 16);
$data = openssl_decrypt(base64_decode($data), 'AES-128-CBC', self::AES_KEY, OPENSSL_NO_PADDING, self::IV);
此时的$data结尾是包含填充数据的,这里数据填充的规则是PKCS7Padding。由于第一次接触,就去科普了一下,这篇文章讲的很明白了数据填充规则之PKCS7。
解密只需要看填充数据的最后一位的ASCII,最后一位的ASCII 等于 填充数据的长度。比如最后一位是,对应的ASCII是8。
表示后8位都是填充数据,对于解密的代码,需要移除这8位填充数据。
上面数据填充规则之PKCS7文章中有PKCS7移除填充数据的函数:
/**
* 移除PKCS7
* @param string $content
* @return string
*/
function pkcs7_strip(string $content): string
{
$padding_char = substr($content, -1);// 取出最后一位字符
$padding_size = ord($padding_char);// 查看最后一位字符的ASCII
$content = substr($content, 0, -$padding_size);// 从后面移除字符的ASCII位的字符串
return $content;
}
调整之后的完整代码:
/**
* PKSC7Padding填充算法
* @param string $content
* @return string
*/
private static function stripPKSC7Padding(string $content): string
{
$padding_char = substr($content, -1);
$padding_size = ord($padding_char);
return substr($content, 0, -$padding_size);
}
/**
* 处理通知
* @param array $params 接收的参数,包括data、timestamp、sign
* @return string
*/
public static function notify(array $params): string
{
$data = $params['data'];
$timestamp = $params['timestamp'];
// 验签
if ($params['sign'] == md5(self::MD5_KEY . $timestamp . $data)) {
self::IV = substr(self::AES_KEY, 0, 16);
$encryptedData = json_decode(self::stripPKSC7Padding(openssl_decrypt(base64_decode($data), 'AES-128-CBC', self::AES_KEY, OPENSSL_NO_PADDING, self::IV)), true);
}
}
参考:
数据填充规则之PKCS7
php5/7 AES/CBC/PKCS7Padding加密的实现
最全的ASCII码对照表