如何使用PHP从PDF中检索数字签名信息?

yk9xbfzb  于 2023-04-04  发布在  PHP
关注(0)|答案(4)|浏览(132)

我有一个应用程序,需要检索一些数据(签名人姓名)从数字签名“附加”的PDF文件。
我只在Java和C#中找到使用iText类AcroFields方法GetSignatureNames的示例

**edit:**我尝试了 pdftk 和dump_data_fields以及generate_fpdf,结果是(不幸的是):

/Fields [
<<
/V /dftk.com.lowagie.text.pdf.PdfDictionary@3048918
/T (Signature1)
>>]

FieldType: Signature
FieldName: Signature1
FieldFlags: 0
FieldJustification: Left

提前致谢!

kmb7vmvb

kmb7vmvb1#

好吧,这是复杂的(我会说甚至是不可能的,但谁知道呢),以实现这只与PHP。
首先,请阅读article about digital signature in Adobe PDF
第二,在阅读完本文后,您将知道签名根据/ByteRange[a b c d]指示符存储在b和c字节之间
第三,我们可以从文档中提取B和c,然后提取签名本身(指南说它将是十六进制解码的PKCS7#对象)。

<?php

 $content = file_get_contents('test.pdf');

 $regexp = '#ByteRange\[\s*(\d+) (\d+) (\d+)#'; // subexpressions are used to extract b and c

 $result = [];
 preg_match_all($regexp, $content, $result);

 // $result[2][0] and $result[3][0] are b and c
 if (isset($result[2]) && isset($result[3]) && isset($result[2][0]) && isset($result[3][0]))
 {
     $start = $result[2][0];
     $end = $result[3][0];
     if ($stream = fopen('test.pdf', 'rb')) {
         $signature = stream_get_contents($stream, $end - $start - 2, $start + 1); // because we need to exclude < and > from start and end

         fclose($stream);
     }

     file_put_contents('signature.pkcs7', hex2bin($signature));
}

第四,在第三步之后,我们在文件signature.pkcs7中有PKCS#7对象。不幸的是,我不知道使用PHP从签名中提取信息的方法。所以你必须能够运行shell命令才能使用openssl

openssl pkcs7 -in signature.pkcs7 -inform DER -print_certs > info.txt

在文件info.txt中运行此命令后,您将拥有一系列证书。最后一个是您需要的证书。您可以查看文件的结构并解析所需的数据。
另请参考this questionthis questionthis topic

编辑于2017-10-09我故意建议您查看exactly this question有一个代码,您可以根据需要进行调整。

use ASN1\Type\Constructed\Sequence;
use ASN1\Element;
use X509\Certificate\Certificate;       

$seq = Sequence::fromDER($binaryData);
$signed_data = $seq->getTagged(0)->asExplicit()->asSequence();
// ExtendedCertificatesAndCertificates: https://tools.ietf.org/html/rfc2315#section-6.6
$ecac = $signed_data->getTagged(0)->asImplicit(Element::TYPE_SET)->asSet();
// ExtendedCertificateOrCertificate: https://tools.ietf.org/html/rfc2315#section-6.5
$ecoc = $ecac->at($ecac->count() - 1);
$cert = Certificate::fromASN1($ecoc->asSequence());
$commonNameValue = $cert->tbsCertificate()->subject()->toString();
echo $commonNameValue;

我已经帮你调整好了,剩下的请你自己做。

yi0zb3m4

yi0zb3m42#

这是我在PHP7中的工作代码:

<?php

require_once('vendor/autoload.php');

use Sop\ASN1\Type\Constructed\Sequence;
use Sop\ASN1\Element;
use Sop\X509\Certificate\Certificate;  


$currentFile = "./upload/test2.pdf";

$content = file_get_contents($currentFile);

$regexp = '/ByteRange\ \[\s*(\d+) (\d+) (\d+)/'; // subexpressions are used to extract b and c

$result = [];
preg_match_all($regexp, $content, $result);

// $result[2][0] and $result[3][0] are b and c
if (isset($result[2]) && isset($result[3]) && isset($result[2][0]) && isset($result[3][0])) {
    $start = $result[2][0];
    $end = $result[3][0];
    if ($stream = fopen($currentFile, 'rb')) {
        $signature = stream_get_contents($stream, $end - $start - 2, $start + 1); // because we need to exclude < and > from start and end

        fclose($stream);
    }

    
    $binaryData = hex2bin($signature);
    
    $seq = Sequence::fromDER($binaryData);
    $signed_data = $seq->getTagged(0)->asExplicit()->asSequence();
    // ExtendedCertificatesAndCertificates: https://tools.ietf.org/html/rfc2315#section-6.6
    $ecac = $signed_data->getTagged(0)->asImplicit(Element::TYPE_SET)->asSet();
    // ExtendedCertificateOrCertificate: https://tools.ietf.org/html/rfc2315#section-6.5
    $ecoc = $ecac->at($ecac->count() - 1);
    $cert = Certificate::fromASN1($ecoc->asSequence());
    $commonNameValue = $cert->tbsCertificate()->subject()->toString();
    echo $commonNameValue;

    
}
kmynzznz

kmynzznz3#

类似于@Denis Alimov提出的solution,但只使用PHP函数(而不是**openssl**命令),并且没有Composer依赖项:

<?php
function der2pem($der_data) {

    // https://www.php.net/manual/en/ref.openssl.php

    $pem = chunk_split(base64_encode($der_data), 64, "\n");
    $pem = "-----BEGIN CERTIFICATE-----\n".$pem."-----END CERTIFICATE-----\n";
    return $pem;
}

function extract_pkcs7_signatures($path_to_pdf) {

    // https://stackoverflow.com/q/46430367

    $pdf_contents = file_get_contents($path_to_pdf);

    $regexp = '/ByteRange\ \[\s*(\d+) (\d+) (\d+)/';

    $result = [];
    preg_match_all($regexp, $pdf_contents, $result);

    $signatures = [];

    if (isset($result[0])) {
        $signature_count = count($result[0]);
        for ($s = 0; $s < $signature_count; $s++) {
            $start = $result[2][$s];
            $end = $result[3][$s];
            $signature = null;
            if ($stream = fopen($path_to_pdf, 'rb')) {
                $signature = stream_get_contents($stream, $end - $start - 2, $start + 1);
                fclose($stream);
                $signature = hex2bin($signature);
                $signatures[] = $signature;
            }
        }
    }

    return $signatures;
}

function who_signed($path_to_pdf) {

    // https://www.php.net/manual/en/openssl.certparams.php
    // https://www.php.net/manual/en/function.openssl-pkcs7-read.php
    // https://www.php.net/manual/en/function.openssl-x509-parse.php

    $signers = [];

    $pkcs7_der_signatures = extract_pkcs7_signatures($path_to_pdf);
    if (!empty($pkcs7_der_signatures)) {
        $parsed_certificates = [];
        foreach ($pkcs7_der_signatures as $pkcs7_der_signature) {
            $pkcs7_pem_signature = der2pem($pkcs7_der_signature);
            $pem_certificates = [];
            $result = openssl_pkcs7_read($pkcs7_pem_signature, $pem_certificates);
            if ($result) {
                foreach ($pem_certificates as $pem_certificate) {
                    $parsed_certificate = openssl_x509_parse($pem_certificate);
                    $parsed_certificates[] = $parsed_certificate;
                }
            }
        }

        // Remove certificate authorities certificates

        $people_certificates = [];
        foreach ($parsed_certificates as $certificate_a) {
            $is_authority = false;
            foreach ($parsed_certificates as $certificate_b) {
                if ($certificate_a['subject'] == $certificate_b['issuer']) {
                    // If certificate A is of the issuer of certificate B, then
                    // certificate A belongs to a certificate authority and,
                    // therefore, should be ignored
                    $is_authority = true;
                    break;
                }
            }
            if (!$is_authority) {
                $people_certificates[] = $certificate_a;
            }
        }

        // Remove duplicate certificates

        $distinct_certificates = [];
        foreach ($people_certificates as $certificate_a) {
            $is_duplicated = false;
            if (count($distinct_certificates) > 0) {
                foreach ($distinct_certificates as $certificate_b) {
                    if (
                        ($certificate_a['subject'] == $certificate_b['subject']) &&
                        ($certificate_a['serialNumber'] == $certificate_b['serialNumber']) &&
                        ($certificate_a['issuer'] == $certificate_b['issuer'])
                    ) {
                        // If certificate B has the same subject, serial number
                        // and issuer as certificate A, then certificate B is a
                        // duplicate and, therefore, should be ignored
                        $is_duplicated = true;
                        break;
                    }
                }
            }
            if (!$is_duplicated) {
                $distinct_certificates[] = $certificate_a;
            }
        }

        foreach ($distinct_certificates as $certificate) {
            $signers[] = $certificate['subject']['CN'];
        }
    }

    return $signers;
}

$path_to_pdf = 'test.pdf';

// In case you want to test the extract_pkcs7_signatures() function:

/*
$signatures = extract_pkcs7_signatures($path_to_pdf);
for ($s = 0; $s < count($signatures); $s++) {
    $path_to_pkcs7 = pathinfo($path_to_pdf, PATHINFO_FILENAME) . $s . '.pkcs7';
    file_put_contents($path_to_pkcs7, $signatures[$s]);
    echo shell_exec("openssl pkcs7 -inform DER -in $path_to_pkcs7 -print_certs -text");
}
exit;
*/

var_dump(who_signed($path_to_pdf));
?>

对于一些test1.pdf,只有一个人签名(我们称她为ALICE),这个脚本应该返回:

array(1) {
  [0]=>
  string(5) "ALICE"
}

对于一些由两个人签名的test2.pdf(我们称之为BOBCAROL),此脚本应返回:

array(2) {
  [0]=>
  string(3) "BOB"
  [1]=>
  string(5) "CAROL"
}

想了解更多信息,看看我的这个问题:

bakd9h0s

bakd9h0s4#

我使用过iText,发现它非常可靠,我强烈推荐它。你可以随时从PHP中调用Java代码作为“微服务”。

相关问题