代码之家  ›  专栏  ›  技术社区  ›  nowox

如何使用openssl提取和验证PDF签名(PKCS7)?

  •  0
  • nowox  · 技术社区  · 6 年前

    我想在PHP中检测签名的pdf并验证签名是否有效。从这个 document 我已经在下面编写了这个PHP代码。

    1. 提取PKCS7代码(它工作,因为我可以从Openssl获得详细信息)
    2. 计算文档的SHA256哈希。

    现在,我想用PKCS7文件验证我的签名。我该怎么做?我最初希望 digest_enc_alg / sha256WithRSAEncryption / enc_digest ,但这似乎不是我要找的。

    class VerifyPDF
    {
        public static function getByteRange($filename)
        {
            $content = file_get_contents($filename);
            if (!preg_match_all('/ByteRange\[\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*\]/',
                $content, $matches))
            {
                throw new \Exception('Unable to get certificate');
            }
    
            return [
                intval($matches[1][0]), // Offset of the first part (usually 0)
                intval($matches[2][0]), // Size of the first part
                intval($matches[3][0]), // Offset to the second part
                intval($matches[4][0])  // Size of the second part
            ];
        }
    
        public static function get_pkcs7($filename)
        {
            [$o1, $l1, $o2, $l2] = self::getByteRange($filename);
    
            if (!$fp = fopen($filename, 'rb')) {
                throw new \Exception("Unable to open $filename");
            }
    
            $signature = stream_get_contents($fp, $o2 - $l1 - 2, $l1 + 1);
            fclose($fp);
    
            file_put_contents('out.pkcs7', hex2bin($signature));
        }
    
        public static function compute_hash($filename)
        {
            [$o1, $l1, $o2, $l2] = self::getByteRange($filename);
    
            if (!$fp = fopen($filename, 'rb')) {
                throw new \Exception("Unable to open $filename");
            }
    
            $i = stream_get_contents($fp, $l1, $o1);
            $j = stream_get_contents($fp, $l2, $o2);
    
            if (strlen($i) != $l1 || strlen($j) != $l2) {
                throw new \Exception('Invalid chunks');
            }
    
            fclose($fp);
    
            return hash('sha256', $i . $j);
        }
    }
    

    5036ae43aba11ce626f6f9b1d5246ba0700e217655b9ff927e31fbefadfa2182
    

    灵感来源于 this

    #!/bin/bash
    PKCS7='out.pkcs7'
    
    # Extract Digest (SHA256)
    OFFSET=$(openssl asn1parse -inform der -in $PKCS7 | \
        perl -ne 'print $1 + $2 if /(\d+):d=\d\s+hl=(\d).*?256 prim.*HEX DUMP/m')
    dd if=$PKCS7 of=signed-sha256.bin bs=1 skip=$OFFSET count=256
    
    # Extract Public key 
    openssl pkcs7 -print_certs -inform der -in $PKCS7 | \
        tac | sed '/-----BEGIN/q' | tac > client.pem
    openssl x509 -in client.pem -pubkey -noout > client.pub.pem
    
    # Verify the signature
    openssl rsautl -verify -pubin -inkey client.pub.pem < signed-sha256.bin > verified.bin
    
    # Get Hash and compare with the computed hash from the PDF
    openssl asn1parse -inform der -in verified.bin | grep -Po '\[HEX DUMP\]:\K\w+$' | tr A-F a-f
    

    给我这个:

    C8581962753927BB57B66B1D0D0F4B33A29EF3E03DA12D2329DB72763AC7EDB6
    

    所以不幸的是两个散列不匹配。。。

    我遗漏了什么吗?

    1 回复  |  直到 6 年前
        1
  •  1
  •   mkl    6 年前

    这个 blog 你曾经 显示以下图形以解释PKCS#7签名容器结构

    screen shot

    但实际上,这只是PKCS#7定义的最简单的结构。如果你看看 SignerInfo 规格( content - signerInfos - SignerInfo ),你会明白的

       SignerInfo ::= SEQUENCE {
         version Version,
         issuerAndSerialNumber IssuerAndSerialNumber,
         digestAlgorithm DigestAlgorithmIdentifier,
         authenticatedAttributes
           [0] IMPLICIT Attributes OPTIONAL,
         digestEncryptionAlgorithm
           DigestEncryptionAlgorithmIdentifier,
         encryptedDigest EncryptedDigest,
         unauthenticatedAttributes
           [1] IMPLICIT Attributes OPTIONAL }
    

    ( RFC 2315 section 9.2 "SignerInfo type" )

    特别是 authenticatedAttributes 你在上面的素描里找不到的。但在目前的签名资料中 (又名签名属性)实际上是必需的!

    此外,如果有 在PKCS#7签名容器签名者信息对象中,加密摘要是 已验证的属性 结构。在这种情况下,文档数据的摘要存储为特定签名属性“messageDigest”属性的值。因此,在这种情况下,您尝试提取错误的值来比较文档摘要。

    follow-up question 已验证的属性 ,所以鼓舞人心的博客让你误入歧途。