【发布时间】:2019-11-13 23:41:19
【问题描述】:
我正在实现一个应用程序来在服务器中签署 PDF 文件,具有以下场景(为了使历史悠久,简短):
- 客户端开始向服务器发送签名、日期/时间和水印
- 服务器将签名字典添加到文件中并发送要签名的数据
- 客户端签到内容
- 服务器完成签名
我正在使用 PDFBox 2.0.15,并使用新功能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;
}
在“IF”语句中,我正在生成要签名的数据。在“ELSE”语句中,将签名通过 post 请求(这就是 ParametersHandle.decompressParamFromBase64 所做的)添加到文档中。所以我在这次尝试中对这个方法有两个帖子请求。
第二种方法是在一个方法中执行每个发布请求,所以我有第二个代码块:
// remember the offset (add 1 because of "<")
int offset = Integer.valueOf(postparams.get("offset"));
this.log("[PDF BOX] Retrieving offset of bytes range for this signature. The value is: "
+ String.valueOf(offset));
File signedPDF = new File(workingDir.getAbsolutePath() + sep + name + "_Signed.pdf");
this.log("[SIGNATURE] Reloading document for apply signature: " + signedPDF.getAbsolutePath());
// invoke external signature service
String signature64 = postparams.get("sign_disgest");
byte[] cmsSignature = ParametersHandle.decompressParamFromBase64(signature64);
this.log("[SIGNATURE] Got signature byte array from APP.");
// set signature bytes received from the service
// now write the signature at the correct offset without any PDFBox methods
this.log("[SIGNATURE] Writing signed document...");
RandomAccessFile raf = new RandomAccessFile(signedPDF, "rw");
raf.seek(offset);
raf.write(Hex.getBytes(cmsSignature));
raf.close();
this.log("[SIGNATURE] New signed document has been saved!");
问题是:我在 Adobe Reader 上验证文档时收到错误消息“自应用签名以来文档已被更改或损坏”。 据我了解,它不应该发生,因为签名字节范围的偏移量在第二次调用后会被记住。
感谢任何帮助或想法,
提前谢谢你。
[编辑]
使用文件的完整列表:https://drive.google.com/drive/folders/1S9a88lCGaQYujlEyCrhyzqvmWB-68LR3
[编辑 2]
根据@mkl 评论,这里是签名的方法:
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 示例,将sign 方法替换为调用此服务的方法,该服务返回我的签名,它可以工作。
【问题讨论】:
-
请添加日志输出和签名的PDF文件。这一切起初看起来有点令人困惑,但在我查看示例之后,您似乎确实阅读并应用了它:-)
-
请分享最终签名的文件和第一步的文件结果。此外,您是否测试了原始 PDFBox 签名代码,仅将签名容器创建替换为对您的服务的调用?可能该签名容器存在问题,而不是您在上面共享的代码。
-
更多可疑之处:日志中“hash”的长度是4998,但应该是55000左右。根据NOTEPAD++,PDF中签名中非零部分的长度为11962。除以 2 大约为 6000。但在日志中长度为 5140。
-
@Tilmanhausherr 感谢您昨天的所有帮助,它帮助我过滤了问题,现在我想我已经找到了。正如我告诉你的,我正在使用 websocket 在服务器和客户端之间传输数据。这是提示:当我开始签名过程时,我有 60662 个字节。将其压缩并编码为 base64 后,我有 72548 个字节(我知道这更大,但如果我将 60k 编码为 base 64,它会变得更大)。问题是:我的 websocket 类已准备好处理 65k 字节哈哈。所以现在我正在研究它,稍后可以发布结果。
-
如果大数据传输有问题,则只传输哈希,并使用此代码:stackoverflow.com/questions/41767351
标签: java pdf digital-signature pdfbox