使用java和iText签署PDF哈希

问题描述 投票:0回答:1

我有一个生成PDF的应用程序,需要签名。

我们没有签署文档的证书,因为它们在HSM中,我们使用证书的唯一方法是使用Web服务。

    PdfReader reader = new PdfReader(src);
    reader.setAppendable(true);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    FileOutputStream fout = new FileOutputStream(dest);
    PdfStamper stamper = PdfStamper.createSignature(reader, fout, '\0');
    PdfSignatureAppearance appearance  = stamper.getSignatureAppearance();

    appearance.setReason("Test");
    appearance.setLocation("footer");
    appearance.setVisibleSignature(new Rectangle(100, 100, 200, 200), 1, null);
    appearance.setCertificate(certChain[0]);

    PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
    dic.setReason(appearance.getReason());
    dic.setLocation(appearance.getLocation());
    dic.setContact(appearance.getContact());
    dic.setDate(new PdfDate(appearance.getSignDate()));
    appearance.setCryptoDictionary(dic);

    HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
    exc.put(PdfName.CONTENTS, new Integer(8192 * 2 + 2));
    appearance.preClose(exc);

    ExternalDigest externalDigest = new ExternalDigest()
    {
        public MessageDigest getMessageDigest(String hashAlgorithm) throws GeneralSecurityException
        {
            return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
        }
    };

    PdfPKCS7 sgn = new PdfPKCS7(null, certChain, "SHA256", null, externalDigest, false);
    InputStream data = appearance.getRangeStream();
    byte[] hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));
    Calendar cal = Calendar.getInstance();

    byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
    sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);

    String hashPdf = new String(Base64.encodeBytes(sh));

    String hashSignat = hashPdf;

这是我们的代码,首先,我们获得签名外观,并计算哈希值

在这一点上,我们有文档的哈希码。然后我们将哈希发送到webservice,我们得到签名的哈希码。

最后,我们将签名的哈希值放到PDF中:

    sgn.setExternalDigest(Base64.decode(hashSignat), null, "RSA");
    byte[] encodedSign = sgn.getEncodedPKCS7(hash, cal, null, null, 
    null, CryptoStandard.CMS);
    byte[] paddedSig = new byte[8192];
    System.arraycopy(encodedSign, 0, paddedSig, 0, 
    encodedSign.length);

    PdfDictionary dic2 = new PdfDictionary();
    dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));

    appearance.close(dic2);

在这一点上,我们获得了PDF签名,但签名无效。 Adobe称“文件自签署后已被更改或损坏”。

我经历过Sign PDF using an external service and iTextPDF signature itext pkcs7 multi signIs it possible to sign a PDF document with hash and signed hash?,但没有运气。

java pdf hash itext digital-signature
1个回答
0
投票

The example file

您在评论中共享了由您的代码签名的example file

对该文件的分析(使用AnalyzeSignatures测试testPriyankaSignatureSampleinformedconsent_Signed执行)显示实际签名的哈希是

1134ED96F42AC7352E546BE0E906C0BF5D44229AEAFAC39145DB40A0BB7E817B

这应该是经过身份验证的属性的哈希值,但它们的哈希值是

E7101D9770ABF5E58D43670AAC6E9418265AE80F74B3BDFB67911C0CC5D5D949.

带符号的字节范围哈希是

D7917CF3BA9FE5D5B626ED825965D699F7C54DBBF9F18DECE18EF8DD36DC4C28,

所以它不是这个哈希。因此,不清楚签名哈希来自何处。

最终证明了这一点

Web服务上的编码实用程序是不同的,因为解码的哈希值是错误的,并且不需要MessageDigest(sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);)。这条线给出了错误的哈希值。

Signing previously signed files

此后出现了一个新问题:

当我签名时,已经签名的文件正在签名,但签名无效。它显示“签名字节范围无效”。

要签署已签名的pdf,您必须使用追加模式。为此你需要一个不同的PdfStamper.createSignature重载与另一个参数,一个boolean,你设置为true

原因是通常(即不激活附加模式)iText重新组织内部PDF结构并丢弃未使用的对象。在已签名的PDF中,这通常会移动(已存在的)签名的位置,该签名使签名结构无效。但是,使用追加模式,iText保持原始PDF字节不变,只附加新数据。

