如何在PHP中验证PKCS#7签名?需转换可行Java代码
如何在PHP中验证PKCS#7(CMS)格式的数字签名
你遇到的核心问题是误用了普通RSA验证逻辑来处理PKCS#7(CMS)格式的签名——PKCS#7签名是一个包含签名值、签名证书、算法信息等的完整容器,不是单纯的RSA签名串。你之前的PHP代码要么生成了新的密钥对(完全错误),要么从网站SSL证书取公钥(不是签名用的证书),自然无法验证通过。
下面给出两种和你Java代码逻辑完全对齐的可行PHP实现方案:
方案1:使用PHP OpenSSL扩展(原生支持PKCS#7)
OpenSSL的openssl_pkcs7_verify函数专门用于处理PKCS#7签名验证,我们可以用它快速实现需求:
<?php // 1. 处理输入的签名参数 $JSONDATA = str_replace("'", '"', $_GET["sign"]); $data = json_decode($JSONDATA, true); $signatureBase64 = $data['signature']; $originalData = json_encode($data['param']); // 需要验证的原始业务数据 // 2. 将base64编码的PKCS#7签名解码后写入临时文件(OpenSSL函数要求文件输入) $signatureDer = base64_decode($signatureBase64); $sigTempFile = tempnam(sys_get_temp_dir(), 'pkcs7_sig'); file_put_contents($sigTempFile, $signatureDer); // 3. 将原始业务数据写入临时文件 $dataTempFile = tempnam(sys_get_temp_dir(), 'pkcs7_data'); file_put_contents($dataTempFile, $originalData); // 4. 执行PKCS#7签名验证 $verifyResult = openssl_pkcs7_verify( $dataTempFile, // 原始数据文件路径 PKCS7_NOVERIFY, // 先只验证签名,不验证证书链;若需验证证书有效性可移除该参数 $sigTempFile, // PKCS#7签名文件路径 [], // 可信CA证书列表(若需验证证书链,传入CA证书路径数组) null, null ); // 5. 清理临时文件 unlink($sigTempFile); unlink($dataTempFile); // 6. 输出验证结果 if ($verifyResult === 1) { echo "签名验证成功!"; } elseif ($verifyResult === 0) { echo "签名验证失败:" . openssl_error_string(); } else { echo "验证过程出错:" . openssl_error_string(); } ?>
关键说明:
PKCS7_NOVERIFY参数:如果你的场景只需要验证签名本身(不需要校验签名证书的有效期、信任链),可以保留该参数;如果需要完整复刻Java代码中的证书有效性检查,移除该参数并传入可信CA证书数组即可。- 临时文件:
openssl_pkcs7_verify要求输入为文件路径,因此需要临时存储解码后的签名和原始数据。
方案2:使用phpseclib的CMS模块(完全对齐Java逻辑)
phpseclib 3.x版本支持CMS(PKCS#7)操作,能直接解析签名容器、提取证书,完全复刻你Java代码的验证流程:
<?php require 'vendor/autoload.php'; // 确保已通过Composer安装phpseclib 3.x use phpseclib3\File\CMS; use phpseclib3\Crypt\PublicKeyLoader; // 1. 处理输入数据 $JSONDATA = str_replace("'", '"', $_GET["sign"]); $data = json_decode($JSONDATA, true); $signatureBase64 = $data['signature']; $originalData = json_encode($data['param']); // 2. 解析PKCS#7签名容器 $cms = new CMS(); $cms->load(base64_decode($signatureBase64)); // 3. 遍历签名者,复刻Java代码的验证逻辑 $verified = false; $signers = $cms->getSigners(); foreach ($signers as $signer) { // 获取签名者证书 $cert = $signer['certificate']; // 检查证书有效期(对应Java的checkValidity()) $now = new DateTime(); if ($cert->getNotBefore() > $now || $cert->getNotAfter() < $now) { continue; } // 验证证书自身的签名(对应Java的verify(signedcert.getPublicKey())) if (!$cert->verify($cert->getPublicKey())) { continue; } // 验证业务数据的签名 $publicKey = PublicKeyLoader::load($cert->getPublicKey()); if ($publicKey->verify($originalData, $signer['signature'])) { $verified = true; break; } } // 输出结果 echo $verified ? "签名验证成功!" : "签名验证失败"; ?>
关键说明:
- 确保安装phpseclib 3.x版本:执行
composer require phpseclib/phpseclib:^3.0完成安装。 - 这段代码完全对齐你的Java逻辑:遍历签名者、检查证书有效期、验证证书自身合法性、最终验证业务数据签名。
为什么你之前的代码失败?
- phpseclib方案错误:你生成了新的RSA密钥对,而不是使用签名容器中携带的证书公钥,相当于用自己的密钥验证第三方签名,必然失败。
- OpenSSL方案错误:你从目标网站的SSL证书获取公钥,但签名是用对方的代码签名证书生成的,和SSL证书完全无关,公钥不匹配自然验证失败。
内容的提问来源于stack exchange,提问作者Prince Expedition
相关产品推荐
相关产品推荐

