记一次PHP实现AES/CBC/PKCS7Padding解密

1,564 阅读1分钟

最近项目对接合作方接口,接口采用了下面安全机制:

  1. 发送方将业务数据进行AES加密

  2. 发送方通过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解密了
解密流程是这样的:

  1. base64解码
  2. CBC解密
  3. 删除填充数据

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 等于 填充数据的长度。比如最后一位是image.png,对应的ASCII是8。

image.png 表示后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
    $contentsubstr($content0, -$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码对照表