Search code examples
bouncycastle

How to encrypt a CMS EnvelopedData value with AES-GCM using BouncyCastle?


I can generate a valid CMS EnvelopedData value with AES-CBC using the Kotlin code below:

import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.cms.CMSAlgorithm
import org.bouncycastle.cms.CMSEnvelopedDataGenerator
import org.bouncycastle.cms.CMSProcessableByteArray
import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder
import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator

fun encrypt(plaintext: ByteArray, recipientCertificate: X509CertificateHolder): ByteArray {
    val cmsEnvelopedDataGenerator = CMSEnvelopedDataGenerator()

    val x509Certificate = JcaX509CertificateConverter()
        .getCertificate(recipientCertificate)
    val transKeyGen =
        JceKeyTransRecipientInfoGenerator(x509Certificate)
    cmsEnvelopedDataGenerator.addRecipientInfoGenerator(transKeyGen)

    val msg = CMSProcessableByteArray(plaintext)
    val encryptor = JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).build()
    val bcEnvelopedData = cmsEnvelopedDataGenerator.generate(msg, encryptor)
    return bcEnvelopedData.encoded
}

But if I replace CMSAlgorithm.AES128_CBC with CMSAlgorithm.AES128_GCM, JceCMSContentEncryptorBuilder.build() throws the following error:

cannot create key generator: 2.16.840.1.101.3.4.1.6 KeyGenerator not available

That error seems to suggest AES-GCM-128 isn't supported, but the fact the object CMSAlgorithm.AES128_GCM exists suggests to me that can't be it -- I must be doing something wrong. Maybe the IV isn't generated behind the scenes for me and I have to set it explicitly somehow?

I'm using org.bouncycastle:bcpkix-jdk15on:1.64.

UPDATE: I've found a test in the BouncyCastle test suite that uses AES-GCM in EnvelopedData values, and they're also passing CMSAlgorithm.AES128_GCM to JceCMSContentEncryptorBuilder in the same way. The only difference is that they're calling .setProvider("BC") before .build(), but I've just tried that and it didn't make any difference.


Solution

  • Answering my question:

    JceCMSContentEncryptorBuilder.setProvider() must be certainly called before .build(), but if the provider isn't registered, you have to pass an instance of org.bouncycastle.jce.provider.BouncyCastleProvider.