PHP如何解决使用国密SM4解密Base64数据错误问题?(基于lpilp/guomi)
最近遇到与java对接的时候接收的base64数据,SM4解密一部分正常、一部分乱码乱码的问题,最后仔细看了接收到的base64数据,发现是PHP接收url中的数据问题。
一、下面展示下解决问题之前的问题:
首先、把需要加密的json串整理出来,进行SM4加密
其次、将加密后的base64串添加到接口请求工具中data参数值中
再次、打印PHP接收到的base64数据串
最后、用接收到的数据串进行SM4解密,打印出来发现只解析了前面一点点,后面的是乱码
解决思路:
重点在以下几点:
1、接收到的base64数据;
2、lpilp/guomi扩展包中的SM4加密解密的方法;
思考:扩展包的加密解密方法肯定是没问题的,但是也不保证没有问题,细扒代码,发现是在解密base64_decode数据后,用解密方法解出来后就有问题了,解密方法之前用过,可以肯定没有问题,那就是传入的解密数据有问题了。
发现问题:仔细一看打印出来的加密后的数据和PHP接收到的数据不一样,接收到的数据中有空格。
原因是:+号在 URL 中被浏览器/前端自动转换成空格(空格编码成 %20),而后端 request()->all() 又把空格保留下来,最终造成 Base64 字符串被破坏。
解决办法:将接收到的数据中的空格转换成+号还原。再去调用解密方法
$cipher = str_replace(' ', '+', $cipher); // 把空格全部还原成 +
$plain = (new RtSm4($key))->decrypt($cipher, 'sm4-ecb', '', 'base64');
将数据顺利解密,出来的结果如下:
与加密数据一致。
附件:SM4和SM2加密解密的公共调用方法
<?phpnamespace App\Support;
use Rtgm\sm\RtSm4;
use Rtgm\sm\RtSm2;
use Rtgm\util\MyAsn1;class GuomiHelper
{/*** 生成安全随机数(不超过128位)** @param int $length 随机数长度,最大128位* @return string*/public static function generateNonce($length = 32){$length = min($length, 128);$bytes = random_bytes(ceil($length / 2));return substr(bin2hex($bytes), 0, $length);}/*** 使用 key 进行 SM4 ECB 加密(在用)** @param string $data* @param string $key* @return string*/public static function sm4EcbEncrypt($data, $key) {//dump($data);// 使用示例// $key = hex2bin("30ca0d013a9a782fe4c6b4c30d3e208c");// $data = "18900112233"; 运行结果;571d9decc557bd9d466f6c90df328bb4$key = hex2bin($key);//将十六进制字符串转换为二进制数据$sm4 = new RtSm4($key);$ciphertext = $sm4->encrypt($data, 'sm4-ecb','','base64');return $ciphertext;}/*** 使用 key 进行 SM4 ECB 解密(在用)* * @param string $data* @param string $key* @return string*/public static function sm4EcbDecrypt($data, $key) {// 移除base64编码中的空格换行$key = hex2bin($key);//将十六进制字符串转换为二进制数据 $data, $type = 'sm4', $iv = '', $formatInput = 'hex'$sm4 = new RtSm4($key);$plaintext = $sm4->decrypt($data, 'sm4-ecb','','base64');return $plaintext;}/*** 账户公钥SM2加密(在用)** @param string $data* @param string $publicKey* @return string*/public static function rtSm2Encrypt($plaintext, $publicKeyBase64) { // 从DER格式中提取公钥内容(去除ASN.1头部)$publicKeyHex = MyAsn1::decode($publicKeyBase64,'base64');// 创建SM2实例$sm2 = new RtSm2();// 执行加密(使用公钥的十六进制格式)$ciphertextBin = $sm2->doEncrypt($plaintext, $publicKeyHex[1]);return '04'.$ciphertextBin;}/*** 账户私钥SM2解密(在用)*/public static function rtSm2Decrypt($ciphertext, $privateKey) {// 创建SM2实例$sm2 = new RtSm2();$m2DecryptData = $sm2->doDecrypt($ciphertext,$privateKey);return $m2DecryptData;}/*** 账户公钥SM2验签* 使用 SM2 非对称加密算法结合 SM3 哈希算法对给定的数据进行签名验证* @param string $publicKeyString:十六进制字符串表示的 SM2 公钥。* @param string $notifyString:需要验证签名的原始数据字符串。* @param string $signString:十六进制字符串表示的签名数据。* @return string*/public static function sm2WithSM3Verify($publicKeyString, $notifyString, $signString){$sm2 = new RtSm2();$result = $sm2->verifySign($notifyString, $signString, $publicKeyString);return $result;}
}