我正在使用正确签名的PDF和标准库来检查消息摘要。 signerInformation.getContentDigest()返回的摘要值与解密的digestInfo.getDigest()值不同。此外,对经过正确签名的PDF文件的签名验证失败。
回答了有关数字签名的所有可能问题。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.List;
import javax.crypto.Cipher;
import javax.xml.bind.DatatypeConverter;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x509.DigestInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationVerifier;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Store;
public class PDFSignatureValidatorSample {
public static void verifyPDF(byte[] doc) throws Exception {
PDDocument document = PDDocument.load(doc);
List<PDSignature> signatures = document.getSignatureDictionaries();
PDSignature sig = signatures.get(0);
if (sig != null) {
String subFilter = sig.getSubFilter();
if (subFilter != null) {
Collection<X509Certificate> certs = new ArrayList<X509Certificate>();
switch (subFilter) {
case "ETSI.CAdES.detached":
case "adbe.pkcs7.detached":
byte[] signatureContent = sig.getContents(doc);
System.out.println("---------signatureContent length------------");
System.out.println(signatureContent.length);
String signatureContentB64 = Base64.getEncoder().encodeToString(signatureContent);
// System.out.println("---------signatureContent b64------------");
// System.out.println("signatureContentB64);
byte[] signedContent = sig.getSignedContent(doc);
String signedContentB64 = Base64.getEncoder().encodeToString(signedContent);
System.out.println("---------signedContent length------------");
System.out.println(signedContent.length);
// System.out.println("---------signedContent b64------------");
// System.out.println(signedContentB64);
// Now we construct a PKCS #7 or CMS.
CMSProcessable cmsProcessableInputStream = new CMSProcessableByteArray(signedContent);
CMSSignedData cmsSignedData = new CMSSignedData(cmsProcessableInputStream, signatureContent);
Store certificatesStore = cmsSignedData.getCertificates();
Collection<SignerInformation> signers = cmsSignedData.getSignerInfos().getSigners();
SignerInformation signerInformation = signers.iterator().next();
Collection matches = certificatesStore.getMatches(signerInformation.getSID());
X509CertificateHolder certificateHolder = (X509CertificateHolder) matches.iterator().next();
certificateHolder.getSerialNumber();
X509Certificate certFromSignedData = new JcaX509CertificateConverter()
.getCertificate(certificateHolder);
certs.add(certFromSignedData);
SignerInformationVerifier signerInformationVerifier = new JcaSimpleSignerInfoVerifierBuilder()
.build(certificateHolder);
boolean isValid = signerInformation.verify(signerInformationVerifier);
System.out.println("---------isValid - checked by signerInformation ------------");
System.out.println(isValid);
System.out.println("---------certSerialNumber dec------------");
System.out.println(certificateHolder.getSerialNumber());
System.out.println("---------certSerialNumber hex------------");
System.out.println(String.format("0x%08X", certificateHolder.getSerialNumber()));
System.out.println("---------getContentType------------");
System.out.println(signerInformation.getContentType().toString());
System.out.println("---------contentDigest base64------------");
byte[] contentDigest = signerInformation.getContentDigest();
String contentDigestB64 = Base64.getEncoder().encodeToString(contentDigest);
System.out.println(contentDigestB64);
System.out.println("---------contentDigest hex------------");
String contentDigestHex = DatatypeConverter.printHexBinary(contentDigest);
System.out.println(contentDigestHex);
System.out.println("---------digestAlgOID------------");
System.out.println(signerInformation.getDigestAlgOID());
System.out.println(signerInformation.getDigestAlgorithmID());
System.out.println("---------encryptionAlgOID------------");
System.out.println(signerInformation.getEncryptionAlgOID());
byte[] signatureBytes = signerInformation.getSignature();
String signatureBytesB64 = Base64.getEncoder().encodeToString(signatureBytes);
System.out.println("---------getSignature (encrypted) base64------------");
System.out.println(signatureBytesB64);
System.out.println("---------getSignature (encrypted) hex------------");
String signatureBytesHex = DatatypeConverter.printHexBinary(signatureBytes);
System.out.println(signatureBytesHex);
Cipher encryptCipher = Cipher.getInstance("RSA");
PublicKey publicKey = certFromSignedData.getPublicKey();
byte[] publicKeyBytes = publicKey.getEncoded();
String publicKeyBytesB64 = Base64.getEncoder().encodeToString(publicKeyBytes);
String publicKeyHex = DatatypeConverter.printHexBinary(publicKeyBytes);
System.out.println("---------publicKey base64------------");
System.out.println(publicKeyBytesB64);
System.out.println("---------publicKey hex------------");
System.out.println(publicKeyHex);
encryptCipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] cipherText = encryptCipher.doFinal(signatureBytes);
String cipherTextB64 = Base64.getEncoder().encodeToString(cipherText);
System.out.println("---------getSignature (decrypted) base64------------");
System.out.println(cipherTextB64);
System.out.println("---------getSignature (decrypted) hex------------");
String cipherTextHex = DatatypeConverter.printHexBinary(cipherText);
System.out.println(cipherTextHex);
byte[] digest = null;
ASN1InputStream ais = new ASN1InputStream(cipherText);
ASN1Primitive obj = ais.readObject();
DigestInfo digestInfo = new DigestInfo((ASN1Sequence) obj);
System.out.println("---------getAlgorithmId------------");
System.out.println(digestInfo.getAlgorithmId().getAlgorithm().getId());
System.out.println("---------getDigest hex------------");
digest = digestInfo.getDigest();
String digestHex = DatatypeConverter.printHexBinary(digest);
System.out.println(digestHex);
ais.close();
final Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(digest);
boolean signatureVerified = signature.verify(signatureBytes);
System.out.println("---------signature.verify------------");
System.out.println(signatureVerified);
Security.addProvider(new BouncyCastleProvider());
Signature bcSignature = Signature.getInstance("RSA", "BC");
bcSignature.initVerify(publicKey);
bcSignature.update(digest);
boolean signatureVerifiedBC = bcSignature.verify(signatureBytes);
System.out.println("---------signature bc.verify------------");
System.out.println(signatureVerifiedBC);
if (digestHex != contentDigestHex) {
System.out.println("--------------------------------------");
System.out.println("---------VERIFICATION FAILED---------");
System.out.println("---------calculated digest------------");
System.out.println(contentDigestHex);
System.out.println("---------decrypted digest ------------");
System.out.println(digestHex);
System.out.println("--------------------------------------");
}
else
System.out.println("VERIFICATION PASSED");
break;
default:
throw new IOException("Unknown certificate type " + subFilter);
};
};
};
};
public static void main(String[] args) throws Exception {
String fileName = "c:/test.pdf";
byte[] doc = Files.readAllBytes(Paths.get(fileName));
verifyPDF(doc);
}
}
控制台输出:
---------signatureContent length------------
18944
---------signedContent length------------
91250
---------isValid - checked by signerInformation ------------
true
---------certSerialNumber dec------------
8811972559309533840
---------certSerialNumber hex------------
0x7A4A6A5AD8227290
---------getContentType------------
1.2.840.113549.1.7.1
---------contentDigest base64------------
hcDVAjmJolBuurj1d2/2vWgO1bajqj1M8gGsQTGa/7w=
---------contentDigest hex------------
85C0D5023989A2506EBAB8F5776FF6BD680ED5B6A3AA3D4CF201AC41319AFFBC
---------digestAlgOID------------
2.16.840.1.101.3.4.2.1
org.bouncycastle.asn1.x509.AlgorithmIdentifier@da3a9fbd
---------encryptionAlgOID------------
1.2.840.113549.1.1.11
---------getSignature (encrypted) base64------------
uV9h778EUQ0wl7O9vNd5bvuzaq/XEx0zHeSnGKWAQQGqPe1YkKByZ1Pexo4ZZ6MqrKx7Ofpvje2gMhls6SAqqs4U2bMdrrM7a3udLDWLjjCHNy90zne2KUz/737gpIbiV4kzWbxlh44oWYgwM1Zc73hwWfh+I7G/fw0H//U4fgjnxbkXIEYU/zBOqQX4xlsWSAvAs1LB1N2+ySCGU9XvT5Btj9/F+e6hH8yMoyOFB1GrChdyasToUNq+5yAQa28nIxCcURvPo20mDtACgccLCVX+joMlFT21SI7mXIiFsIdBzGMqenyi7atJV53Gtvmp+tIpxowsvWTbCEMofsYVHw==
---------getSignature (encrypted) hex------------
B95F61EFBF04510D3097B3BDBCD7796EFBB36AAFD7131D331DE4A718A5804101AA3DED5890A0726753DEC68E1967A32AACAC7B39FA6F8DEDA032196CE9202AAACE14D9B31DAEB33B6B7B9D2C358B8E3087372F74CE77B6294CFFEF7EE0A486E257893359BC65878E2859883033565CEF787059F87E23B1BF7F0D07FFF5387E08E7C5B917204614FF304EA905F8C65B16480BC0B352C1D4DDBEC9208653D5EF4F906D8FDFC5F9EEA11FCC8CA323850751AB0A17726AC4E850DABEE720106B6F2723109C511BCFA36D260ED00281C70B0955FE8E8325153DB5488EE65C8885B08741CC632A7A7CA2EDAB49579DC6B6F9A9FAD229C68C2CBD64DB0843287EC6151F
---------publicKey base64------------
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxR7C91bL7cfR6NFMUvDCTtg4Fp91AH89efm7+1u2NO/nJNAtKvlhItNz4GDv28Ffp5WvKRZlHe5ORbnFvbHOaAAFG43k/tIx23ePa63TdYo8LY5M1/Auw2qDt7UZPZ4vbyABF7wtuNySrYOEcK5pdyAcXjECYHfEQSAH7DItOxMtU9I+Scl7oVo5rFHWEWWxBj3sIW5hxTUAVI9kZufi53XO5jEsHwh+5olR0tsJvrEeUPFHanPiBLi3Y+rrRMimPEeqfHV8jY+rZouovWEoivxNyLb3aooihAS5x4tC1A9p92TeQHBu3RZNfoC9KBB5F0iyb/CAFw9k5Aype8vgDQIDAQAB
---------publicKey hex------------
30820122300D06092A864886F70D01010105000382010F003082010A0282010100C51EC2F756CBEDC7D1E8D14C52F0C24ED838169F75007F3D79F9BBFB5BB634EFE724D02D2AF96122D373E060EFDBC15FA795AF2916651DEE4E45B9C5BDB1CE6800051B8DE4FED231DB778F6BADD3758A3C2D8E4CD7F02EC36A83B7B5193D9E2F6F200117BC2DB8DC92AD838470AE6977201C5E31026077C4412007EC322D3B132D53D23E49C97BA15A39AC51D61165B1063DEC216E61C53500548F6466E7E2E775CEE6312C1F087EE68951D2DB09BEB11E50F1476A73E204B8B763EAEB44C8A63C47AA7C757C8D8FAB668BA8BD61288AFC4DC8B6F76A8A228404B9C78B42D40F69F764DE40706EDD164D7E80BD2810791748B26FF080170F64E40CA97BCBE00D0203010001
---------getSignature (decrypted) base64------------
MDEwDQYJYIZIAWUDBAIBBQAEIOFF1ONvV7F2h9Cq5WOGXtM0CvzuwMyL2zahkGwcjebN
---------getSignature (decrypted) hex------------
3031300D060960864801650304020105000420E145D4E36F57B17687D0AAE563865ED3340AFCEEC0CC8BDB36A1906C1C8DE6CD
---------getAlgorithmId------------
2.16.840.1.101.3.4.2.1
---------getDigest hex------------
E145D4E36F57B17687D0AAE563865ED3340AFCEEC0CC8BDB36A1906C1C8DE6CD
---------signature.verify------------
false
---------signature bc.verify------------
false
--------------------------------------
---------VERIFICATION FAILED---------
---------calculated digest------------
85C0D5023989A2506EBAB8F5776FF6BD680ED5B6A3AA3D4CF201AC41319AFFBC
---------decrypted digest ------------
E145D4E36F57B17687D0AAE563865ED3340AFCEEC0CC8BDB36A1906C1C8DE6CD
--------------------------------------
文件已使用DSS演示WebApp签名 https://ec.europa.eu/cefdigital/DSS/webapp-demo/sign-a-document
可以验证文件DSS演示WebApp https://ec.europa.eu/cefdigital/DSS/webapp-demo/validation
<DigestMatcher type="MESSAGE_DIGEST">
<DigestMethod>SHA256</DigestMethod>
<DigestValue>hcDVAjmJolBuurj1d2/2vWgO1bajqj1M8gGsQTGa/7w=</DigestValue>
<DataFound>true</DataFound>
<DataIntact>true</DataIntact>
</DigestMatcher>
DSS演示WebApp计算出与所提供代码相同的摘要。
可以使用openssl解密签名摘要:
signature=uV9h778EUQ0wl7O9vNd5bvuzaq/XEx0zHeSnGKWAQQGqPe1YkKByZ1Pexo4ZZ6MqrKx7Ofpvje2gMhls6SAqqs4U2bMdrrM7a3udLDWLjjCHNy90zne2KUz/737gpIbiV4kzWbxlh44oWYgwM1Zc73hwWfh+I7G/fw0H//U4fgjnxbkXIEYU/zBOqQX4xlsWSAvAs1LB1N2+ySCGU9XvT5Btj9/F+e6hH8yMoyOFB1GrChdyasToUNq+5yAQa28nIxCcURvPo20mDtACgccLCVX+joMlFT21SI7mXIiFsIdBzGMqenyi7atJV53Gtvmp+tIpxowsvWTbCEMofsYVHw==
publicKey=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxR7C91bL7cfR6NFMUvDCTtg4Fp91AH89efm7+1u2NO/nJNAtKvlhItNz4GDv28Ffp5WvKRZlHe5ORbnFvbHOaAAFG43k/tIx23ePa63TdYo8LY5M1/Auw2qDt7UZPZ4vbyABF7wtuNySrYOEcK5pdyAcXjECYHfEQSAH7DItOxMtU9I+Scl7oVo5rFHWEWWxBj3sIW5hxTUAVI9kZufi53XO5jEsHwh+5olR0tsJvrEeUPFHanPiBLi3Y+rrRMimPEeqfHV8jY+rZouovWEoivxNyLb3aooihAS5x4tC1A9p92TeQHBu3RZNfoC9KBB5F0iyb/CAFw9k5Aype8vgDQIDAQAB
echo $signature | base64 --decode > signature.bin
echo -----BEGIN PUBLIC KEY----- > publicKey.pem
echo $publicKey >> publicKey.pem
echo -----END PUBLIC KEY----- >> publicKey.pem
openssl rsautl -verify -inkey publicKey.pem -pubin -in signature.bin |
openssl asn1parse -inform DER
0:d=0 hl=2 l= 49 cons: SEQUENCE
2:d=1 hl=2 l= 13 cons: SEQUENCE
4:d=2 hl=2 l= 9 prim: OBJECT :sha256
15:d=2 hl=2 l= 0 prim: NULL
17:d=1 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:E145D4E36F57B17687D0AAE563865ED3340AFCEEC0CC8BDB36A1906C1C8DE6CD
openssl还返回与提供的代码相同的解密摘要。
测试文件在这里可用: https://drive.google.com/open?id=1UlOZOp-UYllK7Ra35dggccoWdhcb_Ntp
从签名解密的消息摘要(“真实摘要”)绝对是摘要,必须与计算的消息摘要进行比较。该值在文件中;更准确地说,在签名字段中加密,并且必须使用公共密钥(从证书中提取)解密。这是事实。 根据SignerInformation文档getContentDigest()-返回在验证期间计算出的内容摘要。验证成功,因为signerInformation验证返回true。如前所述,此值是计算得出的,而不是从文件(或文件中的某些字段)读取的,实际上必须计算得出。这两个值应该匹配。 这些事实得出的结论是,两个值很可能相等,但是给定的十六进制或b64表示形式不同。问题是如何对原始值的二进制进行编码,以及如何将它们从二进制解码为表示形式,或者更好的问题是如何使用标准Java库从pdf文件中获取“真实摘要”。
答案 0 :(得分:1)
使用CMS签名容器对PDF的签名字节范围进行签名(如果 Subfilter 值 adbe.pkcs7.detached 和 ETSI.CAdES .detached )。
构建CMS签名容器的第一步是
对于每个签名者,都会计算出一个消息摘要或哈希值 内容使用签名者特定的消息摘要算法。如果 签名人正在签名除内容以外的任何信息, 内容的消息摘要和其他信息是 用签名者的消息摘要算法摘要(请参阅 5.4),结果成为“邮件摘要”。
(RFC 5652 section 5 Signed-data Content Type)
及以后
消息摘要计算过程的结果取决于 是否存在signedAttrs字段。如果没有该字段, 结果只是所描述内容的消息摘要 以上。但是,如果存在该字段,则结果为消息 SignedAttrs值的完整DER编码的摘要 包含在signedAttrs字段中。由于SignedAttrs值, 如果存在,则必须包含content-type和message-digest 属性,这些值将间接包含在结果中。
(RFC 5652 section 5.4. Message Digest Calculation Process)
您显然认为是在只有内容签名的情况下,即 signedAttrs字段不存在。
我们认为这是错误的,要认真对待每个CMS签名配置文件(如CAdES / PAdES),都需要签名某些其他信息。
因此,您从加密的签名值中提取的哈希值不是签名的PDF字节范围的哈希值,而是签名的属性的 ,而签名的PDF字节范围的哈希值只是其中一个属性,即信息摘要属性
如果您检查该签名容器的ASN.1转储(openssl也允许您创建ASN.1转储,请尝试一下!),您会发现(偏移量为0的)签名属性(从偏移量1828开始) :
1828 234: . . . . . [0] {
1831 24: . . . . . . SEQUENCE {
1833 9: . . . . . . . OBJECT IDENTIFIER contentType (1 2 840 113549 1 9 3)
: . . . . . . . . (PKCS #9)
1844 11: . . . . . . . SET {
1846 9: . . . . . . . . OBJECT IDENTIFIER data (1 2 840 113549 1 7 1)
: . . . . . . . . . (PKCS #7)
: . . . . . . . . }
: . . . . . . . }
1857 47: . . . . . . SEQUENCE {
1859 9: . . . . . . . OBJECT IDENTIFIER messageDigest (1 2 840 113549 1 9 4)
: . . . . . . . . (PKCS #9)
1870 34: . . . . . . . SET {
1872 32: . . . . . . . . OCTET STRING
: . . . . . . . . . 85 C0 D5 02 39 89 A2 50 ....9..P
: . . . . . . . . . 6E BA B8 F5 77 6F F6 BD n...wo..
: . . . . . . . . . 68 0E D5 B6 A3 AA 3D 4C h.....=L
: . . . . . . . . . F2 01 AC 41 31 9A FF BC
: . . . . . . . . }
: . . . . . . . }
1906 156: . . . . . . SEQUENCE {
1909 11: . . . . . . . OBJECT IDENTIFIER
: . . . . . . . . signingCertificateV2 (1 2 840 113549 1 9 16 2 47)
: . . . . . . . . (S/MIME Authenticated Attributes)
1922 140: . . . . . . . SET {
1925 137: . . . . . . . . SEQUENCE {
1928 134: . . . . . . . . . SEQUENCE {
1931 131: . . . . . . . . . . SEQUENCE {
1934 32: . . . . . . . . . . . OCTET STRING
: . . . . . . . . . . . . 7E 0A 99 20 3C E5 79 10 ~.. <.y.
: . . . . . . . . . . . . B7 22 59 58 A1 7F A3 3B ."YX...;
: . . . . . . . . . . . . C4 BC 78 14 BC C1 B6 A1 ..x.....
: . . . . . . . . . . . . FF A6 AE 9E 0A FD 8D A6
1968 95: . . . . . . . . . . . SEQUENCE {
1970 83: . . . . . . . . . . . . SEQUENCE {
1972 81: . . . . . . . . . . . . . [4] {
1974 79: . . . . . . . . . . . . . . SEQUENCE {
1976 11: . . . . . . . . . . . . . . . SET {
1978 9: . . . . . . . . . . . . . . . . SEQUENCE {
1980 3: . . . . . . . . . . . . . . . . . OBJECT IDENTIFIER
: . . . . . . . . . . . . . . . . . . countryName (2 5 4 6)
: . . . . . . . . . . . . . . . . . . (X.520 DN component)
1985 2: . . . . . . . . . . . . . . . . . PrintableString 'HR'
: . . . . . . . . . . . . . . . . . }
: . . . . . . . . . . . . . . . . }
1989 19: . . . . . . . . . . . . . . . SET {
1991 17: . . . . . . . . . . . . . . . . SEQUENCE {
1993 3: . . . . . . . . . . . . . . . . . OBJECT IDENTIFIER
: . . . . . . . . . . . . . . . . . . organizationName (2 5 4 10)
: . . . . . . . . . . . . . . . . . . (X.520 DN component)
1998 10: . . . . . . . . . . . . . . . . . UTF8String 'AKD d.o.o.'
: . . . . . . . . . . . . . . . . . }
: . . . . . . . . . . . . . . . . }
2010 26: . . . . . . . . . . . . . . . SET {
2012 24: . . . . . . . . . . . . . . . . SEQUENCE {
2014 3: . . . . . . . . . . . . . . . . . OBJECT IDENTIFIER '2 5 4 97'
2019 17: . . . . . . . . . . . . . . . . . UTF8String 'VATHR-58843087891'
: . . . . . . . . . . . . . . . . . }
: . . . . . . . . . . . . . . . . }
2038 15: . . . . . . . . . . . . . . . SET {
2040 13: . . . . . . . . . . . . . . . . SEQUENCE {
2042 3: . . . . . . . . . . . . . . . . . OBJECT IDENTIFIER
: . . . . . . . . . . . . . . . . . . commonName (2 5 4 3)
: . . . . . . . . . . . . . . . . . . (X.520 DN component)
2047 6: . . . . . . . . . . . . . . . . . UTF8String 'HRIDCA'
: . . . . . . . . . . . . . . . . . }
: . . . . . . . . . . . . . . . . }
: . . . . . . . . . . . . . . . }
: . . . . . . . . . . . . . . }
: . . . . . . . . . . . . . }
2055 8: . . . . . . . . . . . . INTEGER 7A 4A 6A 5A D8 22 72 90
: . . . . . . . . . . . . }
: . . . . . . . . . . . }
: . . . . . . . . . . }
: . . . . . . . . . }
: . . . . . . . . }
: . . . . . . . }
: . . . . . . }
其中的第二个条目是您要查找的哈希值:
1857 47: . . . . . . SEQUENCE {
1859 9: . . . . . . . OBJECT IDENTIFIER messageDigest (1 2 840 113549 1 9 4)
: . . . . . . . . (PKCS #9)
1870 34: . . . . . . . SET {
1872 32: . . . . . . . . OCTET STRING
: . . . . . . . . . 85 C0 D5 02 39 89 A2 50 ....9..P
: . . . . . . . . . 6E BA B8 F5 77 6F F6 BD n...wo..
: . . . . . . . . . 68 0E D5 B6 A3 AA 3D 4C h.....=L
: . . . . . . . . . F2 01 AC 41 31 9A FF BC
: . . . . . . . . }
: . . . . . . . }
与您计算出的值相同
---------contentDigest base64------------
hcDVAjmJolBuurj1d2/2vWgO1bajqj1M8gGsQTGa/7w=
---------contentDigest hex------------
85C0D5023989A2506EBAB8F5776FF6BD680ED5B6A3AA3D4CF201AC41319AFFBC
要提取messageDigest值,可以按以下步骤操作:
PDDocument document = PDDocument.load(THE_PDF);
List<PDSignature> signatures = document.getSignatureDictionaries();
PDSignature sig = signatures.get(0);
byte[] cmsBytes = sig.getContents(bytes);
CMSSignedData cms = new CMSSignedData(cmsBytes);
SignerInformation signerInformation = cms.getSignerInfos().iterator().next();
Attribute attribute = signerInformation.getSignedAttributes().get(PKCSObjectIdentifiers.pkcs_9_at_messageDigest);
ASN1Encodable value = attribute.getAttributeValues()[0];
System.out.printf("MessageDigest attribute value: %s\n", value);
(CalculateDigest测试testExtractMessageDigestAttributeForUser2893427
)
输出:
MessageDigest attribute value: #85c0d5023989a2506ebab8f5776ff6bd680ed5b6a3aa3d4cf201ac41319affbc
您引用了Wiki diagram showing how a digital signature is applied and then verified。它只能用于了解一般概念,而不能理解CAdES / PAdES签名容器等特定签名配置文件的所有详细信息。
此外,它也不代表所有签名算法。仅对于某些部分(例如RSA签名),您实际上可以在解密后从签名字节值中提取哈希值;对于其他DSA和ECDSA,则只能对照签名字节检查给定的哈希值。
答案 1 :(得分:0)
package hr.ccr.validator;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.List;
import javax.crypto.Cipher;
import javax.xml.bind.DatatypeConverter;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x509.DigestInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationVerifier;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Store;
public class PDFSignatureValidatorSample {
public static void verifyPDF(byte[] doc) throws Exception {
PDDocument document = PDDocument.load(doc);
List<PDSignature> signatures = document.getSignatureDictionaries();
PDSignature sig = signatures.get(0);
if (sig != null) {
String subFilter = sig.getSubFilter();
if (subFilter != null) {
Collection<X509Certificate> certs = new ArrayList<X509Certificate>();
switch (subFilter) {
case "ETSI.CAdES.detached":
case "adbe.pkcs7.detached":
byte[] signatureContent = sig.getContents(doc);
System.out.println("---------signatureContent length------------");
System.out.println(signatureContent.length);
String signatureContentB64 = Base64.getEncoder().encodeToString(signatureContent);
// System.out.println("---------signatureContent b64------------");
// System.out.println("signatureContentB64);
byte[] signedContent = sig.getSignedContent(doc);
String signedContentB64 = Base64.getEncoder().encodeToString(signedContent);
System.out.println("---------signedContent length------------");
System.out.println(signedContent.length);
// System.out.println("---------signedContent b64------------");
// System.out.println(signedContentB64);
// Now we construct a PKCS #7 or CMS.
CMSProcessable cmsProcessableInputStream = new CMSProcessableByteArray(signedContent);
CMSSignedData cmsSignedData = new CMSSignedData(cmsProcessableInputStream, signatureContent);
Store certificatesStore = cmsSignedData.getCertificates();
Collection<SignerInformation> signers = cmsSignedData.getSignerInfos().getSigners();
SignerInformation signerInformation = signers.iterator().next();
Collection matches = certificatesStore.getMatches(signerInformation.getSID());
X509CertificateHolder certificateHolder = (X509CertificateHolder) matches.iterator().next();
certificateHolder.getSerialNumber();
X509Certificate certFromSignedData = new JcaX509CertificateConverter()
.getCertificate(certificateHolder);
certs.add(certFromSignedData);
SignerInformationVerifier signerInformationVerifier = new JcaSimpleSignerInfoVerifierBuilder()
.build(certificateHolder);
boolean isValid = signerInformation.verify(signerInformationVerifier);
byte[] encodedSignedAttributes = signerInformation.getEncodedSignedAttributes();
String encodedSignedAttributesB64 = Base64.getEncoder().encodeToString(encodedSignedAttributes);
System.out.println("---------encodedSignedAttributesB64------------");
System.out.println(encodedSignedAttributesB64);
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] derSignedAttributesHash = sha256.digest(encodedSignedAttributes);
String derSignedAttributesHashB64 = Base64.getEncoder().encodeToString(derSignedAttributesHash);
String derSignedAttributesHashHex = DatatypeConverter.printHexBinary(derSignedAttributesHash);
System.out.println("---------derSignedAttributesHashB64------------");
System.out.println(derSignedAttributesHashB64);
System.out.println("---------derSignedAttributesHashHex------------");
System.out.println(derSignedAttributesHashHex);
System.out.println("---------isValid - checked by signerInformation ------------");
System.out.println(isValid);
System.out.println("---------certSerialNumber dec------------");
System.out.println(certificateHolder.getSerialNumber());
System.out.println("---------certSerialNumber hex------------");
System.out.println(String.format("0x%08X", certificateHolder.getSerialNumber()));
System.out.println("---------getContentType------------");
System.out.println(signerInformation.getContentType().toString());
System.out.println("---------contentDigest base64------------");
byte[] contentDigest = signerInformation.getContentDigest();
String contentDigestB64 = Base64.getEncoder().encodeToString(contentDigest);
System.out.println(contentDigestB64);
System.out.println("---------contentDigest hex------------");
String contentDigestHex = DatatypeConverter.printHexBinary(contentDigest);
System.out.println(contentDigestHex);
System.out.println("---------digestAlgOID------------");
System.out.println(signerInformation.getDigestAlgOID());
System.out.println(signerInformation.getDigestAlgorithmID());
System.out.println("---------encryptionAlgOID------------");
System.out.println(signerInformation.getEncryptionAlgOID());
byte[] signatureBytes = signerInformation.getSignature();
String signatureBytesB64 = Base64.getEncoder().encodeToString(signatureBytes);
System.out.println("---------getSignature (encrypted) base64------------");
System.out.println(signatureBytesB64);
System.out.println("---------getSignature (encrypted) hex------------");
String signatureBytesHex = DatatypeConverter.printHexBinary(signatureBytes);
System.out.println(signatureBytesHex);
Cipher encryptCipher = Cipher.getInstance("RSA");
PublicKey publicKey = certFromSignedData.getPublicKey();
byte[] publicKeyBytes = publicKey.getEncoded();
String publicKeyBytesB64 = Base64.getEncoder().encodeToString(publicKeyBytes);
String publicKeyHex = DatatypeConverter.printHexBinary(publicKeyBytes);
System.out.println("---------publicKey base64------------");
System.out.println(publicKeyBytesB64);
System.out.println("---------publicKey hex------------");
System.out.println(publicKeyHex);
encryptCipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] cipherText = encryptCipher.doFinal(signatureBytes);
String cipherTextB64 = Base64.getEncoder().encodeToString(cipherText);
System.out.println("---------getSignature (decrypted) base64------------");
System.out.println(cipherTextB64);
System.out.println("---------getSignature (decrypted) hex------------");
String cipherTextHex = DatatypeConverter.printHexBinary(cipherText);
System.out.println(cipherTextHex);
byte[] digest = null;
ASN1InputStream ais = new ASN1InputStream(cipherText);
ASN1Primitive obj = ais.readObject();
DigestInfo digestInfo = new DigestInfo((ASN1Sequence) obj);
System.out.println("---------getAlgorithmId------------");
System.out.println(digestInfo.getAlgorithmId().getAlgorithm().getId());
System.out.println("---------getDigest hex------------");
digest = digestInfo.getDigest();
String digestHex = DatatypeConverter.printHexBinary(digest);
System.out.println(digestHex);
ais.close();
final Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(encodedSignedAttributes);
boolean signatureVerified = signature.verify(signatureBytes);
System.out.println("---------signature.verify------------");
System.out.println(signatureVerified);
if (!digestHex.equalsIgnoreCase(derSignedAttributesHashHex)) {
System.out.println("--------------------------------------");
System.out.println("---------VERIFICATION FAILED---------");
System.out.println("---------calculated digest------------");
System.out.println(derSignedAttributesHashHex);
System.out.println("---------decrypted digest ------------");
System.out.println(digestHex);
System.out.println("--------------------------------------");
}
else System.out.println("VERIFICATION PASSED");
break;
default:
throw new IOException("Unknown certificate type " + subFilter);
};
};
};
};
public static void main(String[] args) throws Exception {
String fileName = "c:/test.pdf";
byte[] doc = Files.readAllBytes(Paths.get(fileName));
verifyPDF(doc);
}
}