iText - 生成没有证书链的PDF哈希

时间:2015-03-23 12:40:03

标签: pdf hash certificate itext digital-signature

我正在尝试构建一个发生以下情况的应用程序:

  1. 客户端从服务器请求PDF哈希。
  2. 服务器生成PDF文件的哈希并将其发送给客户端。
  3. 客户端使用其私钥对此哈希进行签名,并将签名的哈希与其自己的证书的公共部分一起发送。
  4. 服务器生成一个新的签名PDF文件。
  5. 我遇到的问题是:服务器似乎不可能在事先没有客户端证书的情况下生成待签名的哈希值。我真的更喜欢创建一个解决方案,其中服务器不需要知道客户端的证书就可以创建文档摘要。

    到目前为止我找到的所有示例都使用PdfPKCS7.getAuthenticatedAttributeBytes函数来获取待签名的哈希值,但这需要知道客户端证书。我查看过" PDF文档的数字签名" Bruno Lowagie撰写的白皮书,但我没有看到确切消化的信息。

    这是我当前尝试的代码段:

    public byte[] simplePresign(String src, String digestAlgorithm) throws IOException, DocumentException, GeneralSecurityException {
        this.digestAlgorithm = digestAlgorithm;
        tsaClient = new CustomTSAClient();
    
        PdfReader reader = new PdfReader(src);
        os = new ByteArrayOutputStream();
        PdfAStamper stamper = PdfAStamper.createSignature(reader, os, '\0', PdfAConformanceLevel.PDF_A_1B);
        appearance = stamper.getSignatureAppearance();
    
        PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
        appearance.setCryptoDictionary(dic);
    
        HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
        exc.put(PdfName.CONTENTS, getEstimatedSize(null, tsaClient) * 2 + 2);
        appearance.preClose(exc);
    
        InputStream data = appearance.getRangeStream();
        MessageDigest mDigest = DigestAlgorithms.getMessageDigest(digestAlgorithm, null);
    
        return DigestAlgorithms.digest(data, mDigest);
    
    }
    

    不幸的是,这个哈希似乎不正确,签署此哈希并根据签名哈希生成签名文档会导致签名无效。

    如果有人可以帮助我改进此代码段,或者让我对需要消化签名的数据有所了解,我将不胜感激。

1 个答案:

答案 0 :(得分:5)

您似乎忽略了DeferredSigning示例。

在此示例中,我们首先创建一个带有空签名的PDF:

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

当然,在此示例中,公共证书chain[0]将传递给appearance,它用于创建可视外观并创建PdfPKCS7对象。

获得带有空签名的PDF后,您可以在服务器上创建PdfSignatureAppearance并获取可以发送给客户端进行签名的哈希值。这可以使用getRangeStream()方法来获取需要散列的PDF字节范围。此方法返回InputStream,可以像这样使用:

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

现在您可以将此sh发送给客户进行签名。您将收到另一个byte[],这是需要添加到PDF的实际签名,假设byte[]被称为sig

您的外部签名容器可以保持非常简单:它只需要返回签名字节:

class MyExternalSignatureContainer implements ExternalSignatureContainer {
    protected byte[] sig;
    public MyExternalSignatureContainer(byte[] sig) {
        this.sig = sig;
    }
    public byte[] sign(InputStream is) throws Exception {
        return sig;
    }
    public void modifySigningDictionary(PdfDictionary signDic) {
    }
}

您现在可以在服务器上使用createSignature()方法:

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(sig);
    MakeSignature.signDeferred(reader, fieldname, os, external);
}