Halvard
Halvard

Reputation: 4003

How do I encrypt with aes-256-gcm and get it into a pkcs7 file?

To follow the new Peppol requirements for banking in Norway I have to use asymmetric encryption. The file I need to send is a pkcs#7 file and the encryption of the key needs to be aes-256-gcm.

Note that I am working on a Windows machine and am hoping to be able to do this in C#.

Encryption by itself works fine

Using only System.Security.Cryptography I can do this:

X509Certificate2 cert = new X509Certificate2(certificateAsByteArray);

byte[] encryptedData = new byte[data.Length];
byte[] tag = new byte[16]; // GCM tag length is 16 bytes
byte[] nonce = new byte[12]; // GCM standard nonce length is 12 bytes
RandomNumberGenerator.Fill(nonce); // Generate a random nonce

byte[] symmetricKey = new byte[32]; // 256 bits for AES-256-GCM
RandomNumberGenerator.Fill(symmetricKey);
using (AesGcm aesGcm = new AesGcm(symmetricKey))
{
    aesGcm.Encrypt(nonce, indreAsice, encryptedData, tag);
}

But I don't know how to get this into a pkcs#7 file.

MimeKit makes pkcs#7 file

MimeKit is a tool that helps with this.

var pkcs7AsStream = new MemoryStream();
using (var memoryStream = new MemoryStream(indreAsice))
using (var ctx = new AesGcmContextContext())
{
    ctx.Import(cert);
    var recipients = new MimeKit.Cryptography.CmsRecipientCollection
    {
        new MimeKit.Cryptography.CmsRecipient(cert)
    };
    var mimePart = new MimePart(MimeKit.ContentType.Parse("application/pkcs7-mime"))
    {
        Content = new MimeContent(memoryStream)
    };

    var mime = ApplicationPkcs7Mime.Encrypt(ctx, recipients, mimePart);
    mime.WriteTo(pkcs7AsStream);
}
pkcs7AsStream.Seek(0, SeekOrigin.Begin);

The context is overridden by me:

public class AesGcmContextContext : TemporarySecureMimeContext
{
    protected override EncryptionAlgorithm GetPreferredEncryptionAlgorithm(MimeKit.Cryptography.CmsRecipientCollection recipients)
    {
        return EncryptionAlgorithm.Aes256;
        //return base.GetPreferredEncryptionAlgorithm(recipients);
    }
}

This gives a pkcs#7 file where the encryption used is aes-256-cbc, not aes-256-gcm. It turns out you can't choose aes-256-gcm in MimeKit and .Aes256 gives aes-256-cbc.

OpenSSL

Another way of generating pkcs#7 is OpenSSL using cms like this:

codeopenssl cms -encrypt -in plaintext.txt -outform DER -out cms-enveloped-data.p7m -aes-256-gcm recipient-cert.pem

But this does not work. The similar for aes-256-cbc works fine though.

BouncyCastle

It is perhaps here the best chance lies, but I can't seem to get this to work:

Org.BouncyCastle.Cms.CmsEnvelopedDataGenerator envData = new CmsEnvelopedDataGenerator();
envData.AddKeyTransRecipient(sertifikater.GetPublicCertificate());
var enveloped = envData.Generate(new CmsProcessableByteArray(data), "2.16.840.1.101.3.4.1.46");
var kryptencoded = enveloped.GetEncoded();

It accepts 2.16.840.1.101.3.4.1.42 (aes-256-cbc), but not 2.16.840.1.101.3.4.1.46 (aes-256-gcm).

My latest attempt is to use CryptoConfig.AddAlgorithm to add the algorihm, but with no success.

Finally the question:

It seems aes-256-gcm and pkcs#7 doesn't like each other. I have heard that this works fine in Java world, but we want to stay with .Net as everything else we do is .Net. How do I get this working?

Upvotes: 0

Views: 679

Answers (1)

Halvard
Halvard

Reputation: 4003

A friend of a colleague finally found one way to solve this. Cudos, and hopefully some beers, to him! It turns out that in the .Net world there are two different BouncyCastle, Portable.BouncyCastle and the other one (sorry, don't know if it has a name). For decryption the regular BouncyCastle package works, but for encryption we had to use Portable.BouncyCastle and make some adjustments.

Note that since we had to use both packages we had to use aliasing to differentiate, so when you read Port.BouncyCastle it means the Portable.BouncyCastle, but otherwise it is the other one.

First CmsEnvelopedDataGenerator had to be extended and GetAlgorithmIdentifier overrideen:

public class SmarterCmsEnveloped : Port.Org.BouncyCastle.Cms.CmsEnvelopedDataGenerator
{

    protected override Port.Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier GetAlgorithmIdentifier(string encryptionOid, Port.Org.BouncyCastle.Crypto.Parameters.KeyParameter encKey, Port.Org.BouncyCastle.Asn1.Asn1Encodable asn1Params,
        out Port.Org.BouncyCastle.Crypto.ICipherParameters cipherParameters)
    {
        if (encryptionOid == NistObjectIdentifiers.IdAes256Gcm.Id)
        {
            var random = new Port.Org.BouncyCastle.Security.SecureRandom();
            var nonce = Port.Org.BouncyCastle.Security.SecureRandom.GetNextBytes(random, 12);
            cipherParameters = new Port.Org.BouncyCastle.Crypto.Parameters.AeadParameters(encKey, 12 * 8, nonce);
            return new Port.Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier(Port.Org.BouncyCastle.Asn1.Nist.NistObjectIdentifiers.IdAes256Gcm, new Port.Org.BouncyCastle.Asn1.DerSequence(new Port.Org.BouncyCastle.Asn1.DerOctetString(nonce)));
        }

        return base.GetAlgorithmIdentifier(encryptionOid, encKey, asn1Params, out cipherParameters);
    }
}

Then this would give us plcs#7 with the symmetric key envrypted with aes-256-gcm:

var cmsGenerator = new SmarterCmsEnveloped();
var parser = new Port.Org.BouncyCastle.X509.X509CertificateParser();
var certificate = parser.ReadCertificate(certificateAsByteArray);
var sha256 = new Port.Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier(Port.Org.BouncyCastle.Asn1.Nist.NistObjectIdentifiers.IdSha256);
cmsGenerator.AddRecipientInfoGenerator(new Port.Org.BouncyCastle.Operators.CmsKeyTransRecipientInfoGenerator(
    certificate, 
    new Port.Org.BouncyCastle.Crypto.Operators.Asn1KeyWrapper(Port.Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers.IdRsaesOaep,
    new Port.Org.BouncyCastle.Asn1.Pkcs.RsaesOaepParameters(sha256,
    new Port.Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier(Port.Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers.IdMgf1, sha256)) , certificate.GetPublicKey())));

var content = new Port.Org.BouncyCastle.Cms.CmsProcessableByteArray(dataToBeEncryptedWithAes256Gcm);
var generator = cmsGenerator.Generate(content, Port.Org.BouncyCastle.Asn1.Nist.NistObjectIdentifiers.IdAes256Gcm.Id);

I hope this helps someone else, as this was frustrating to work with!

Upvotes: 0

Related Questions