0

I am using Bouncy Castle provided library to encrypt,decrypt,sign and verify sign. I am doing this as
1. Encrypt data
2. Sign data
3. Write signed byte to a file
4. Read signed byte from file
5. Verify signature
6. Decrypt data

I have taken reference from Beginning Cryptography with Java

My problem is in step 5 when i am verifying data i am getting

org.bouncycastle.cms.CMSException: message-digest attribute value does not match calculated value

My code is below

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.PKIXCertPathBuilderResult;
import java.security.cert.TrustAnchor;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;

import javax.security.auth.x500.X500Principal;
import javax.security.auth.x500.X500PrivateCredential;

import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.cms.CMSEnvelopedData;
import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
import org.bouncycastle.cms.CMSEnvelopedDataParser;
import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.RecipientId;
import org.bouncycastle.cms.RecipientInformation;
import org.bouncycastle.cms.RecipientInformationStore;
import org.bouncycastle.cms.SignerId;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.x509.X509V1CertificateGenerator;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;

public class Test {


    private static final char[] KEY_STORE_PASSWORD = "123456".toCharArray();
    private static final long VALIDITY_PERIOD = 365 * 24 * 60 * 60 * 1000;
    private static final char[] KEY_PASSWORD = "keyPassword".toCharArray();
    public static String ROOT_ALIAS = "root";
    public static String INTERMEDIATE_ALIAS = "intermediate";
    public static String END_ENTITY_ALIAS = "end";
    public static String PLAIN_TEXT = "Hello World!123";

    public static void main(String[] args)  {
        try{

        // CREATE KEY STORE
        KeyStore keyStore = createKeyStore();

        // STEP 1. ENCRYPT AND SIGN
        byte[] step1Data = encryptData(keyStore, PLAIN_TEXT.getBytes());
        CMSSignedData cmsSignedData = signData(keyStore,step1Data);
        new File("D:\\pkcs7\\encrypted-file.p7b");
        FileOutputStream fileOuputStream = new FileOutputStream("D:\\pkcs7\\encrypted-file.p7b"); 
        fileOuputStream.write(cmsSignedData.getEncoded());
        fileOuputStream.flush();
        fileOuputStream.close();


        // STEP 2. READ ENCRYPTED DATA AND VERIFY SIGN AND DECRYPT IT
        File file =new File("D:\\pkcs7\\encrypted-file.p7b");
        FileInputStream fileInputStream = new FileInputStream(file);
        byte[] encryptedAndSignedByte = new byte[(int)file.length()];
        fileInputStream.read(encryptedAndSignedByte );
        fileInputStream.close();
        cmsSignedData =  new CMSSignedData(encryptedAndSignedByte);
        if( verifyData(keyStore, cmsSignedData) == true ){
            decryptData(keyStore,encryptedAndSignedByte);
        }

        }catch (Exception e) {
            e.printStackTrace();
        }


    }

    /**
     * 
     * This method will encrypt data
     */
    private static byte[] encryptData(KeyStore keyStore, byte[] plainData) throws Exception {
        PrivateKey key = (PrivateKey) keyStore.getKey(END_ENTITY_ALIAS,
                KEY_PASSWORD);
        Certificate[] chain = keyStore.getCertificateChain(END_ENTITY_ALIAS);
        X509Certificate cert = (X509Certificate) chain[0];

        // set up the generator
        CMSEnvelopedDataGenerator gen = new CMSEnvelopedDataGenerator();

        gen.addKeyTransRecipient(cert);

        // create the enveloped-data object
        CMSProcessable data = new CMSProcessableByteArray(plainData);


        CMSEnvelopedData enveloped = gen.generate(data,
                CMSEnvelopedDataGenerator.AES128_CBC, "BC");

        return  enveloped.getEncoded();
        // recreate

    }

    private static byte[] decryptData(KeyStore keyStore,byte[] encryptedData) throws Exception{
        CMSEnvelopedDataParser envelopedDataParser = new CMSEnvelopedDataParser(new ByteArrayInputStream(encryptedData));

        PrivateKey key = (PrivateKey) keyStore.getKey(END_ENTITY_ALIAS,KEY_PASSWORD);
        Certificate[] chain = keyStore.getCertificateChain(END_ENTITY_ALIAS);
        X509Certificate cert = (X509Certificate) chain[0];


        CMSEnvelopedData enveloped = new CMSEnvelopedData(encryptedData);

        // look for our recipient identifier
        RecipientId recId = new RecipientId();

        recId.setSerialNumber(cert.getSerialNumber());
        recId.setIssuer(cert.getIssuerX500Principal().getEncoded());

        RecipientInformationStore recipients = enveloped.getRecipientInfos();
        RecipientInformation recipient = recipients.get(recId);

        if (recipient != null) {
            // decrypt the data
            byte[] recData = recipient.getContent(key, "BC");
            System.out.println("----------------------- RECOVERED DATA -----------------------");
            System.out.println(new String(recData));
            System.out.println("--------------------------------------------------------------");
            return recData;

        } else {
            System.out.println("could not find a matching recipient");
        }
        return null;
    }

