You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 12:27:44