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

标签 php bash pdf digital-signature pkcs#7

我想用 PHP 检测签名的 PDF 并验证签名是否有效。从这里document我在下面编写了这段 PHP 代码。

它的作用是:

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

最后我有一个 PKCS7 文件和一个 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

不幸的是,两个哈希值不匹配......

我错过了什么吗?

最佳答案

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。但在任何当前要认真对待的签名配置文件中,这些authenticatedAttributes(又名签名属性)实际上是必需的!

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

例如,您在 follow-up question 中共享的示例文档有authenticatedAttributes,所以这个鼓舞人心的博客让你误入歧途。

关于php - 如何使用openssl提取和验证PDF签名(PKCS7)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54356538/

相关文章:

javascript - Instagram API 速率限制

php - 有没有更好的方法可以在 SQL 中一次从 3 个用户名获取电子邮件?

macos - 应用程序在其他 Mac 上提供 "cant open .app because classic environment is not supported"但在我自己上工作正常

java - PDFBox 中的绝对文本位置

php - 在哪里可以找到 xxhash64 和 md5 碰撞概率统计信息?

PHP保护PDF和DOC

bash - 如何在包含来自 postgres 数据库的数据的 bash 脚本中创建变量

java - 如何从 java 程序启动 Hadoop、Accumulo 和 ZooKeeper?

image - 如何在 TCPDF 中添加图像

python - Django / python : Show pdf in a template