PHP实现AES-256-CBC PKCS7对接.NET服务端加密失败排查
问题根因
你的PHP实现和官方JS示例存在多处逻辑不一致,是导致服务端返回公钥错误、加密校验失败的直接原因,核心问题如下:
- DH公钥生成阶段使用
base_convert转超大素数存在浮点数精度丢失,生成的本地公钥本身不符合规范 - 共享密钥计算时存在变量名笔误,且GMP输出结果未补全前导零,导致共享密钥长度不足、内容错位
- IV、AES密钥的截取长度、偏移完全不符合JS逻辑
- 传入openssl的密钥、IV未做十六进制转二进制处理,和CryptoJS的解析逻辑不匹配
- 传给服务端的本地公钥未做前导零补全,格式不符合服务端解析要求
逻辑对照(和官方JS实现对齐)
官方JS的加密参数规则:
- 计算得到DH共享密钥后,取前96个十六进制字符作为加密用的密钥基串
- IV取密钥基串前32个十六进制字符(对应16字节,符合AES-CBC的IV长度要求)
- AES密钥取密钥基串第32位开始的64个十六进制字符(对应32字节,符合AES-256的密钥长度要求)
- IV、密钥均为十六进制解析后的原始二进制字节,不是普通字符串
修正后可运行代码
常量定义与本地密钥对生成
// DH固定协议参数 private const DH_PRIME = 'F488FD584E49DBCD20B49DE49107366B336C380D451D0F7C88B31C7C5B2D8EF6F3C923C043F0A55B188D8EBB558CB85D38D334FD7C175743A31D186CDE33212CB52AFF3CE1B1294018118D7C84A70A72D686C40319C807297ACA950CD9969FABD00A509B0246D3083D66A45D419F9C7CBD894B221926BAABA25EC355E92F78C7'; private const DH_GENERATOR = '02'; private const DH_KEY_BYTE_LENGTH = 192; // DH素数对应的字节长度 // 初始化GMP参数 $prime = gmp_init(self::DH_PRIME, 16); $generator = gmp_init(self::DH_GENERATOR, 16); // 生成本地私钥(增加随机熵避免私钥重复) $privateKey = gmp_init(uniqid(mt_rand(), true), 32); // 计算本地公钥 $localPublicKey = gmp_powm($generator, $privateKey, $prime); // 公钥补前导零到固定长度,转base64后传给服务端 $localPublicKeyBin = gmp_export($localPublicKey); $localPublicKeyBin = str_pad($localPublicKeyBin, self::DH_KEY_BYTE_LENGTH, "\0", STR_PAD_LEFT); $localPublicKeyBase64 = base64_encode($localPublicKeyBin);
共享密钥计算
// 解码服务端返回的base64格式公钥 $serverPublicKeyBin = base64_decode($apiResponse['response']['Command']['OutputParams']['Param']['7']['@attributes']['Value']); $serverPublicKey = gmp_import($serverPublicKeyBin); // 计算DH共享密钥 $sharedKey = gmp_powm($serverPublicKey, $privateKey, $prime); // 共享密钥补前导零到固定长度,转十六进制字符串 $sharedKeyBin = gmp_export($sharedKey); $sharedKeyBin = str_pad($sharedKeyBin, self::DH_KEY_BYTE_LENGTH, "\0", STR_PAD_LEFT); $sharedKeyHex = bin2hex($sharedKeyBin);
加密参数提取与数据加密
// 对齐JS规则截取IV和密钥 $secretString = substr($sharedKeyHex, 0, 96); $iv = hex2bin(substr($secretString, 0, 32)); $key = hex2bin(substr($secretString, 32, 64)); // AES-256-CBC加密,openssl默认使用PKCS7填充,无需额外配置 $cipher = 'AES-256-CBC'; $encryptedUsername = base64_encode(openssl_encrypt($username, $cipher, $key, OPENSSL_RAW_DATA, $iv)); $encryptedPassword = base64_encode(openssl_encrypt($password, $cipher, $key, OPENSSL_RAW_DATA, $iv));
注意事项
- 不要使用
base_convert转换超大整数,会因为浮点数精度丢失导致计算结果完全错误,所有DH大数运算直接通过GMP函数处理 - 所有DH公钥、共享密钥转二进制后必须在左侧补
\0到固定192字节长度,否则会因为前导零丢失导致密钥错位 - 传给服务端的本地公钥必须是补零后二进制转base64的格式,不要直接传十六进制字符串
内容的提问来源于stack exchange,提问作者dual
相关产品推荐
相关产品推荐

