Cryptographic Message Syntax - Java

Cryptographic Message Syntax(CMS) is the IETF's standard for cryptographically protected messages. It can be used to digitally sign, digest, authenticate or encrypt any form of digital data.

Wikipedia

 

This tutorial shows how to implement CMS in java. We would use maven as our build tool. Add the following dependencies to your POM file.

    
<dependency>
        <groupid>
            org.bouncycastle
        </groupid>
        <artifactid>
            bcpkix-jdk15on
        </artifactid>
        <version>
            1.50
        </version>
</dependency>

Bouncy Castle implements CMS for both the Java platform and the .Net platform. The implementation uses the Public Key Infrastructure (PKI) architecture to secure messages/data.

Brief explanation: Let us assume you have already generated your private and public(certificate) keys. The public key should be sent to the party who would be sending the secured message.

After the secured data has been received, the private key would be used to decrypt the data. Ideally, both parties would exchange public keys in other for both parties to send secure data.

 

Source Code:

 

Create a class that contains the following methods:

 

 

 

    /*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package us.extendit.cms;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.logging.Logger;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSAlgorithm;
import org.bouncycastle.cms.CMSEnvelopedDataParser;
import org.bouncycastle.cms.CMSEnvelopedDataStreamGenerator;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.RecipientInformation;
import org.bouncycastle.cms.RecipientInformationStore;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.Store;

public class CMS {

    private static final  Logger logger = Logger.getLogger(CMS.class.getName());

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    public static CMSSignedData signMessage(X509Certificate cert, PrivateKey key, byte[] data) {
        /*
         * Sign the data with key, and embed the certificate associated within the CMSSignedData
         */

        CMSTypedData content = new CMSProcessableByteArray(data);
        ArrayList certList = new ArrayList<>();
        certList.add(cert);
        Store certs;
        try {
            certs = new JcaCertStore(certList);

            CMSSignedDataGenerator signGen = new CMSSignedDataGenerator();

            ContentSigner sha1signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(key);

            signGen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(sha1signer, cert));
            signGen.addCertificates(certs);

            return signGen.generate(content, true); //content could have been the content of File zip = new File("fichier.zip"); CMSProcessableFile content = new CMSProcessableFile(zip);
        } catch (CertificateEncodingException | OperatorCreationException | CMSException e) {
            logger.severe(e.getMessage());
            return null;
        }
    }

    public static CMSSignedData signMessageSimple(X509Certificate cert, PrivateKey key, byte[] data) {
        /*
         * Sign data with the private key, but does not embed the certificate
         * Still need the certificate to identify signer
         */
        Security.addProvider(new BouncyCastleProvider());
        CMSTypedData content = new CMSProcessableByteArray(data);
        try {
            CMSSignedDataGenerator signGen = new CMSSignedDataGenerator();

            ContentSigner sha1signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(key);

            signGen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(sha1signer, cert));

            return signGen.generate(content, true); //content could have been the content of File zip = new File("fichier.zip"); CMSProcessableFile content = new CMSProcessableFile(zip);
        } catch (OperatorCreationException | CertificateEncodingException | CMSException e) {
            logger.severe(e.getMessage());
            return null;
        }
    }

    public static BigInteger getSerialFromSignedData(CMSSignedData data) {
        /*
         * Return the serial of the cert that has been used to sign the data
         */
        return ((SignerInformation) data.getSignerInfos().getSigners().iterator().next()).getSID().getSerialNumber();
    }

    public static Object verifySignedMessage(CMSSignedData data) {
        /*
         * Verify the signature using the cert embeded into the signed object
         */
        Security.addProvider(new BouncyCastleProvider());
        Object datareturn = null;
        Store certs = data.getCertificates();

        SignerInformation signer = (SignerInformation) data.getSignerInfos().getSigners().iterator().next();

        X509CertificateHolder cert = (X509CertificateHolder) certs.getMatches(signer.getSID()).iterator().next();

        try {
            if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
                datareturn = data.getSignedContent().getContent();
            }
        } catch (OperatorCreationException | CertificateException | CMSException e) {
            logger.severe(e.getMessage());
        }
        return datareturn;
    }

    public static Object verifySignedMessage(CMSSignedData data, X509Certificate cert) {
        /*
         * Verify the signature but does not use the certificate in the signed object
         */
        Object datareturn = null;

        SignerInformation signer = (SignerInformation) data.getSignerInfos().getSigners().iterator().next();
        try {
            if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
                datareturn = data.getSignedContent().getContent();
            }
        } catch (OperatorCreationException | CMSException e) {
            logger.severe(e.getMessage());
        }
        return datareturn;
    }

    public static byte[] encryptMessage(byte[] data, X509Certificate cert) {
        /*
         * Cipher the data given with the cert
         */
        CMSEnvelopedDataStreamGenerator edGen = new CMSEnvelopedDataStreamGenerator();
        try {
            edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(cert).setProvider("BC"));

            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            OutputStream out = edGen.open(bout, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES256_CBC).setProvider("BC").build());

            out.write(data);
            out.close();
            return bout.toByteArray();
        } catch (CertificateEncodingException | CMSException | IOException e) {
            logger.severe(e.getMessage());
            return null;
        }
    }

    public static byte[] decryptMessage(byte[] data, X509Certificate cert, PrivateKey key) {
        /*
         * Decrypt the message and check the signature, that's why it require the cert of the signer
         */

        byte[] cleardata = null;
        try {
            // Initialise parser 
            CMSEnvelopedDataParser envDataParser = new CMSEnvelopedDataParser(data);
            RecipientInformationStore recipients = envDataParser.getRecipientInfos();

            RecipientInformation recipient = (RecipientInformation) recipients.getRecipients().iterator().next();

            byte[] envelopedData = recipient.getContent(new JceKeyTransEnvelopedRecipient(key));

            byte[] signedBytes = envelopedData;
            CMSSignedData signedDataIn = new CMSSignedData(signedBytes);
            Object res = verifySignedMessage(signedDataIn);
            if (res != null) {
                cleardata = (byte[]) signedDataIn.getSignedContent().getContent();
            }
        } catch (CMSException | IOException e) {
            logger.severe(e.getMessage());
        }
        return cleardata;
    }

    public static Object decryptMessage(byte[] data, PrivateKey key) {
        /*
         * Just decipher the message with the given private key
         */

        try {
            CMSEnvelopedDataParser envDataParser = new CMSEnvelopedDataParser(data);

            RecipientInformation recipient = (RecipientInformation) envDataParser.getRecipientInfos().getRecipients().iterator().next();
            byte[] envelopedData = recipient.getContent(new JceKeyTransEnvelopedRecipient(key));

            return envelopedData;
        } catch (CMSException | IOException e) {
            logger.severe(e.getMessage());
        }
        return null;
    }

    public static Object getContentFromSignedData(CMSSignedData data) {
        /*
         * Return the data from signed data
         */
        return data.getSignedContent().getContent();
    }

    public static PrivateKey getPrivateKey(InputStream privateKeyPath, String password) {
        Security.addProvider(new BouncyCastleProvider());
        PEMParser parser = new PEMParser(new InputStreamReader(privateKeyPath));
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
        PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
        KeyPair kp;
        try {
            Object obj = parser.readObject();
            if (obj instanceof PrivateKeyInfo) {
                return converter.getPrivateKey((PrivateKeyInfo) obj);
            }

            if (obj instanceof PEMEncryptedKeyPair) {
                kp = converter.getKeyPair(((PEMEncryptedKeyPair) obj).decryptKeyPair(decProv));
                return kp.getPrivate();
            }
            
            if(obj instanceof PEMKeyPair){
               return converter.getKeyPair((PEMKeyPair)obj).getPrivate();
            }
        } catch (IOException e) {
            logger.severe(e.getMessage());
        } finally {
            if (null != parser) {
                try {
                    parser.close();
                } catch (IOException e) {
                }
            }
        }
        return null;
    }

    public static X509Certificate cert(InputStream inputStream) {

        PEMParser r = new PEMParser(new InputStreamReader(inputStream));
        try {
            Object obj = r.readObject();
            if (obj instanceof X509CertificateHolder) {
                return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME)
                        .getCertificate(((X509CertificateHolder) obj));
            }
        } catch (IOException | CertificateException e) {
            logger.severe(e.getMessage());
        } finally {
            try {
                r.close();
            } catch (IOException e) {
                logger.severe(e.getMessage());
            }
        }
        return null;
    }

}

 

 

The  "cert" method would read the public key (certificate). This key would act as the asymmetric key. Calling the "encryptMessage" would encrypt the data in CMS format.

The "CMSAlgorithm.AES256_CBC" is the algorithm the symmetric key generated by the CMS infrastructure uses to encrypt the actual message.

The symmetric key is then encrypted with the asymmetric key. Both the symmetric key and secured data are then packaged into an envelope data format the CMS infrastructure understands.

 

When data in CMS format is received, the "decryptMessage" method is used.  A private key is needed and the "getPrivateKey" method would help retrieve the key.

The "decryptMessage" method knows how to decrypt the generated symmetric key. The decrypted symmetric key is then used to decrypt the secured data.

This is the basic flow on how securing data can be achieved using the CMS infrastructure.

 

CMS Sample Code

There are 2 Comments

How to generate keys? Which of public-key cryptography algorithms can be used? Could you provide runnable example?

Take a look this PKI tutorial(https://pki-tutorial.readthedocs.org/en/latest/#simple-pki). You can also use the java keytool utility that comes with the JDK. If you need a graphical user interface, you could use the Key tool explorer(http://keystore-explorer.sourceforge.net).

Add new comment