bcgit / bc-java

Bouncy Castle Java Distribution (Mirror)
https://www.bouncycastle.org/java.html
MIT License
2.28k stars 1.13k forks source link

Encrypt and Decrypt email using AES-GCM #1838

Open vbrandl opened 1 week ago

vbrandl commented 1 week ago

Hey

I want do encrypt and decrypt an email using AES128-GCM. I generated a keypair for testing using openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem. Encrypting and then decrypting does work fine within BouncyCastle, but I cannot decrypt the message using OpenSSL

I have the following functions for en- and decryption:

private static void encrypt(final X509Certificate[] chain, final byte[] content, final Path out) throws Exception {
  final var recpInfo = new JceKeyTransRecipientInfoGenerator(chain[0]);
  final var envelopedGenerator = new CMSEnvelopedDataStreamGenerator();
  envelopedGenerator.addRecipientInfoGenerator(recpInfo);
  final var encryptor = new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_GCM).setProvider(BC_PROV).build();
  try (final var outFile = Files.newOutputStream(out, StandardOpenOption.CREATE_NEW);
  final var envOut = envelopedGenerator.open(outFile, encryptor);
  final var data = new ByteArrayInputStream(content)) {
    data.transferTo(envOut);
  }
}

private static void decrypt(final PrivateKey privKey, final byte[] encrypted, final Path out) throws Exception {
  final var parser = new CMSEnvelopedDataParser(encrypted);
  final var recipients = parser.getRecipientInfos();
  final var recipient = recipients.getRecipients().iterator().next();
  final var keyRecip = new JceKeyTransEnvelopedRecipient(privKey).setProvider(BC_PROV);
  try (final var outFile = Files.newOutputStream(out, StandardOpenOption.CREATE_NEW);
  final var contentIn = recipient.getContentStream(keyRecip).getContentStream()) {
    contentIn.transferTo(outFile);
  }
}

OpenSSL gives me the following error:

$ openssl cms -inkey key.pem -recip certificate.pem -decrypt -in encrypted.mime
Error reading SMIME Content Info
4017FF8F667F0000:error:068000D1:asn1 encoding routines:SMIME_read_ASN1_ex:no content type:crypto/asn1/asn_mime.c:420:

How can I generate a SMIME email, that can also be decrypted using OpenSSL?

Attached you can find my example keys as well as encrypted.mime and decrypted.mime.

example.zip

bukka commented 5 days ago

You need to use CMSAuthEnvelopedDataStreamGenerator for encryption and CMSAuthEnvelopedDataParser for decryption. I have got a testing app that has got all logic for that working. The CMS envelope part can be seen here: https://github.com/bukka/jcrypto/blob/1028865c22972b1af358dc020222ae4bc57fabd7/src/main/java/eu/bukka/jcrypto/cms/CMSEnvelope.java .

There is also working mail BC integration added which implements integrates changes (included for now in the repo as pr's are still not reviewed / merged) done - check the app code for more details. The respective PR's are https://github.com/bcgit/bc-java/pull/1791 and https://github.com/bcgit/bc-java/pull/1794 in case you need it too.

vbrandl commented 5 days ago

Thanks for the links. I'll try to implement a solution based on that.

So in general, there is currently no officially supported way to do SMIME with AES-GCM using only BC?

I'm asking because communication in the German energy market is required to use AES-GCM starting next month.

bukka commented 4 days ago

So in general, there is currently no officially supported way to do SMIME with AES-GCM using only BC?

There is currently just KeyTrans support for AES-GCM CMS implementation so no S/MIME support. The first PR (#1791) adds SMIME support to mail component and the PR #1794 adds support for KeyAgree and KEK RI. I made also different version of those classes that can be easily included to the project which can be found in https://github.com/bukka/jcrypto/tree/1028865c22972b1af358dc020222ae4bc57fabd7/src/main/java/eu/bukka/jcrypto/bc/mail/smime and https://github.com/bukka/jcrypto/tree/1028865c22972b1af358dc020222ae4bc57fabd7/src/main/java/eu/bukka/jcrypto/bc/cms/jcajce which is useful until the changes in some form are part of BouncyCastle.

There's currently no support in pkix mime/smime package. I plan to look into those later but that's in my free time so it might take some time.

In addition you can find some examples that test integration with OpenSSL in https://github.com/bukka/jcrypto/tree/1028865c22972b1af358dc020222ae4bc57fabd7/examples . There is some minor difference but KeyTrans and KeyAgree work fine. I'm author of OpenSSL CMS AES-GCM support and currently also looking to some fixes in that area that should make it more compatible. That's also in my free time so it will take some time to fix all those issues but there's already one of them addressed here: https://github.com/openssl/openssl/pull/25482#pullrequestreview-2311957833

I'm asking because communication in the German energy market is required to use AES-GCM starting next month.

I'm aware of it and the work in PR's and my tools were actually funded by company called Virtimo AG for exactly this reason.

vbrandl commented 4 days ago

Thanks again for your detailed explanations.

Today the regulatory body published a change of the AES-GCM requirements: https://www.edi-energy.de/index.php?id=38&tx_bdew_bdew%5Buid%5D=2501&tx_bdew_bdew%5Baction%5D=download&tx_bdew_bdew%5Bcontroller%5D=Dokument&cHash=debea299775661f227a17b4896b4bd3c

The deadline for AES-GCM has been moved to 2025-04-01. Therefore we will wait for the upstream changes to be merged and won't try to implement it on our own.

bukka commented 4 days ago

But if your code is above as you originally posted (CMS only and handling SMIME headers yourself), then it's already supported. You need to wait only if you use mail component (org.bouncycastle.mail.smime), pkix smime (org.bouncycastle.mime.smime) or if you need also key agree RI (it means if you are currently using JceKeyAgreeEnvelopedRecipient) for CBC - you should probably need that one as spec requires it for receiving IIRC.