前言: PHP7有着openssl_pkcs7_verify的ca验签忽略缺陷,知道php8才修复.写这篇文章是为了记录也是为了后面的人方便查阅。
缺陷来源:bugs.php.net/bug.php?id=… 更新版本:github.com/php/php-src…
前因: 由于公司业务需要需要对接阿里云某些特定的接口,需要实现pkcs7验签。原本业务用的php7.3可以是不管怎样也只报验签错误,或者是验签成功无法写入。
pkcs7验签实现: 因为我不需要验证ca所以就没做ca验证。把获取的签名实体,公钥,以及签名,分别写入文件这样才能实现,pkcs7验签。着重讲php的pkcs7签名文件格式。 示例:
MIME-Version: 1.0
Content-Disposition: attachment; filename="smime.p7m"
Content-Type: application/x-pkcs7-mime; smime-type=signed-data; name="smime.p7m"
Content-Transfer-Encoding: base64
签名
解决方案: 1.捕捉异常错误 openssl_pkcs7_verify(): signature OK, but cannot open for writing 这个就是验签通过的 2.升级php版本之修复版本(不过有业务的公司大概率是没办法升级的) 3.新建一个验签项目,就可以实现验签了。
相关代码
//$data 实体数据
//$signature 签名信息
//$publicKey公钥
private function verifySignature($data, $signature, $publicKey)
{
$signatureFileName = time() . random_int(0, 999999) . '.p7m';
$signatureFileCentent = self::der2smime($signature, $signatureFileName);
Filesystem::disk('verify')->write($signatureFileName, $signatureFileCentent);
$signaturePath = Filesystem::disk('verify')->path($signatureFileName);
$dataFileName = time() . random_int(0, 999999) . '.txt';
Filesystem::disk('verify')->write($dataFileName, $data);
$dataPath = Filesystem::disk('verify')->path($dataFileName);
$publicKeyFileName = time() . random_int(0, 999999) . '.txt';
Filesystem::disk('verify')->write($publicKeyFileName, $publicKey);
$publicKeyPath = Filesystem::disk('verify')->path($publicKeyFileName);
$isValid = openssl_pkcs7_verify(
$signaturePath, //私钥信息
PKCS7_NOVERIFY,
null,
[],
$publicKeyPath, //公钥
$dataPath //文件路径
);
Filesystem::disk('verify')->delete($signatureFileName);
Filesystem::disk('verify')->delete($dataFileName);
Filesystem::disk('verify')->delete($publicKeyFileName);
return $isValid ?? false;
}
private static function der2smime($signature,$signatureFileName)
{
// 去除多余的空白字符
$signature = preg_replace('/\s+/', '', $signature);
$smimeContent = "MIME-Version:1.0\n";
$smimeContent .= "Content-Disposition:attachment;filename=".$signatureFileName."\n";
$smimeContent .= "Content-Type:application/pkcs7-mime;smime-type=signed-data; ame="$signatureFileName"\n";
$smimeContent .= "Content-Transfer-Encoding:base64\n\n";
$smimeContent .= chunk_split($signature);
return $smimeContent;
}