我正在实现一个应用程序,以便在服务器中对PDF文件进行签名,并使用以下场景(使历史变长,变短):
我正在使用PDFBox 2.0.15,并使用新的功能保存增量ForExteralSigning
,如下面的代码所示:
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”语句中,添加签名,这是通过发布请求(这就是ParametersHandle.decompressParamFromBase64
所做的)到文档中的。所以在这次尝试中,我对这个方法有两个发布请求。
第二种方法是用一种方法处理每个post请求,所以我有第二个代码块:
// 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!");
问题是:在AdobeReader上验证文档时出现错误“自从应用签名以来,文档已被修改或损坏”。据我所知,这不应该发生,因为签名字节范围的偏移量在第二次post调用中被记住。
感谢任何帮助或想法,
提前谢谢你。
[编辑]
有关已使用文件的完整列表:https://drive.google.com/drive/folders/1S9a88lCGaQYujlEyCrhyzqvmWB-68LR3
[编辑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
示例,替换了符号
方法,用于调用返回我签名的服务。
多亏了Tilman Hausherr,我才知道发生了什么:
1-我有一个与SmatCards等通信的桌面应用程序,它是签名者。为了与服务器通信(通过网页),我们使用WebSocket。我已经编写了自己的websocket服务器类,这就是为什么它只准备使用65k字节。比我试图在这里发送数据时:
ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fos);
byte[] cmsSignature = sign(externalSigning.getContent());
我在应用程序中出错了。
2-Tilman建议我看看这个@mkl答案,他做了同样的事情:创建外部签名的SHA256哈希。getContent()
并发送到另一个地方进行签名。我不知道为什么,但唯一不适合我的是:
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();
所以,再次感谢社区!!!
Client=My application,Server=MSSP(移动签名服务提供商) 服务器仅对哈希值进行签名。 要签名数据: *Base64编码的SHA-384待签名数据摘要。(64个字符) *Base64编码的SHA-512待签名数据摘要。(88个字符) *要签名的Base64编码的DER编码的PKCS#1摘要信息。 注意:我使用MSSP(移动签名服务提供商)架构。对于SHA256算法,D
签署修改的问题。 Iam使用DSC令牌传递文档哈希和签名哈希(外部签名)。 Iam收到错误,例如:“文档自签名以来已被更改或损坏”获取文档哈希:- 外部签名代码:- 签名附加代码:- 此签名附加后,但在打开签名的PDF时会出现如下图所示的错误。 PDF文件链接:https://drive.google.com/file/d/1qRT2CVgET8Ds1fu0b5psii3j8ytPKaLH/vie
需要通过使用外部webservice对文档哈希进行签名来签署PDF,该过程必须在2个步骤中完成,并使用临时空签名。 在Priyanka问题和Grazina问题之后,阅读了那些帖子上的mkl答案,我现在有一个无效的签名,即使像Grazina那样添加了哈希前缀。 iTextSharp版本:5.5.13.1 这个节目是我上一个问题的另一个问题。当前代码(编译并开始调用SignPDF方法): 获得的结果
首先,虽然我关注StackOverflow已经有相当一段时间了,但这是我第一次发布一些东西,所以如果我做错了或者不按规则做的话,请随时为我指出正确的方向。 我正在开发一个PDF数字签名应用程序,使用iText5,它依赖于一个外部服务,在我准备好PDF签名后提供一个签名哈希。 如iText文档中所述,在第一阶段,我准备了PDF(在最终实现中,所有PDF都可能是多签名的,因此我使用追加模式),如下所示
第一个选项是不可行的,因为PDF的签名没有时间戳(这是一个非常重要的必要条件),所以选择了第二个选项。 这是我们的代码,首先,我们得到签名外观,并计算散列: 在这一点上,我们有了文档的哈希代码。然后我们将散列发送到webservice,我们得到签名的散列代码。 按照mkl的建议,我遵循了这本书的4.3.3部分PDF文档的数字签名,现在我的代码如下: 第一部分,当我们计算散列时: 在第二部分,我们得
我已经玩弄了一段时间的iTextSharp 5.5.7,找不到正确的方法从智能卡为PDF制作一个有效的数字签名-Adobe Reader总是说它的签名人和未知,并且不能解码签名的DER数据。 我查看了Makesignature.cs代码以供参考,以及is的功能: 然后,根据iExternalSignature.cs中的“Sign”方法 “@param message要进行散列和签名的邮件” 所以我