    private static CMSSignedData signData(KeyStore keyStore,byte[] encryptedData ) throws Exception {
        // GET THE PRIVATE KEY
        PrivateKey key = (PrivateKey) keyStore.getKey(END_ENTITY_ALIAS,
                KEY_PASSWORD);

        Certificate[] chain = keyStore.getCertificateChain(END_ENTITY_ALIAS);
        CertStore certsAndCRLs = CertStore.getInstance("Collection",
                new CollectionCertStoreParameters(Arrays.asList(chain)), "BC");
        X509Certificate cert = (X509Certificate) chain[0];

        // set up the generator
        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        gen.addSigner(key, cert, CMSSignedDataGenerator.DIGEST_SHA224);
        gen.addCertificatesAndCRLs(certsAndCRLs);

        // create the signed-data object
        CMSProcessable data = new CMSProcessableByteArray(encryptedData);
        CMSSignedData signed = gen.generate(data, "BC");

        // recreate
        signed = new CMSSignedData(data, signed.getEncoded());
        // ContentInfo conInf = signed.getContentInfo();
        // CMSProcessable sigContent = signed.getSignedContent();
        return signed;
    }

    private static boolean verifyData(KeyStore keyStore, CMSSignedData signed)
            throws Exception {
        // verification step
        X509Certificate rootCert = (X509Certificate) keyStore.getCertificate(ROOT_ALIAS);

        if (isValidSignature(signed, rootCert)) {
            System.out.println("verification succeeded");
            return true;
        } else {
            System.out.println("verification failed");
        }
        return false;
    }

    /**
     * Take a CMS SignedData message and a trust anchor and determine if the
     * message is signed with a valid signature from a end entity entity
     * certificate recognized by the trust anchor rootCert.
     */
    private static boolean isValidSignature(CMSSignedData signedData,
            X509Certificate rootCert) throws Exception {

        boolean[] bArr = new boolean[2];
        bArr[0] = true;
        CertStore certsAndCRLs = signedData.getCertificatesAndCRLs(
                "Collection", "BC");
        SignerInformationStore signers = signedData.getSignerInfos();
        Iterator it = signers.getSigners().iterator();

        if (it.hasNext()) {
            SignerInformation signer = (SignerInformation) it.next();
            SignerId signerConstraints = signer.getSID();
            signerConstraints.setKeyUsage(bArr);
            PKIXCertPathBuilderResult result = buildPath(rootCert,
                    signer.getSID(), certsAndCRLs);
            return signer.verify(result.getPublicKey(), "BC");
        }

        return false;
    }

    /**
     * Build a path using the given root as the trust anchor, and the passed in
     * end constraints and certificate store.
     * <p>
     * Note: the path is built with revocation checking turned off.
     */
    public static PKIXCertPathBuilderResult buildPath(X509Certificate rootCert,
            X509CertSelector endConstraints, CertStore certsAndCRLs)
            throws Exception {
        CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", "BC");
        PKIXBuilderParameters buildParams = new PKIXBuilderParameters(
                Collections.singleton(new TrustAnchor(rootCert, null)),
                endConstraints);

        buildParams.addCertStore(certsAndCRLs);
        buildParams.setRevocationEnabled(false);

        return (PKIXCertPathBuilderResult) builder.build(buildParams);
    }

    /**
     * Create a KeyStore containing the a private credential with certificate
     * chain and a trust anchor.
     */
    public static KeyStore createKeyStore() throws Exception {
        KeyStore keyStore = KeyStore.getInstance("JKS");
        keyStore.load(null, null);

        keyStore.load(null, null);

        X500PrivateCredential rootCredential = createRootCredential();
        X500PrivateCredential interCredential = createIntermediateCredential(
                rootCredential.getPrivateKey(), rootCredential.getCertificate());
        X500PrivateCredential endCredential = createEndEntityCredential(
                interCredential.getPrivateKey(),
                interCredential.getCertificate());

        keyStore.setCertificateEntry(rootCredential.getAlias(),
                rootCredential.getCertificate());
        keyStore.setKeyEntry(
                endCredential.getAlias(),
                endCredential.getPrivateKey(),
                KEY_PASSWORD,
                new Certificate[] { endCredential.getCertificate(),
                        interCredential.getCertificate(),
                        rootCredential.getCertificate() });

        keyStore.store(new FileOutputStream("d:\\pkcs7\\KeyStore.jks"),
                KEY_STORE_PASSWORD);
        return keyStore;
    }

