验证期间计算的签名PDF内容摘要与签名中的解密摘要不同

时间:2019-09-13 15:56:25

标签: java pdf hash signature verify

我正在使用正确签名的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文件中获取“真实摘要”。

2 个答案:

答案 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

使用PDFBox和BouncyCastle提取messageDigest值

要提取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);
    }

}