base64 编码的签名属性 DER 结构中的消息摘要

Message digest in a base64 encoded signed attributes DER structure

我有以下 ASN1 ASN.1 dump

SET (4 elem)
  SEQUENCE (2 elem)
    OBJECT IDENTIFIER 1.2.840.113549.1.9.3 contentType (PKCS #9)
    SET (1 elem)
      OBJECT IDENTIFIER 1.2.840.113549.1.7.1 data (PKCS #7)
  SEQUENCE (2 elem)
    OBJECT IDENTIFIER 1.2.840.113549.1.9.5 signingTime (PKCS #9)
    SET (1 elem)
      UTCTime 2021-05-26 19:03:42 UTC
  SEQUENCE (2 elem)
    OBJECT IDENTIFIER 1.2.840.113549.1.9.52 cmsAlgorithmProtection (RFC 6211)
    SET (1 elem)
      SEQUENCE (2 elem)
        SEQUENCE (2 elem)
          OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.1 sha-256 (NIST Algorithm)
          NULL
        [1] (2 elem)
          OBJECT IDENTIFIER 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1)
          NULL
  SEQUENCE (2 elem)
    OBJECT IDENTIFIER 1.2.840.113549.1.9.4 messageDigest (PKCS #9)
    SET (1 elem)
      OCTET STRING (32 byte) E2BB4AD28C95B99E9EDEF70662AFE825AF477680F4867B59833AA05313D8F4C0

我知道 OCTET STRING 是我要签名的消息摘要(哈希 sha-256)。在这种情况下,这是一个使用 PDFBOX 的 PDF 文档,我用来签名的代码如下

public byte[] signPKCS7(InputStream content) throws IOException,SignedBytesException {
        try {
            if (SigUtils.checkCertificateUsage((X509Certificate) certificateChain[0])) {
                CMSSignedDataGenerator signGenerator = new CMSSignedDataGenerator();
                X509Certificate userCert = (X509Certificate) this.certificateChain[0];
                ContentSigner mySigner = new CustomSigner(invoke,String.valueOf(userCert.getSerialNumber()),sad);
                signGenerator.addSignerInfoGenerator(
                        new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build())
                                .build(mySigner, userCert));
                signGenerator.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));
                CMSProcessableInputStream msg = new CMSProcessableInputStream(content);
                CMSSignedData signedData = signGenerator.generate(msg, false);
                return signedData.getEncoded();
            } else {
                throw new Exception("Unable to sign pdf. Certificate usage not appropiate for request");
            }
        } catch (GeneralSecurityException | CMSException | OperatorCreationException e) {
            logger.error(e.getMessage());
            throw new RuntimeException("unable to sign pdf!", e);
        }
    }

我还计算了我要签名的文档的 sha-256,结果如下

0622971147486E1900037EFF229D921D14F5B51AAC7171729B2B66F81CDF6585

所以我的问题是,ANS1的消息摘要和我计算的一样吗?如果是这样,当我使用以下代码遍历 ASN1 结构时,我如何获得该结果我无法获得相同的结果

private byte[] getMessageDigest(byte[] signatures) throws IOException {
        ASN1InputStream input = new ASN1InputStream(signatures);
        byte[] bytesToSign = null;
        ASN1Primitive p;
        while ((p = input.readObject()) != null) {
            if (p instanceof ASN1Set) {
                ASN1Set set = ASN1Set.getInstance(p);
                ASN1Sequence asn1 = ASN1Sequence.getInstance(set.getObjectAt(3));
                ASN1Set setOcter = ASN1Set.getInstance(asn1.getObjectAt(1));
                ASN1OctetString octstr = ASN1OctetString.getInstance(setOcter.getObjectAt(0));
                bytesToSign = octstr.getOctets();
            }
        }
        return bytesToSign;
    }

并使用以下代码将字节转换为十六进制

private  String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }
        return new String(hexChars);
    }

我得到以下结果

E2BB4AD28C95B99E9EDEF70662AFE825AF477680F4867B59833AA05313D8F4C0

这是 ASN1 转储的 OCTET STRING,但它不是文档的哈希值。那个 Octet String 总是在变化,所以我可以假设它实际上不是一个常规的消息摘要。那么它到底是什么,我能否获得我要发送给签名的内容的 sha-256

简而言之

文档哈希不是根据您要签名的原始 PDF 计算得出的。该 PDF 首先准备通过应用某些更改进行签名,然后根据准备好的 PDF 计算哈希,除了其中的占位符间隙准备稍后容纳签名容器。

详细

要创建集成的 PDF 签名,必须对 PDF 应用某些更改:

  • 待集成签名的持有者为PDF中的AcroForm表单域。如果 PDF 不包含空的、未使用的签名字段(或不应使用现有字段),则必须将新的签名字段添加到 PDF。
  • 签名表单域可能具有可视化、小部件注释,代表文档本身某些页面上的签名。如果需要这样的可视化效果,则必须将匹配的注释添加到 PDF。
  • 必须将描述签名模式的信息和其他细节添加到 PDF 中。因此,必须将所选签名字段的值设置为具有这些签名详细信息的 PDF 中的新字典对象;这里有两个特殊条目,ByteRangeContents。两者都设置为适合初学者大小的空白值。
  • 标记已添加到 PDF 根 AcroForm 对象,指示 PDF 已签名。

有了这些添加,PDF 就被存储了。此后 Contents 值在文件中的位置是固定的,并且 ByteRange 值的空白值被修补为四个整数的数组, Contents 值之前的文件段的起始偏移量和大小以及之后的文件段的起始偏移量和大小。

然后对文件的这些段的字节进行哈希处理,并生成一个 CMS 签名容器来签署此文档哈希,然后将其注入到 Contents 值中。


在您的情况下,您在待签名属性中找到的散列,

E2BB4AD28C95B99E9EDEF70662AFE825AF477680F4867B59833AA05313D8F4C0

是准备文件的这两个部分的散列,它几乎总是与原始 PDF 的散列不同,就像你的情况一样

0622971147486E1900037EFF229D921D14F5B51AAC7171729B2B66F81CDF6585