    /**
     * Create a random 1024 bit RSA key pair
     */
    public static KeyPair generateRSAKeyPair() throws Exception {
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
        kpGen.initialize(1024, new SecureRandom());
        return kpGen.generateKeyPair();
    }

    /**
     * Generate a sample V1 certificate to use as a CA root certificate
     */
    public static X509Certificate generateCertificate(KeyPair pair)
            throws Exception {
        X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
        certGen.setSerialNumber(BigInteger.valueOf(1));
        certGen.setIssuerDN(new X500Principal("CN=Test CA Certificate"));
        certGen.setNotBefore(new Date(System.currentTimeMillis()
                - VALIDITY_PERIOD));
        certGen.setNotAfter(new Date(System.currentTimeMillis()
                + VALIDITY_PERIOD));
        certGen.setSubjectDN(new X500Principal("CN=Test CA Certificate"));
        certGen.setPublicKey(pair.getPublic());
        certGen.setSignatureAlgorithm("SHA1WithRSAEncryption");
        return certGen.generateX509Certificate(pair.getPrivate(), "BC");
    }

    /**
     * Generate a sample V1 certificate to use as a CA root certificate
     */
    public static X509Certificate generateRootCert(KeyPair pair)
            throws Exception {
        X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();

        certGen.setSerialNumber(BigInteger.valueOf(1));
        certGen.setIssuerDN(new X500Principal("CN=Test CA Certificate"));
        certGen.setNotBefore(new Date(System.currentTimeMillis()
                - VALIDITY_PERIOD));
        certGen.setNotAfter(new Date(System.currentTimeMillis()
                + VALIDITY_PERIOD));
        certGen.setSubjectDN(new X500Principal("CN=Test CA Certificate"));
        certGen.setPublicKey(pair.getPublic());
        certGen.setSignatureAlgorithm("SHA1WithRSAEncryption");

        return certGen.generateX509Certificate(pair.getPrivate(), "BC");
    }

