我已经部分阅读了itextpdf's paper有关数字签名的信息,并从中开始开发代码:
public class ServerSignature implements ExternalSignature {
public String getHashAlgorithm() {
return DigestAlgorithms.SHA256;
}
public String getEncryptionAlgorithm() {
return "RSA";
}
public byte[] sign(byte[] message) throws GeneralSecurityException {
byte[] s = Base64.getDecoder().decode(SPMService.this.signature);
return s;
}
}
public void sign(Certificate[] chain, CryptoStandard subfilter, String reason,
String location) throws GeneralSecurityException, DocumentException, IOException {
// Creating the reader and the stamper
System.out.println("SRC: " + this.SRC);
PdfReader reader = new PdfReader(this.SRC);
FileOutputStream os = new FileOutputStream(this.DEST);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', null, true);
// Creating the appearance
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason(reason);
appearance.setLocation(location);
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");
// Creating the signature
ExternalDigest digest = new BouncyCastleDigest();
ExternalSignature signature = new ServerSignature();
MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, subfilter);
this.verifySignatures(this.DEST);
}
private void verifySignature(AcroFields fields, String name) throws GeneralSecurityException, IOException {
// Returns true
System.out.println("Signature covers whole document: " + fields.signatureCoversWholeDocument(name));
// Returns the right revisions, e.g. 1 of 1
System.out.println("Document revision: " + fields.getRevision(name) + " of " + fields.getTotalRevisions());
PdfPKCS7 pkcs7 = fields.verifySignature(name);
// Returns SHA256
System.out.println("Digest algorithm: " + pkcs7.getHashAlgorithm());
//Returns RSA
System.out.println("Encryption algorithm: " + pkcs7.getEncryptionAlgorithm());
// Return /adbe.pkcs7.detached
System.out.println("Filter subtype: " + pkcs7.getFilterSubtype());
// Get the signing certificate to find out the name of the signer.
X509Certificate cert = (X509Certificate) pkcs7.getSigningCertificate();
System.out.println("Name of the signer: " + CertificateInfo.getSubjectFields(cert).getField("CN"));
if (pkcs7.getSignName() != null) {
System.out.println("Alternative name of the signer: " + pkcs7.getSignName());
}
// The following log is returning false always. Why?
System.out.println("Integrity check OK? " + pkcs7.verify());
}
private void verifySignatures(String src) throws IOException, GeneralSecurityException {
Security.addProvider(new BouncyCastleProvider());
PdfReader reader = new PdfReader(src);
AcroFields fields = reader.getAcroFields();
ArrayList<String> names = fields.getSignatureNames();
for (String name : names) {
System.out.println("===== " + name + " =====");
verifySignature(fields, name);
}
System.out.println();
}
public void start() throws GeneralSecurityException, DocumentException, IOException {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
// certificate I received in the Controller
InputStream targetStream = new ByteArrayInputStream(this.certificate.getBytes());
Certificate[] chain = new Certificate[1];
chain[0] = factory.generateCertificate(targetStream);
this.sign(chain, CryptoStandard.CMS, "Reason", "Location");
}
使用此代码,当我使用Adobe Acrobat Reader打开签名的文档时,签名可见并且也嵌入到文档中。问题在于它带有红色标记,表明“自签名以来,文档已被更改或损坏”。
有人可以向我解释什么遗漏了,或者我的逻辑错了吗?
答案 0 :(得分:3)
问题不在您显示的代码中。因此,这要么是您使用的ServerSignature
的问题,要么是它所解决的服务器端代码的问题。
分析与您共享的example PDF可以看到:
PDF中带符号字节范围的SHA256摘要为
37B415ACC3E5A146073E8C95D5DEE7E93190CF082210A9CE53AD78F7C5D58002
实际上,嵌入式签名容器SignerInfo
包含签名的属性
. . . . . . SEQUENCE {
. . . . . . . OBJECT IDENTIFIER messageDigest (1 2 840 113549 1 9 4)
. . . . . . . . (PKCS #9)
. . . . . . . SET {
. . . . . . . . OCTET STRING
. . . . . . . . . 37 B4 15 AC C3 E5 A1 46 7......F
. . . . . . . . . 07 3E 8C 95 D5 DE E7 E9 .>......
. . . . . . . . . 31 90 CF 08 22 10 A9 CE 1..."...
. . . . . . . . . 53 AD 78 F7 C5 D5 80 02
. . . . . . . . }
. . . . . . . }
因此,签名字节范围的摘要计算,其在签名容器创建中的使用以及签名容器的嵌入都可以正常工作。
可以使用PKCS#1 1.5填充将签名字节成功解密为DigestInfo
对象。因此,用于加密的私钥与所谓的签名者证书中的公钥匹配。
签名容器中签名属性的SHA256摘要为
0083D5FA72C3D81EA038BF62D4AFFC949F23E120DA59E59C5CB8EFA5214BA975
但已解密的签名字节(已删除PKCS#1 1.5填充)
3031300D0609608648016503040201050004208DECC8571946D4CD70A024949E033A2A2A54377FE9F1C1B944C20F9EE11A9E51
所以实际签名的SHA256摘要是
8DECC8571946D4CD70A024949E033A2A2A54377FE9F1C1B944C20F9EE11A9E51
显然不匹配。因此,显然在您使用的ExternalSignature
实现ServerSignature
中或在它所处理的后端中,签名属性字节没有得到正确处理。
不幸的是,您既没有显示您使用的ServerSignature
实现代码,也没有显示相关服务器上的任何代码。因此,无法进一步分析该问题。很明显,您显示的iText签名代码可以正常工作。
答案 1 :(得分:0)
想到了三种可能性:
您使用了错误的文档区域进行验证。我曾经犯过一个非常容易犯的错误,尽管我从未尝试过处理PDF,所以我无能为力。显然,如果您相差一个字节,就会出现不匹配的情况。而且,很显然,您不会针对整个文件进行计算;至少必须排除签名值,因为在计算原始签名时不存在该值。可能还省略了一些其他字段。
您使用了错误的算法。我不了解PDF内容,因此无法发表评论,但是您如何选择使用的算法?您确定它是正确的吗?
该文档确实已被篡改。