如何使用openssl提取和验证PDF签名(PKCS7)? [英] How to extract and verify PDF signature (PKCS7) with openssl?

查看:494
本文介绍了如何使用openssl提取和验证PDF签名(PKCS7)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在PHP中检测签名的PDF,并验证签名是否有效。从这个



实际上,这仅表示PKCS#7定义的最简单的结构。如果您查看 SignerInfo 规范( content-signerInfos-SignerInfo ),则会看到

  SignerInfo :: = SEQUENCE {
版本Version,
issuerAndSerialNumber IssuerAndSerialNumber,
digestAlgorithm DigestAlgorithmIdentifier,
authenticatedAttributes
[0]隐含属性,可选,
digestEncryptionAlgorithm
DigestEncryptionAlgorithmIdentifier,
cryptoDigestated
[1]隐式属性可选}

RFC 2315第9.2节 SignerInfo类型



特别是您可以在上面的草图中找不到可选的 authenticatedAttributes 。但是,在任何当前的签名配置文件中,必须认真使用这些 authenticatedAttributes (又称签名属性)!



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



例如,在您的后续问题中有 authenticatedAttributes ,因此启发性博客使您误入歧途。 / p>

I would like to detect signed PDFs in PHP and verify if the signature is valid. From this document I have written this PHP code below.

What it does is:

  1. Extract the PKCS7 code (it works because I can get the details from Openssl)
  2. Compute the SHA256 hash of the document.

At the end I has a PKCS7 file and a SHA256.

Now, I would like to verify my signature against my PKCS7 file. How can I do this? I initially looked to the digest_enc_alg/sha256WithRSAEncryption/enc_digest, but it seems it is not what I am looking about.

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);
    }
}

The HASH I get is:

5036ae43aba11ce626f6f9b1d5246ba0700e217655b9ff927e31fbefadfa2182

So inspired from this I did the following:

#!/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

Which gives me this:

C8581962753927BB57B66B1D0D0F4B33A29EF3E03DA12D2329DB72763AC7EDB6

So unfortunately by two hashes do not match...

Am I missing something?

解决方案

The blog you were inspired from shows the following graphics to explain the PKCS#7 signature container structure

Actually, though, this represents only the most simple structure defined by PKCS#7. If you look at the SignerInfo specification (content - signerInfos - SignerInfo), you'll see

   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")

In particular there are the OPTIONAL authenticatedAttributes which you don't find in the sketch above. But in any current signature profile to be taken seriously these authenticatedAttributes (aka signed attributes) are actually required!

Furthermore, if there are authenticatedAttributes in a PKCS#7 signature container signer info object, the encrypted digest is not the digest of the document data but instead the digest of the authenticatedAttributes structure. In this case the digest of the document data is stored as the value of a specific signed attribute, the "messageDigest" attribute. Thus, in this case you try to extract the wrong value to compare the document digest with.

For example in case of the example document you shared in your follow-up question there are authenticatedAttributes, so the inspiring blog led you astray.

这篇关于如何使用openssl提取和验证PDF签名(PKCS7)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