I have a service that needs to encrypt and decrypt a connection text. I don't want to hard-code the password, so I've decided to use a certificate, but I want to be able to add it to the store the first time the settings UI (a separate configuration app) is opened.
I have code that checks for an existing certificate, and adds a new one if the certificate is not found.
private static void GenerateNewSecurityCertificate()
{
var ecdsa = ECDsa.Create(); // generate asymmetric key pair
var request = new CertificateRequest($"cn={CertificateName}", ecdsa, HashAlgorithmName.SHA512);
var cert = request.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(50));
File.WriteAllBytes("c:\\temp\\EncryptionCert.pfx", cert.Export(X509ContentType.Pfx, _certificatePassword));
}
private static void AddCertificateToStore()
{
using (var store = new X509Store(StoreName.TrustedPublisher, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadWrite);
using (var cert = new X509Certificate2("c:\\temp\\EncryptionCert.pfx", _certificatePassword,
X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet))
{
store.Add(cert);
}
}
}
I run into the issue when I try to retrieve the public key
using (var store = new X509Store(StoreName.TrustedPublisher, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
using (var cert = store.Certificates.Find(X509FindType.FindBySubjectName, CertificateName, false)[0])
{
var publicKey = cert.GetRSAPublicKey();
encryptedKey = publicKey.Encrypt(aesKey.Key, RSAEncryptionPadding.OaepSHA512);
}
}
I get a null returned when I call cert.GetRSAPublicKey()
. Does anyone see what I might be doing wrong?
Update:
I updated GenerateNewSecurityCertificate
to read
var rsa = RSA.Create();
var request = new CertificateRequest($"cn={CertificateName}", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
I am able to get the public key now, but get an Exception reading
"The parameter is incorrect"
at System.Security.Cryptography.NCryptNative.EncryptData[T](SafeNCryptKeyHandle key, Byte[] data, T& paddingInfo, AsymmetricPaddingMode paddingMode, NCryptEncryptor`1 encryptor)
on my call to publicKey.Encrypt
.
Since you apparently encrypt an AES key, it can be assumed that the (maximum) length of your data is 32 bytes (AES-256). The error message can be reproduced, e.g. if the message is too large for the key used.
For RSA, the length of a message must not exceed the key length. In fact, it is even smaller, because a part of the allowed length is reserved for padding. For OAEP this part depends on the digest used. For SHA512 the reserved part (130 bytes) is so large that a 1024 bits (128 bytes) key is not sufficient, with a 2048 bits (256 bytes) key the message may still be 126 bytes large, see here.
You create an RSA key with
var rsa = RSA.Create();
which uses a default value for the key length, s. RSA.Create()
. This default value also depends on the .NET version. Under .NET Framework 4.8 a 1024 bits key is created on my machine (nowadays too short), under .NET Core 3.1 a 2048 bits key is created. You can check the key length with rsa.KeySize
.
Probably a key that is too short for your purposes is generated in your environment. It is always better not to rely on defaults, but to specify the values explicitly. The Create
method has an overload that makes this possible, see RSA.Create(Int32)
. You should use this overload and create a key of (at least 2048 bits).
Alternatively, the bug could theoretically be eliminated with another digest (e.g. SHA256). Independent of this, a key with 1024 bits should not be used nowadays (12/2020) for security reasons, the length should be at least 2048 bits (see here).