    /**
     * Generate a sample V3 certificate to use as an end entity certificate
     */
    public static X509Certificate generateEndEntityCert(PublicKey entityKey,
            PrivateKey caKey, X509Certificate caCert) throws Exception {
        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();

        certGen.setSerialNumber(BigInteger.valueOf(1));
        certGen.setIssuerDN(caCert.getSubjectX500Principal());
        certGen.setNotBefore(new Date(System.currentTimeMillis()
                - VALIDITY_PERIOD));
        certGen.setNotAfter(new Date(System.currentTimeMillis()
                + VALIDITY_PERIOD));
        certGen.setSubjectDN(new X500Principal("CN=Test End Certificate"));
        certGen.setPublicKey(entityKey);
        certGen.setSignatureAlgorithm("SHA1WithRSAEncryption");

        certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
                new AuthorityKeyIdentifierStructure(caCert));
        certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false,
                new SubjectKeyIdentifierStructure(entityKey));
        certGen.addExtension(X509Extensions.BasicConstraints, true,
                new BasicConstraints(false));
        certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(
                KeyUsage.digitalSignature | KeyUsage.keyEncipherment));

        return certGen.generateX509Certificate(caKey, "BC");
    }

    /**
     * Generate a X500PrivateCredential for the root entity.
     */
    public static X500PrivateCredential createRootCredential() throws Exception {
        KeyPair rootPair = generateRSAKeyPair();
        X509Certificate rootCert = generateRootCert(rootPair);

        return new X500PrivateCredential(rootCert, rootPair.getPrivate(),
                ROOT_ALIAS);
    }

    /**
     * Generate a X500PrivateCredential for the intermediate entity.
     */
    public static X500PrivateCredential createIntermediateCredential(
            PrivateKey caKey, X509Certificate caCert) throws Exception {
        KeyPair interPair = generateRSAKeyPair();
        X509Certificate interCert = generateIntermediateCert(
                interPair.getPublic(), caKey, caCert);

        return new X500PrivateCredential(interCert, interPair.getPrivate(),
                INTERMEDIATE_ALIAS);
    }

    /**
     * Generate a X500PrivateCredential for the end entity.
     */
    public static X500PrivateCredential createEndEntityCredential(
            PrivateKey caKey, X509Certificate caCert) throws Exception {
        KeyPair endPair = generateRSAKeyPair();
        X509Certificate endCert = generateEndEntityCert(endPair.getPublic(),
                caKey, caCert);

        return new X500PrivateCredential(endCert, endPair.getPrivate(),
                END_ENTITY_ALIAS);
    }

    /**
     * Generate a sample V3 certificate to use as an intermediate CA certificate
     */
    public static X509Certificate generateIntermediateCert(PublicKey intKey,
            PrivateKey caKey, X509Certificate caCert) throws Exception {
        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();

        certGen.setSerialNumber(BigInteger.valueOf(1));
        certGen.setIssuerDN(caCert.getSubjectX500Principal());
        certGen.setNotBefore(new Date(System.currentTimeMillis()));
        certGen.setNotAfter(new Date(System.currentTimeMillis()
                + VALIDITY_PERIOD));
        certGen.setSubjectDN(new X500Principal(
                "CN=Test Intermediate Certificate"));
        certGen.setPublicKey(intKey);
        certGen.setSignatureAlgorithm("SHA1WithRSAEncryption");

        certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
                new AuthorityKeyIdentifierStructure(caCert));
        certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false,
                new SubjectKeyIdentifierStructure(intKey));
        certGen.addExtension(X509Extensions.BasicConstraints, true,
                new BasicConstraints(0));
        certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(
                KeyUsage.digitalSignature | KeyUsage.keyCertSign
                        | KeyUsage.cRLSign));

        return certGen.generateX509Certificate(caKey, "BC");
    }


}
Arvind Purohit
  • 49
  • 2
  • 5
  • 10
  • Related [earlier question](http://stackoverflow.com/questions/11044888/using-pkcs-7-cryptography) by the same user – CodesInChaos Jun 17 '12 at 13:10
  • 1
    Your question seems focused on `.p7b` files. But file extensions are merely conventions. It would better if you could focus on the programming problem you are trying to solve and where you are stuck in that pursuit. That way we could better assist you. That said I will offer an answer to you questions, but I doubt it will get you closer to solving your problem. – President James K. Polk Jun 17 '12 at 13:28
  • just a note on the order between encryption and signature. Please, be sure of first signing and then encrypt (unlike what you wrote in your post). Otherwise you encouter many issues. For instance, the identity of the issuer is leaked and also someone could remove the signature and add their own one. – iammyr Jul 20 '16 at 11:36

2 Answers2

6

In typical usage a .p7b file contains only public key certificates and never a private key. It is often used to store an entire chain of certificates rather than a single certificate. The 'p7b' name comes from the format which is the degenerate form of PKCS#7 SignedData structure. Typically, private keys are stored in a PKCS#12 (often a file that has either a .p12 or a .pfx extension) file but other formats are also common.

To read in the certificates from a p7b file you can use the CertificateFactory class. A PKCS#12 file is directly usable as a keystore.

You mention PKCS#7 frequently. PKCS#7 is an old standard that is extremely large and open ended. These days the standard that is more commonly implemented is an extended subset of PKCS#7 called CMS. It's an IETF standard documented in RFC 5652. The Bouncycastle PKIX/CMS library has extensive support for the CMS specification.

President James K. Polk
  • 40,516
  • 21
  • 95
  • 125
  • Is it true that all p7 formats (with any extension such as .p7b or .p7s etc) are encrypted? Is it possible to convert the message without encryption to p7 files? I mean Is encryption mandatory in p7 or it is optional? – Hosein Aqajani May 04 '16 at 07:26
1
  try {
    File file = new File("d:\\TESTS\\VG.p7b");
    FileInputStream fis = new FileInputStream(file);
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    Collection c = cf.generateCertificates(fis);
    Iterator i = c.iterator();
    while (i.hasNext()) {
      X509Certificate cert509 = (X509Certificate) i.next();
      System.out.println(cert509);
    }
  }
  catch (Throwable th) {
    th.printStackTrace();
  }
Vitaly
  • 27
  • 1
  • 4
    Welcome to Stack Overflow! Would you consider adding some narrative to explain why this code works, and what makes it an answer to the question? – Andrew Barber Aug 27 '12 at 11:23
  • This will not load the certificate chain. Only loads the leaf certificate. Not the root & intermediate certificates. – AnirbanDebnath Aug 20 '19 at 12:20