当前位置: 首页 > 知识库问答 >
问题:

使用java和iText签名PDF哈希

叶允晨
2023-03-14

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

我们没有用于签署文档的证书,因为它们在HSM中,而我们可以使用证书的唯一方法是使用WebService。

    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;

这是我们的代码,首先,我们得到签名外观,并计算散列

    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称“文档自签署以来已被更改或损坏”。

我已经通过使用外部服务和iText,PDF签名iText pkcs7多签名和是否可能签署一个PDF文档与哈希和签名哈希?但没有运气。

共有1个答案

华坚成
2023-03-14

您在问题的注释中共享了由代码签名的示例文件。

对该文件的分析(使用Analyesignatures testTestPriyanKasignaturesAmpleInformedConsent_Signed执行)显示,实际签名的哈希是

1134ED96F42AC7352E546BE0E906C0BF5D44229AEAFAC39145DB40A0BB7E817B

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

E7101D9770ABF5E58D43670AAC6E9418265AE80F74B3BDFB67911C0CC5D5D949.
D7917CF3BA9FE5D5B626ED825965D699F7C54DBBF9F18DECE18EF8DD36DC4C28,

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

最终证明

由于解码的哈希值错误,web service上的编码实用程序不同,因此不需要MessageDigest(sh=MessageDigest.GetInstance(“sha256”,“bc”).digest(sh);)。这一行给出了错误的散列。

此后出现了一个新问题:

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

要签名已经签名的PDF,必须使用追加模式。为此,您需要一个不同的pdfstamper.createSignatury重载,其中需要多一个参数,即boolean,您将其设置为true

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

我只是很困惑,为什么字节“sh”的值每次都对同一个文件进行更改?实际上,我想存储文件的哈希值。是因为“卡尔”吗?

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

你要么必须

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

第一个选项对我来说是不可能的。我没有得到第二个和第三个选项。你能详细说明这两件事吗?或者给出我能参考的任何资料吗?

OpenPdf(较早的iText分叉)包含一个修补程序,将EnforcedModificationDateOverrideFileIDIncludeFileID属性添加到PDFstamper。(PDFSignatureOutcome已具有SignDate属性。)已应用此修补程序允许eSignature DSS在其签名过程中使用OpenPdf(也包括创建两次签名PDF,因此需要固定的哈希值)。

您可能不想切换到这个旧的iText分叉,因为它缺少更新的iText版本的许多修补程序和新选项。

因此,您可能应该保留原始创建的带有空签名的文件(例如,在某个临时文件夹中),并在检索到最终签名后立即应用延迟签名。

这就是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) {
    }
    
}
    null
 类似资料:
  • 遵循http://itextpdf.com/book/digitalsignatures中的“使用智能卡和PKCS#11签名文档”主题并创建与所提供的代码示例类似的代码示例后,签名的文件签名在Adobe Reader中无效,签名外观具有不可否认证书的名称(即eID所有者的名称),但在Adobe Reader的签名面板中显示: 我使用的是金雅拓PinPad和葡萄牙语eID与eID中间件软件一起安装,

  • 第一个选项是不可行的,因为PDF的签名没有时间戳(这是一个非常重要的必要条件),所以选择了第二个选项。 这是我们的代码,首先,我们得到签名外观,并计算散列: 在这一点上,我们有了文档的哈希代码。然后我们将散列发送到webservice,我们得到签名的散列代码。 按照mkl的建议,我遵循了这本书的4.3.3部分PDF文档的数字签名,现在我的代码如下: 第一部分,当我们计算散列时: 在第二部分,我们得

  • 我正在编写一个Java web服务,它使用来自网络中一些客户端的iText对PDF文档进行签名。文档的签名正确,可以使用外部工具进行验证。然而,由于一些法律限制,为了将此文档存储在一个正式的文档存储库中,我必须提供来自签名的散列/摘要消息。 我已经尝试了几乎所有的方法来获得那个散列,但我能得到的最接近的方法是用下面的代码段以字符串的形式获得整个签名(证书+散列/摘要+时间戳)(请原谅字符串和[1]

  • 我正在ASP.NET应用程序中使用iText7对PDF进行数字签名。我用的是vb,但我也能找到一个C#答案。我们使用GlobalSign DSS以使签名不作为未知(AATL)出现。 我从我们公司的SSL证书创建了一个pfx文件,我可以使用该文件对PDF文件进行签名,但我不明白如何使用从GlobalSign获得的东西来完成同样的事情。此服务要求发送一个十六进制编码的SHA256摘要,作为返回,我接收

  • 首先,虽然我关注StackOverflow已经有相当一段时间了,但这是我第一次发布一些东西,所以如果我做错了或者不按规则做的话,请随时为我指出正确的方向。 我正在开发一个PDF数字签名应用程序,使用iText5,它依赖于一个外部服务,在我准备好PDF签名后提供一个签名哈希。 如iText文档中所述,在第一阶段,我准备了PDF(在最终实现中,所有PDF都可能是多签名的,因此我使用追加模式),如下所示

  • 问题内容: 我正在编写一个Java网络服务,该网络服务从网络中的某些客户端使用iText对PDF文档进行签名。文档已正确签名,可以使用外部工具进行验证。但是,由于某些法律限制,为了将此文档存储在正式的文档库中,我必须提供签名中的哈希/摘要消息。 我已经尝试了几乎所有方法来获取该哈希,但是我可以获取的最接近的方法是使用此代码段将整个签名(CERT + HASH / DIGEST + TIMESTAM