Changing hashes

我只是混淆了为什么字节“sh”的值每次都在同一个文件的变化?实际上,我想存储文件的哈希值。是因为“cal”?

实际上,每次开始操作PDF时,结果都会获得一个新的唯一ID。此外,存储修改时间。在签名用例的情况下,签名时间也不同。

我可以一次获取哈希并将其存储在db中并稍后对哈希进行签名并附加到pdf中吗?

你要么必须

  • 保持压模和外观打开的时间,或者你必须
  • 保留准备好的文件,或者你必须
  • 修补iText,允许您为签名时间,操作时间和ID设置固定值。

第一种选择对我来说是不可能的。我没有获得第二个第三选项。你能详细说明或提供我可以参考的任何参考资料吗?

Patching iText to enforce fixed hashes for each file

好的,首先关闭第三个选项,修补iText,通常是你不想做的事情,因为它使后续的iText更新变得困难。

OpenPdf(一个较旧的iText分支)包含一个补丁,将EnforcedModificationDateOverrideFileIdIncludeFileID属性添加到PdfStamper。 (PdfSignatureAppearance已经拥有SignDate属性。)此补丁已应用于允许eSignature DSS在其签名过程中使用OpenPdf(其中还包括创建签名的PDF两次,因此需要固定的散列值)。

您可能不想切换到这个旧的iText分支,因为它错过了许多修复和新的iText版本的新选项。

Keeping the already prepared file

因此,您可能应该使用空签名保留最初创建的文件,例如在某个临时文件夹中,一旦检索到最终签名,就应用延迟签名。

这基本上是iText示例C4_09_DeferredSigning的全部内容,它首先创建一个包含所有内容的中间PDF,只丢失signatrue字节:

public void emptySignature(String src, String dest, String fieldname, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
    PdfReader reader = new PdfReader(src);
    FileOutputStream os = new FileOutputStream(dest);
    PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
    PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
    appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, fieldname);
    appearance.setCertificate(chain[0]);
    ExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
    MakeSignature.signExternalContainer(appearance, external, 8192);
}

仅在第二步中注入实际签名:

public void createSignature(String src, String dest, String fieldname, PrivateKey pk, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {

    PdfReader reader = new PdfReader(src);
    FileOutputStream os = new FileOutputStream(dest);
    ExternalSignatureContainer external = new MyExternalSignatureContainer(pk, chain);
    MakeSignature.signDeferred(reader, fieldname, os, external);
}

运用

class MyExternalSignatureContainer implements ExternalSignatureContainer {

    protected PrivateKey pk;
    protected Certificate[] chain;

    public MyExternalSignatureContainer(PrivateKey pk, Certificate[] chain) {
        this.pk = pk;
        this.chain = chain;
    }

    public byte[] sign(InputStream is) throws GeneralSecurityException {
        try {
            PrivateKeySignature signature = new PrivateKeySignature(pk, "SHA256", "BC");
            String hashAlgorithm = signature.getHashAlgorithm();
            BouncyCastleDigest digest = new BouncyCastleDigest();
            PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
            byte hash[] = DigestAlgorithms.digest(is, digest.getMessageDigest(hashAlgorithm));
            Calendar cal = Calendar.getInstance();
            byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
            byte[] extSignature = signature.sign(sh);
            sgn.setExternalDigest(extSignature, null, signature.getEncryptionAlgorithm());
            return sgn.getEncodedPKCS7(hash, cal, null, null, null, CryptoStandard.CMS);
        }
        catch (IOException ioe) {
            throw new ExceptionConverter(ioe);
        }
    }

    public void modifySigningDictionary(PdfDictionary signDic) {
    }

}

这里的has是在第二步中计算出来的,但你不需要这样做,你可以

  • 已经在第一步计算它不使用上面的ExternalBlankSignatureContainer中的原始emptySignature(),而是扩展它以计算MyExternalSignatureContainer所做的哈希值,
  • 将此值存储在数据库中,以及
  • (只要你检索签名)使用MyExternalSignatureContainer的变体注入该签名,该变量不计算哈希值,而是精确地注入返回的签名。
© www.soinside.com 2019 - 2024. All rights reserved.