Reputation: 4003
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
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