我正在尝试构建一个发生以下情况的应用程序:
- 客户端向服务器请求PDF哈希
- 服务器生成PDF文件的散列并将其发送给客户端
- 客户端用他的私钥对此哈希进行签名,并将签名的哈希与他自己证书的公共部分一起发送
- 服务器生成一个新的签名PDF文件
我遇到的问题是:如果事先没有客户端的证书,服务器似乎不可能生成要签名的哈希。我真的更喜欢创建一个解决方案,其中服务器不需要知道客户端的证书来创建文档摘要。
到目前为止,我发现的所有示例都使用PdfPKCS7getAuthenticatedAttributeBytes函数来获取要签名的哈希,但这需要知道客户端证书。我看过Bruno Lowagie的"PDF文档的数字签名"白皮书,但我没能确切地了解哪些信息被消化了。
以下是我当前尝试的代码片段:
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, ' ', 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);
}
不幸的是,该哈希似乎不正确,对该哈希进行签名并基于签名哈希生成签名文档会导致签名无效。
如果有人能帮助我改进这个代码片段,或者以其他方式让我深入了解签名所需的数据,我将不胜感激。
您似乎忽略了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, ' ');
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);
}
itext7中存在类
com.itextpdf.signatures.PdfPKCS7
在这个类别中,有2个感兴趣的成员
Collection<Certificate> signCerts;
private X509Certificate signCert;
这些都是私有的,如果我们将它们公开或为这些变量设置一个setter方法,那么就很容易了。
我在下面做了
使用伪证书创建了PdfPKCS7对象,并检索了getAuthenticatedAttributeBytes()以获取tbs字节。这是我寄来的原始叹息。
在我收到原始签名字节后,我将这两个变量设置为我从签名者应用程序或服务收到的实际最终证书,然后它就工作了
现在,我创建了新的PdfPKCS7类的精确副本,并公开了这两个变量。