PHP+JS环境下提取PDF数字签名(日期、签名人等信息)的实现问题
代码失效核心原因
你写的逻辑有两个直接导致运行失败的问题:
- 正则匹配目标传错:你把txt文件的路径字符串赋值给
$txt后直接传给正则匹配函数,根本没有读取txt内存储的PDF实际内容,自然检索不到ByteRange字段。 - 解析逻辑不完整:现有代码只尝试读取证书内的主体名称,没有解析签名容器内存储的签名时间属性,且对不同版本的PKCS#7签名结构兼容性差。
可直接运行的修正代码
你本地已经安装了对应ASN1、X509依赖,调整路径后即可直接使用,支持提取签名人姓名、签名时间两个核心属性:
<?php // 注意:不要把vendor目录放在System32下,移到站点根目录避免权限问题 require_once('C:\xampp\htdocs\ArchiTW\vendor\autoload.php'); use Sop\ASN1\Type\Constructed\Sequence; use Sop\ASN1\Type\Constructed\Set; use Sop\ASN1\Element; use Sop\X509\Certificate\Certificate; use Sop\ASN1\Type\Primitive\UTCTime; $currentFile = 'C:\xampp\htdocs\ArchiTW\upload\kprocajlo\dokumenty\test.pdf'; $content = file_get_contents($currentFile); // 正则加s修饰符兼容ByteRange折行、多空格场景,匹配所有签名块 $regexp = '/ByteRange\s*\[\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*\]/s'; preg_match_all($regexp, $content, $matches, PREG_SET_ORDER); $signInfo = []; foreach ($matches as $byteRange) { $sigStart = (int)$byteRange[2]; $sigLen = (int)$byteRange[3] - $sigStart - 2; // 去掉包裹签名的<>尖括号 $f = fopen($currentFile, 'rb'); fseek($f, $sigStart + 1); // 跳过开头的< $sigHex = trim(fread($f, $sigLen)); fclose($f); $sigDer = hex2bin($sigHex); $seq = Sequence::fromDER($sigDer); // 解析PKCS#7 SignedData结构 $signedData = $seq->getTagged(0)->asExplicit()->asSequence(); // 取证书集合,拿最后一个签名者证书(兼容证书链场景) $certSet = $signedData->getTagged(0)->asImplicit(Element::TYPE_SET)->asSet(); $signerCert = Certificate::fromASN1($certSet->at($certSet->count() - 1)->asSequence()); // 提取签名人姓名(取证书主题CN字段) $signerName = $signerCert->tbsCertificate()->subject()->firstValueOf('CN')->toString(); // 解析签名者信息取签名时间 $signerInfoSet = $signedData->getTagged(2)->asImplicit(Element::TYPE_SET)->asSet(); $signerInfoSeq = $signerInfoSet->at(0)->asSequence(); $signedAttrs = $signerInfoSeq->getTagged(0)->asImplicit(Element::TYPE_SET)->asSet(); $signTime = ''; // OID 1.2.840.113549.1.9.5 对应签名时间属性 foreach ($signedAttrs->elements() as $attr) { $attrSeq = $attr->asSequence(); if ($attrSeq->at(0)->asOID()->oid() === '1.2.840.113549.1.9.5') { $timeVal = $attrSeq->at(1)->asSet()->at(0); if ($timeVal instanceof UTCTime) { $signTime = $timeVal->dateTime()->format('Y-m-d H:i:s'); } break; } } $signInfo[] = [ 'signer_name' => $signerName, 'sign_time' => $signTime ]; } // 输出测试结果 print_r($signInfo);
Joomla+Fabrik环境适配注意事项
- 不要把Composer依赖放在
C:\Windows\System32目录,系统目录权限限制会导致依赖加载随机失败,统一移到Joomla站点根目录下的vendor文件夹,用站点相对路径引入。 - 读取PDF文件必须使用
rb二进制模式,否则Windows环境下会自动转义特殊字节,导致签名块偏移量计算错误。 - 如果PDF存在多个有效签名,上述代码会循环返回所有签名的信息,直接绑定到Fabrik表单字段即可。
内容的提问来源于stack exchange,提问作者Krzysztof
相关产品推荐
相关产品推荐

