signature=0d5bd7f2bf1b2cef1d21bf5c9e8a2483,java - Signed PDF content digest calculated during verifi...

I am using properly signed PDF and standard libraries to check message digest.

signerInformation.getContentDigest() return digest value different from decripted digestInfo.getDigest() value. In addition, signature verification fails on properly signed PDF file.

Went through all possible questions about digital signature.

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 signatures = document.getSignatureDictionaries();

PDSignature sig = signatures.get(0);

if (sig != null) {

String subFilter = sig.getSubFilter();

if (subFilter != null) {

Collection certs = new ArrayList();

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 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);

}

}

Console output:

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

--------------------------------------

SHA256

hcDVAjmJolBuurj1d2/2vWgO1bajqj1M8gGsQTGa/7w=

true

true

DSS Demonstration WebApp calculates the same digest as presented code.

Digest from signature can be decrypted using 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 also returns the same decrypted digest as presented code.

Message digest decrypted from signature (“real digest”) is definitely digest that must be used for comparison with calculated message digest. This value is in file; more precisely, encrypted in signature field and has to be decrypted with public key (extracted from certificate). This is a fact.

According to the SignerInformation documentation getContentDigest() - return the content digest that was calculated during verification. Verification was successful, as signerInformation verify returned true. As stated, this value is calculated, not read from file (or some field in file), and indeed it has to be calculated. These two values should match.

Those facts lead to the conclusion that both values are most likely equal but given hex or b64 representation differ. The question is how original binary of values is encoded and how are they decoded from binary to their representation or better the question is how to get “real digest” from pdf file using standard java libraries.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值