【发布时间】:2017-12-04 05:09:52
【问题描述】:
我正在尝试用 Java 验证数字签名的 PDF 文档。
我使用 Apache PDFBox 2.0.6 来获取签名和已签名的原始 PDF,然后我使用 Bouncy Castle 来验证分离的签名(计算原始文件的哈希,使用签名者的公开验证签名键入并比较结果)。
我阅读了this article 并尝试使用以下代码获取签名字节和原始 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 和我使用 doc.getSignatureDictionaries().get(0).getSignedContent(signedPDF); 从 PDFBox 获得的 PDF 不一样。
这是我的充气城堡验证码:
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;
}
【问题讨论】:
-
你的问题让我有些困惑。
getSignedContent()返回不带签名内容字符串的 PDF。这不是真正的 PDF。请参阅源代码下载中的 ShowSignature.java 示例,了解如何验证签名。如果这没有帮助,请编辑您的问题。 -
@user3362334 你能记录下 PDFUtils() 的来源吗?它不在 apache pdfbox-2.0.18 中......
-
@user2677034,你能分享一下 PDFUtils 和 SignatureInfo 的 maven repo 吗?
-
@User、PDFUtils 和 SignatureInfo 均来自 BouncyCastle
-
Thnx alot @user2677034 ,我已经为 bouncyCastle 添加了 maven,但给出了编译错误。有什么线索吗?
标签: java pdf digital-signature bouncycastle pdfbox