Search code examples
javacryptographyaesrsabouncycastle

Create CMS Enveloped Data with already encrypted content and encrypted key


tldr:

Is there a way to create CMS Enveloped Data when i have already encrypted content with AES and secret key which is already encrypted with public key?

Long version:

I have an application which encrypts and decrypts data with AES (CBC and GCM mode). Symetric key is encrypted/decrypted with RSA key pairs. When user requests for data we decrypt it in backend (Java) and send it to the browser

Usually we have public key and private key but there is requirement that in some cases we dont have private key and the decryption should take place in browser (user provides PFX with privatkey). The solution for this is PKI.js which can decrypt data using PFX and CMS Enveloped Data.

The problem is that we already encrypted the data and dont have access to plain data which we can use to build CMS Enveloped Data.

Edit: @dave_thompson_085 thank you for reply! I have an follow-up question. I dont hold certificates in system so only thing i have is public key. Is there a way to adjust your code to this requirement?

Before your answer i was encrypting data for second time just for CMS Enveloped Object. In this code i used only public key for generating reciepents. Is there a way to adjust your code to generate reciepent with public key only? My previous code:

    SubjectKeyIdentifier subjectKeyIdentifier = new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey);
    JcaAlgorithmParametersConverter paramsConverter = new JcaAlgorithmParametersConverter();
    OAEPParameterSpec oaepSpec = new OAEPParameterSpec(digest, "MGF1", new MGF1ParameterSpec(mgfDigest), PSource.PSpecified.DEFAULT);
    AlgorithmIdentifier oaepAlgId = paramsConverter.getAlgorithmIdentifier(PKCSObjectIdentifiers.id_RSAES_OAEP, oaepSpec);
    RecipientInfoGenerator recipientInfoGenerator = new JceKeyTransRecipientInfoGenerator(subjectKeyIdentifier.getEncoded(),oaepAlgId,publicKey).setProvider("BC");
    envelopedDataGenerator.addRecipientInfoGenerator(recipientInfoGenerator);

And what about Hashing Algorithm? Do i need one or it is only additional protection to ensure that CMS Enveloped Object didnt changed?


Solution

  • FWIW you can use BouncyCastle only to do the DER formatting (plus set the versions, a minor convenience), plus PEM if you want that (also a minor convenience), after you do all the rest of the work yourself. Example:

    import java.io.*;
    import java.security.*;
    import java.security.cert.*;
    import java.security.spec.*;
    import java.util.*;
    import javax.crypto.*;
    import javax.crypto.spec.*;
    import org.bouncycastle.asn1.*;
    import org.bouncycastle.asn1.cms.*;
    import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
    import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
    import org.bouncycastle.asn1.x500.X500Name;
    import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
    
            // sample data and encryption, replace as needed
            byte[] input = "testdata".getBytes();
            X509Certificate cert = null;
            try(InputStream is = new FileInputStream(args[0])){
                cert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is);
            }
            byte[] skey = new byte[16], snonce = new byte[16];
            SecureRandom rand = new SecureRandom(); rand.nextBytes(skey); rand.nextBytes(snonce);
            Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
            aes.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(skey,"AES"), new IvParameterSpec(snonce));
            byte[] ctx1 = aes.doFinal(input);
            Cipher rsa = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            rsa.init(Cipher.ENCRYPT_MODE, cert.getPublicKey());
            byte[] ctx2 = rsa.doFinal(skey);
            // now build the message
            byte[] issuer = cert.getIssuerX500Principal().getEncoded();
            ASN1Set recips = new DERSet( new KeyTransRecipientInfo( 
                    new RecipientIdentifier(
                            new IssuerAndSerialNumber(X500Name.getInstance(issuer),cert.getSerialNumber() )), 
                    new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption), 
                    new DEROctetString(ctx2) ));
            EnvelopedData env = new EnvelopedData (null/*no originfo*/, recips, 
                    new EncryptedContentInfo(CMSObjectIdentifiers.data, 
                            new AlgorithmIdentifier(NISTObjectIdentifiers.id_aes128_CBC,
                                    new DEROctetString(snonce) ), 
                            new DEROctetString(ctx1) ),
                    new DERSet() /*no attributes*/ );
            ContentInfo msg = new ContentInfo(CMSObjectIdentifiers.envelopedData, env);
            try(OutputStream os = new FileOutputStream(args[1]) ){
                os.write(msg.getEncoded());
            }
            // or use PemWriter (and a PemObject) if you want PEM