我正在尝试验证Java中经过数字签名的PDF文档。
我使用Apache PDFBox 2.0.6获取签名和已签名的原始PDF,然后使用Bouncy
Castle验证分离的签名(计算原始文件的哈希,使用签名者的公钥验证签名并进行比较结果)。
我阅读了这篇文章,并尝试使用以下代码获取签名字节和原始PDF字节:
PDDocument doc = PDDocument.load(signedPDF);
byte[] origPDF = doc.getSignatureDictionaries().get(0).getSignedContent(signedPDF);
byte[] signature = doc.getSignatureDictionaries().get(0).getContents(signedPDF);
但是,当我将origPDF保存到文件中时,我注意到它仍然具有签名原始PDF所没有的签名字段。另外,保存origPDF的大小为21
kb,而原始PDF的大小为15 kb。那可能是因为签名字段。
但是,当我尝试像这样从origPDF中剥离签名字段时:
public byte[] stripCryptoSig(byte[] signedPDF) throws IOException {
PDDocument pdDoc = PDDocument.load(signedPDF);
PDDocumentCatalog catalog = pdDoc.getDocumentCatalog();
PDAcroForm form = catalog.getAcroForm();
List<PDField> acroFormFields = form.getFields();
for (PDField field: acroFormFields) {
if (field.getFieldType().equalsIgnoreCase("Sig")) {
System.out.println("START removing Sign Flags");
field.setReadOnly(true);
field.setRequired(false);
field.setNoExport(true);
System.out.println("END removing Sign Flags");
/*System.out.println("START flattenning field");
field.getAcroForm().flatten();
field.getAcroForm().refreshAppearances();
System.out.println("END flattenning field");
*/
field.getAcroForm().refreshAppearances();
}
}
我收到以下警告:
警告:无效的字典,找到:’[‘,但预期:’/’在偏移15756
警告:签名字段的外观生成尚未实现-您需要手动生成/更新
而且,当我在Acrobat中打开PDF时,签名字段消失了,但是我看到了签名的图像,该签名曾经是PDF页面的一部分。这很奇怪,因为我以为我使用byte
[]完全删除了签名origPDF = doc.getSignatureDictionaries().get(0).getSignedContent(signedPDF);
顺便说一句,我在origPDF上调用了stripCryptoSig(byte [] signedPDF)函数,所以这不是一个错误。
当我尝试使用充气城堡验证签名时,出现以下消息异常: message-digest属性值与计算值不匹配
我猜这是因为签名的原始PDF和我从PDFBox使用的PDF
doc.getSignatureDictionaries().get(0).getSignedContent(signedPDF);
不相同。
这是我的充气城堡验证码:
private SignatureInfo verifySig(byte[] signedData, boolean attached) throws OperatorCreationException, CertificateException, CMSException, IOException {
SignatureInfo signatureInfo = new SignatureInfo();
CMSSignedData cmsSignedData;
if (attached) {
cmsSignedData = new CMSSignedData(signedData);
}
else {
PDFUtils pdfUtils = new PDFUtils();
pdfUtils.init(signedData);
signedData = pdfUtils.getSignature(signedData);
byte[] sig = pdfUtils.getSignedContent(signedData);
cmsSignedData = new CMSSignedData(new CMSProcessableByteArray(signedData), sig);
}
SignerInformationStore sis = cmsSignedData.getSignerInfos();
Collection signers = sis.getSigners();
Store certStore = cmsSignedData.getCertificates();
Iterator it = signers.iterator();
signatureInfo.setValid(false);
while (it.hasNext()) {
SignerInformation signer = (SignerInformation) it.next();
Collection certCollection = certStore.getMatches(signer.getSID());
Iterator certIt = certCollection.iterator();
X509CertificateHolder cert = (X509CertificateHolder) certIt.next();
if(signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert))){
signatureInfo.setValid(true);
if (attached) {
CMSProcessableByteArray userData = (CMSProcessableByteArray) cmsSignedData.getSignedContent();
signatureInfo.setSignedDoc((byte[]) userData.getContent());
}
else {
signatureInfo.setSignedDoc(signedData);
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String signedOnDate = "null";
String validFromDate = "null";
String validToDate = "null";
Date signedOn = this.getSignatureDate(signer);
Date validFrom = cert.getNotBefore();
Date validTo = cert.getNotAfter();
if(signedOn != null) {
signedOnDate = sdf.format(signedOn);
}
if(validFrom != null) {
validFromDate = sdf.format(validFrom);
}
if(validTo != null) {
validToDate = sdf.format(validTo);
}
DefaultAlgorithmNameFinder algNameFinder = new DefaultAlgorithmNameFinder();
signatureInfo.setSignedBy(IETFUtils.valueToString(cert.getSubject().getRDNs(BCStyle.CN)[0].getFirst().getValue()));
signatureInfo.setSignedOn(signedOn);
signatureInfo.setIssuer(IETFUtils.valueToString(cert.getIssuer().getRDNs(BCStyle.CN)[0].getFirst().getValue()));
signatureInfo.setValidFrom(validFrom);
signatureInfo.setValidTo(validTo);
signatureInfo.setVersion(String.valueOf(cert.getVersion()));
signatureInfo.setSignatureAlg(algNameFinder.getAlgorithmName(signer.getDigestAlgorithmID()) + " WTIH " + algNameFinder.getAlgorithmName(cert.getSubjectPublicKeyInfo().getAlgorithmId()));
/*signatureInfo.put("Signed by", IETFUtils.valueToString(cert.getSubject().getRDNs(BCStyle.CN)[0].getFirst().getValue()));
signatureInfo.put("Signed on", signedOnDate);
signatureInfo.put("Issuer", IETFUtils.valueToString(cert.getIssuer().getRDNs(BCStyle.CN)[0].getFirst().getValue()));
signatureInfo.put("Valid from", validFromDate);
signatureInfo.put("Valid to", validToDate);
signatureInfo.put("Version", "V" + String.valueOf(cert.getVersion()));
signatureInfo.put("Signature algorithm", algNameFinder.getAlgorithmName(signer.getDigestAlgorithmID()) + " WTIH " + algNameFinder.getAlgorithmName(cert.getSubjectPublicKeyInfo().getAlgorithmId()));*/
break;
}
}
return signatureInfo;
}
在我的情况下,我设置签名和signedData的代码中有错误。我不小心交换了这些值。
因此,代替:
signedData = pdfUtils.getSignature(signedData);
byte[] sig = pdfUtils.getSignedContent(signedData);
它应该是:
byte[] sig = pdfUtils.getSignature(signedData);
signedData = pdfUtils.getSignedContent(signedData);
现在,它正在工作。我用来测试的文件已使用签名adbe.pkcs7.detached
。但是,如果使用其他签名方法,将无法正常工作。
因此,感谢@Tilman Hausherr向我指出ShowSignature.java示例。这就是签名验证的方式。
并且,也感谢@mkl的详细说明。
我现在知道,当创建签名时,将添加签名字段,并根据该新值计算哈希值。这就是验证有效的原因。您不需要没有签名字段的原始PDF。
我正在尝试验证Java中的数字签名PDF文档。 我得到以下警告: 警告:无效字典,在偏移量15756处找到:“[”,但应为:“/” 警告:尚未实现签名字段的外观生成-您需要手动生成/更新 当我尝试使用bouncy castle验证签名时,我得到一个消息异常:message-digest属性值不匹配计算值 我想这是因为签名的原始PDF和我使用从PDFBox中获得的PDF不一样。 这里是我的bounc
我试图找到用pdfbox版本2签署pdf的例子。x、 在bouncycastle中,我看到的只是pdfbox版本1.8.9 https://github.com/mkl-public/testarea-pdfbox1/blob/master/src/main/java/mkl/testarea/pdfbox1/sign/CreateSignature.java 这适用于pdfbox 1.8.9,但
这是接口签名 这是类签名者 我用java和bouncycastle创建了证书和密钥对,我现在不知道是问题还是我做错了什么?
我正在尝试使用pdfbox库签署pdf。我现在卡住了,真的需要帮助。 这是我的代码: 然后我正在保存我的pdf,但是:1)我注意到sign method从来没有被调用2)我应该在哪里附加CertyFicate?在sign method中? 以下是我保存PDF的方法:
我应该如何更改A方法?任何想法都是好的。提前谢谢。
问题内容: 我已经测试了一种解决方案,可以验证ECDSA签名如何从EC公共密钥字节中获取PublicKey对象?,该签名可以完美地处理给定的数据。 这是数据: 这是代码(显示 true ): 当我将签名和数据更改为来自已实施系统的示例输入时,就会出现我的问题: 新数据输出此错误: 我认为问题出在 安全消息 附带的签名上,因为: 密钥对的长度和格式与示例相同。并且是正确的,因为它来自对消息进行签名的