我正在尝试验证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 = doc.getSignatureDictionaries().get(0).getSignedContent(signedPDF);
顺便说一句,我在origPDF上调用了stripCryptoSig(byte [] signedPDF)函数,所以这不是一个错误。
当我尝试使用充气城堡验证签名时,出现以下消息异常: message-digest属性值与计算值不匹配
我猜这是因为签名的原始PDF和我从PDFBox使用的PDF doc.getSignatureDictionaries().get(0).getSignedContent(signedPDF);不相同。
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。但是,如果使用其他签名方法,将无法正常工作。
adbe.pkcs7.detached
因此,感谢@Tilman Hausherr向我指出ShowSignature.java示例。这就是签名验证的方式。
并且,也感谢@mkl的详细说明。
我现在知道,当创建签名时,将添加签名字段,并根据该新值计算哈希值。这就是验证有效的原因。您不需要没有签名字段的原始PDF。