Java 带有PDFBox 2.0.15的外部签名
我正在实现一个应用程序,以便在服务器中对PDF文件进行签名,使用以下场景(以创建长历史,短历史):Java 带有PDFBox 2.0.15的外部签名,java,pdf,digital-signature,pdfbox,Java,Pdf,Digital Signature,Pdfbox,我正在实现一个应用程序,以便在服务器中对PDF文件进行签名,使用以下场景(以创建长历史,短历史): 客户端开始向服务器发送签名、日期/时间和水印 服务器将签名字典添加到文件中并发送要签名的数据 客户端签名内容 服务器完成签名 我正在使用PDFBox 2.0.15,并使用新功能saveIncrementalForExternalSigning,如下代码所示: try { String name = document.getID(); File signedFile
saveIncrementalForExternalSigning
,如下代码所示:
try {
String name = document.getID();
File signedFile = new File(workingDir.getAbsolutePath() + sep + name + "_Signed.pdf");
this.log("[SIGNATURE] Creating signed version of the document");
if (signedFile.exists()) {
signedFile.delete();
}
FileOutputStream tbsFos = new FileOutputStream(signedFile);
ExternalSigningSupport externalSigning = pdfdoc.saveIncrementalForExternalSigning(tbsFos);
byte[] content = readExternalSignatureContent(externalSigning);
if (postparams.get("action").equalsIgnoreCase("calc_hash")) {
this.log("[SIGNATURE] Calculating hash of the document");
String strBase64 = ParametersHandle.compressParamBase64(content);
// this saves the file with a 0 signature
externalSigning.setSignature(new byte[0]);
// remember the offset (add 1 because of "<")
int offset = signature.getByteRange()[1] + 1;
this.log("[SIGNATURE] Sending calculated hash to APP");
return new String[] { strBase64, processID, String.valueOf(offset) };
} else {
this.log("[SIGNATURE] Signature received from APP");
String signature64 = postparams.get("sign_disgest");
byte[] cmsSignature = ParametersHandle.decompressParamFromBase64(signature64);
this.log("[SIGNATURE] Setting signature to document");
externalSigning.setSignature(cmsSignature);
pdfdoc.close();
IOUtils.closeQuietly(signatureOptions);
this.log("[DOXIS] Creating new version of document on Doxis");
createNewVersionOfDocument(doxisServer, documentServer, doxisSession, document, signedFile);
return new String[] { "SIGNOK" };
}
} catch (IOException ex) {
this.log("[SAVE FOR SIGN] " + ex);
return null;
}
问题是:在AdobeReader上验证文档时出现错误“自从应用签名以来,文档已被更改或损坏”。
据我所知,这不应该发生,因为签名字节范围的偏移量在第二次post调用中被记住
感谢您的任何帮助或想法
先谢谢你
[编辑]
有关已使用文件的完整列表:
[编辑2]
根据@mkl comment,以下是进行签名的方法:
public byte[] sign(byte[] hash)
throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
PrivateKey privKey = (PrivateKey) windowsCertRep.getPrivateKey(this.selected_alias, "");
X509Certificate[] certificateChain = windowsCertRep.getCertificateChain(this.selected_alias);
try
{
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
X509Certificate cert = (X509Certificate) certificateChain[0];
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privKey);
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(sha1Signer, cert));
gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));
CMSProcessableInputStream msg = new CMSProcessableInputStream(new ByteArrayInputStream(hash));
CMSSignedData signedData = gen.generate(msg, false);
return signedData.getEncoded();
}
catch (GeneralSecurityException e)
{
throw new IOException(e);
}
catch (CMSException e)
{
throw new IOException(e);
}
catch (OperatorCreationException e)
{
throw new IOException(e);
}
}
我已经测试了CreateVisibleSignature2
examaple,将sign
方法替换为调用此服务并返回签名的方法,即它可以正常工作。多亏了我能够弄清楚发生了什么:
1-我有一个与SmatCards等通信的桌面应用程序,它是签名者。为了与服务器通信(通过网页),我们使用WebSocket。我已经编写了自己的websocket服务器类,这就是为什么它只准备使用65k字节。比我试图在这里发送数据时:
ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fos);
byte[] cmsSignature = sign(externalSigning.getContent());
我在应用程序中出错
2-Tilman建议我看看这个,他做了同样的事情:创建externalSigning.getContent()
的SHA256散列,然后发送到另一个地方进行签名。我不知道为什么,但唯一对我不起作用的是:
gen.addSignerInfoGenerator(builder.build(
new BcRSAContentSignerBuilder(sha256withRSA,
new DefaultDigestAlgorithmIdentifierFinder().find(sha256withRSA))
.build(PrivateKeyFactory.createKey(pk.getEncoded())),
new JcaX509CertificateHolder(cert)));
因此,我将此块替换为:
ContentSigner sha256Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privKey);
然后,我的完整签名方法如下:
PrivateKey privKey = (PrivateKey) windowsCertRep.getPrivateKey(this.selected_alias, "changeit");
X509Certificate[] certificateChain = windowsCertRep.getCertificateChain(this.selected_alias);
List<X509Certificate> certList = Arrays.asList(certificateChain);
JcaCertStore certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
Attribute attr = new Attribute(CMSAttributes.messageDigest,
new DERSet(new DEROctetString(hash)));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(attr);
SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
AlgorithmIdentifier sha256withRSA = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256withRSA");
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(certificateChain[0].getEncoded());
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);
ContentSigner sha256Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privKey);
gen.addSignerInfoGenerator(builder.build(sha256Signer, new JcaX509CertificateHolder(cert)));
gen.addCertificates(certs);
CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
return s.getEncoded();
PrivateKey privKey=(PrivateKey)windowsCertRep.getPrivateKey(此.selected_别名“changeit”);
X509Certificate[]CertificateCain=windowsCertRep.GetCertificateCain(此.selected_别名);
List certList=Arrays.asList(certificateChain);
JcaCertStore certs=新的JcaCertStore(certList);
CMSSignedDataGenerator gen=新的CMSSignedDataGenerator();
Attribute attr=新属性(CMSAttributes.messageDigest,
新的DERSet(新的DEROctetString(hash));
ASN1EncodableVector v=新的ASN1EncodableVector();
v、 添加(attr);
SignerInfoGeneratorBuilder=new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
.setSignedAttributeGenerator(新的DefaultSignedAttributeTableGenerator(新的AttributeTable(v)));
算法标识符sha256withRSA=new DefaultSignatureAlgorithmIdentifierFinder().find(“sha256withRSA”);
CertificateFactory=CertificateFactory.getInstance(“X.509”);
InputStream in=new ByteArrayInputStream(certificateChain[0].getEncoded());
X509Certificate cert=(X509Certificate)certFactory.generateCertificate(in);
ContentSigner sha256Signer=新的JcaContentSignerBuilder(“SHA256WithRSA”).build(privKey);
gen.addSignerInfoGenerator(builder.build(sha256Signer,新JcaX509CertificateHolder(cert));
一般证书(证书);
CMSSignedData s=gen.generate(新的CMSAbsentContent(),false);
返回s.getEncoded();
所以,再次感谢你们社区 请添加日志输出和签名的PDF文件。所有这些一开始看起来有点混乱,但在我看了这个示例之后,似乎您确实阅读并应用了:-)请不仅共享最终签名的文件,还共享第一步的文件结果。此外,您是否测试了原始的PDFBox签名代码,仅用对您的服务的调用替换了签名容器的创建?这个签名容器可能有问题,而不是你上面共享的代码。更可疑的是:日志中的“哈希”长度是4998,但应该是55000左右。根据NOTEPAD++,PDF中签名的非零部分的长度为11962。除以2,大约是6000。但是日志的长度是5140。@Tilmanhausher谢谢你昨天的帮助,它帮助我过滤了这个问题,现在我想我已经找到了。正如我告诉你们的,我正在使用websocket在服务器和客户端之间传输数据。这里有一个提示:当我开始签名过程时,我有60662字节。在压缩它并编码到base64之后,我有72548个字节(我知道它更大,但如果我将60k编码到base64,它将变得更大)。问题是:我的websocket类已经准备好处理65k字节了,哈哈。所以现在我正在研究它,可以稍后发布结果。如果传输大数据有问题,那么只传输散列,并使用以下代码:
PrivateKey privKey = (PrivateKey) windowsCertRep.getPrivateKey(this.selected_alias, "changeit");
X509Certificate[] certificateChain = windowsCertRep.getCertificateChain(this.selected_alias);
List<X509Certificate> certList = Arrays.asList(certificateChain);
JcaCertStore certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
Attribute attr = new Attribute(CMSAttributes.messageDigest,
new DERSet(new DEROctetString(hash)));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(attr);
SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
AlgorithmIdentifier sha256withRSA = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256withRSA");
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(certificateChain[0].getEncoded());
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);
ContentSigner sha256Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privKey);
gen.addSignerInfoGenerator(builder.build(sha256Signer, new JcaX509CertificateHolder(cert)));
gen.addCertificates(certs);
CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
return s.getEncoded();