Key Size in RSA C# is not changing ! - c#

I am generating key pair and store them in xml file using
ToXmlString(true);
I need to set the key size to 2048
according to the MSDN the only place to do this is from the constructor of the RSACryptoServiceProvider
private void AssignParameter(ProviderType providerType)
{
CspParameters cspParams;
cspParams = new CspParameters((int)providerType);
cspParams.KeyContainerName = RSAEncryption.containerName;
cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
cspParams.ProviderName = "Microsoft Strong Cryptographic Provider";
cspParams.KeyNumber = (int)KeyNumber.Exchange;
this.rsa = new RSACryptoServiceProvider(2048, cspParams);
}
when I check the key size using
int x = this.rsa.KeySize;
I always get 1024
so whats the wrong here??

I've seen this before, try changing the container name or try
using (this.rsa = new RSACryptoServiceProvider(2048, cspParams))
{
}
or this.rsa.Clear(); after you are done with it.
If you already have a container with the same name it will re-use the container I believe.

You need first to clear the existing container like this:
rsa.PersistKeyInCsp = false;
rsa.Clear();
Then it should work with you.
Don't forget to set:
rsa.PersistKeyInCsp = true;

Related

How to create non-exportable private key with X509 certificate in windows key store programmatically (C#)

I am trying to create a private key and add a certificate(self-signed or signed from a CA) where I should be able to export the certificate only and make the private key non-exportable in C#. That is, if someone tries to export the certificate from certmgr, the export option will be disabled like this picture-
I want the same non-exportable option programmatically in C# while creating it. The private key usually becomes non-exportable when a .pfx/.p12 file is installed using Crypto Shell Extensions when Mark this Key is exportable is unchecked.
I can successfully create key pairs and add certificate entries in the windows key store. But Yes, export the private key option always becomes enabled that is I can't restrict the private key from export. I have tried this -
public void init(){
AsymmetricCipherKeyPair asymmetricCipherKeyPair = GetKeyPair();
X509Name issuer = this.GenerateRelativeDistinguishedName("test org");
X509Name subject = this.GenerateRelativeDistinguishedName("test user1");
Org.BouncyCastle.X509.X509Certificate cert = GenerateCertificate(issuer, subject, asymmetricCipherKeyPair.Private, asymmetricCipherKeyPair.Public);
importSelfSignedCert(asymmetricCipherKeyPair, cert);
}
private AsymmetricCipherKeyPair GetKeyPair()
{
return new Pkcs1xHandler().GenerateKeyPair(Constants.RsaKeyLength.Length2048Bits);
}
protected X509Name GenerateRelativeDistinguishedName(String commonName)
{
IDictionary attributes = new Hashtable();
IList ordering;
attributes.Add(X509Name.CN, commonName);
ordering = new ArrayList(attributes.Keys);
return new X509Name(ordering, attributes);
}
protected void importSelfSignedCert(AsymmetricCipherKeyPair asymmetricCipherKeyPair, Org.BouncyCastle.X509.X509Certificate cert)
{
try
{
int ID =1;
AsymmetricCipherKeyPair ackp = asymmetricCipherKeyPair;
var rsaPriv = Org.BouncyCastle.Security.DotNetUtilities.ToRSA(ackp.Private as RsaPrivateCrtKeyParameters);
// Setup RSACryptoServiceProvider with "KeyContainerName" set to "KeyContainer"+ enrollmentID
var csp = new CspParameters();
csp.KeyContainerName = "TestPrivKey" + ID;
csp.Flags |= CspProviderFlags.UseMachineKeyStore;
var rsaPrivate = new RSACryptoServiceProvider(csp);
// Import private key to windows keystrore, from already generated BouncyCastle rsa privatekey
rsaPrivate.ImportParameters(rsaPriv.ExportParameters(true));
//Console.Write("rsaprivate key:" + rsaPrivate.ToXmlString(true));
System.Security.Cryptography.X509Certificates.X509Certificate2 certificate = new System.Security.Cryptography.X509Certificates.X509Certificate2();
var flags = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.UserKeySet;
certificate.Import(cert.GetEncoded(), String.Empty, flags);
certificate.PrivateKey = rsaPrivate;
// opening up the windows cert store because thats where I want to save it.
System.Security.Cryptography.X509Certificates.X509Store store = new System.Security.Cryptography.X509Certificates.X509Store(System.Security.Cryptography.X509Certificates.StoreName.My, System.Security.Cryptography.X509Certificates.StoreLocation.CurrentUser);
store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.MaxAllowed);
store.Add(certificate);
store.Close();
rsaPrivate.PersistKeyInCsp = true; //persisting the key in container is important to retrieve the key later
///make non exporable
csp.Flags = CspProviderFlags.UseNonExportableKey;
var rsaPrivate2 = new RSACryptoServiceProvider(csp);
rsaPrivate2.ExportParameters(false); //restrict to export
rsaPrivate2.PersistKeyInCsp = true;
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine("Error : " + e);
Console.WriteLine(e);
Log.Print(LogLevel.High, e.ToString());
}
}
And when I try to export the certificate, I get the option to Yes, export the private key too, like the below image -
Is there any way to make the private-key non-exportable like the first image while creating it programmatically? I would grateful for any hints, references or code samples. Thanks.
To make the private key non-exportable, the CspProviderFlags.UseNonExportableKey must additionally be set when importing the key:
...
var csp = new CspParameters();
csp.KeyContainerName = "TestPrivKey" + ID;
csp.Flags |= CspProviderFlags.UseMachineKeyStore | CspProviderFlags.UseNonExportableKey; // Fix
var rsaPrivate = new RSACryptoServiceProvider(csp);
rsaPrivate.ImportParameters(rsaPriv.ExportParameters(true));
...
If this is done, the Yes, export the private key option is disabled in the wizard.
Note that in the posted code that flag is also set, but it is set too late, namely after the certificate has been saved to the store.

Sign a CSR with Azure Key Vault

How can I achieve the very basic CSR Signing HSM functionality with Azure Key Vault?
I had found a very long and manual process to somehow achieve it:
Create a private key in Key Vault
Create a CSR, digest it with SHA256
Sign the digest with the previous private key using the Sign() method
Create a local x.509 cert and append the signature
Upload the new signed cert to Key Vault
Problem is, it is manual, long (also, quite a bit of latency) and error prone. Also I haven't found a single C# code example for this, and I'm looking for EC and not RSA.
The question is, is there a simple CertificateRequest.Sign() function in Key Vault? this seems to be so basic for an HSM-like service...
Thanks
This blog post by Vitaliy Slepakov describes a solution that he created which implements the steps you listed above using C#/.NET Core.
The code is available here:
https://github.com/vslepakov/keyvault-ca/
The heart of it is the following:
byte[] certificateRequest = /* ... */;
string issuerCertificateName = /* ... */;
KeyVaultServiceClient keyVaultServiceClient = /* ... */;
X509SignatureGenerator generator = /* see next section */;
var pkcs10CertificationRequest = new Pkcs10CertificationRequest(certificateRequest);
//TODO: Validate CSR via pkcs10CertificationRequest.Verify()
var info = pkcs10CertificationRequest.GetCertificationRequestInfo();
var notBefore = DateTime.UtcNow.AddDays(-1);
// Get the RSA public key from the CSR
var asymmetricKeyParameter = Org.BouncyCastle.Security.PublicKeyFactory.CreateKey(info.SubjectPublicKeyInfo);
var rsaKeyParameters = (Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters)asymmetricKeyParameter;
var rsaKeyInfo = new RSAParameters
{
Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned(),
Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned()
};
var publicKey = RSA.Create(rsaKeyInfo);
//TODO: Validate publicKey
var certBundle = await keyVaultServiceClient.GetCertificateAsync(issuerCertificateName).ConfigureAwait(false);
var signingCert = new X509Certificate2(certBundle.Cer);
// new serial number
var serialNumber = new byte[SerialNumberLength];
RandomNumberGenerator.Fill(serialNumber);
serialNumber[0] &= 0x7F;
var subjectDN = new X500DistinguishedName(subjectName);
var request = new CertificateRequest(subjectDN, publicKey, GetRSAHashAlgorithmName(hashSizeInBits), RSASignaturePadding.Pkcs1);
// Basic constraints
request.CertificateExtensions.Add(
new X509BasicConstraintsExtension(caCert, caCert, 0, true));
// Subject Key Identifier
var ski = new X509SubjectKeyIdentifierExtension(
request.PublicKey,
X509SubjectKeyIdentifierHashAlgorithm.Sha1,
false);
request.CertificateExtensions.Add(ski);
// Authority Key Identifier
if (issuerCAKeyCert != null)
request.CertificateExtensions.Add(BuildAuthorityKeyIdentifier(issuerCAKeyCert));
else
request.CertificateExtensions.Add(BuildAuthorityKeyIdentifier(subjectDN, serialNumber.Reverse().ToArray(), ski));
if (caCert)
request.CertificateExtensions.Add(
new X509KeyUsageExtension(
X509KeyUsageFlags.DigitalSignature |
X509KeyUsageFlags.KeyCertSign |
X509KeyUsageFlags.CrlSign,
true));
else
{
// Key Usage
var defaultFlags =
X509KeyUsageFlags.DigitalSignature |
X509KeyUsageFlags.DataEncipherment |
X509KeyUsageFlags.NonRepudiation |
X509KeyUsageFlags.KeyEncipherment;
request.CertificateExtensions.Add(new X509KeyUsageExtension(defaultFlags, true));
// Enhanced key usage
request.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection {
new Oid("1.3.6.1.5.5.7.3.1"),
new Oid("1.3.6.1.5.5.7.3.2") }, true));
}
if (issuerCAKeyCert != null)
{
if (notAfter > issuerCAKeyCert.NotAfter)
{
notAfter = issuerCAKeyCert.NotAfter;
}
if (notBefore < issuerCAKeyCert.NotBefore)
{
notBefore = issuerCAKeyCert.NotBefore;
}
}
var issuerSubjectName = issuerCAKeyCert != null ? issuerCAKeyCert.SubjectName : subjectDN;
X509Certificate2 signedCert = request.Create(
issuerSubjectName,
generator,
notBefore,
notAfter,
serialNumber);
The custom X509SignatureGenerator implementation is here and used the following Key Vault SDK method:
HashAlgorithm hash = /* see GitHub */;
var digest = hash.ComputeHash(data);
var resultKeyVaultPkcs = await keyVaultClient.SignAsync(signingKey, algorithm, digest, RSASignaturePadding.Pkcs1);
Hopefully you can adapt this code to meet your needs. I'll be doing that as well. 😀
KV has a sign operation, but it's not specific to any data type (such as a cert request).
The KV .NET SDK offers this https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.keyvault.keyvaultclient.signwithhttpmessagesasync?view=azure-dotnet-legacy which is just a wrapper over REST APIs https://learn.microsoft.com/en-us/rest/api/keyvault/
This isn't exactly what you want, but I wrote some code to demonstrate C# KV encrypt/decrypt which should help you get started https://github.com/x509cert/AzureKeyVault

CNGKey.Create won't persist

I'm playing with CNGKey and the storage. I would like to store the key, and later retrieve it for encryption.
I am usingCngKey.Create and I see that it is persisted in the file system. To test access to it, immediately after the Create command I get false for CngKey.Existsm using visual studio's 'watch' window.
This happens for both RSA, using Microsoft's built in enum, and AES, using "AES" string.
My code for AES:
CngKeyCreationParameters keyParams = new CngKeyCreationParameters
{
ExportPolicy = CngExportPolicies.AllowExport,
KeyCreationOptions = CngKeyCreationOptions.MachineKey | CngKeyCreationOptions.OverwriteExistingKey,
Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider,
//KeyUsage = CngKeyUsages.Decryption
};
CngAlgorithm aesAlgorithm = new CngAlgorithm("AES");
CngKey.Create(aesAlgorithm, "mykeyAES", keyParams);
My code for RSA:
CngKeyCreationParameters keyParams = new CngKeyCreationParameters
{
ExportPolicy = CngExportPolicies.AllowExport,
KeyCreationOptions = CngKeyCreationOptions.MachineKey | CngKeyCreationOptions.OverwriteExistingKey,
Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider,
//KeyUsage = CngKeyUsages.Decryption
};
if (!CngKey.Exists(keyName, CngProvider.MicrosoftSoftwareKeyStorageProvider))
{
CngKey key = CngKey.Create(CngAlgorithm.Rsa, keyName, keyParams);
}
The only relevant information I get from searching the web, is getting to the same questions on SO which don't help me much with my specific case.
Appreciate any help!
Edit:
According to #Martheen's reply, Open has changed to:
CngKey key = CngKey.Open(keyName, CngProvider.MicrosoftSoftwareKeyStorageProvider, CngKeyOpenOptions.MachineKey);
I'm getting true on CngKey.Exists but get an exception 'keypair does not exist'
If you create the key as machine-wide, you'd have to specify it too on accessing them
CngKey.Exists("mykeyAES", CngProvider.MicrosoftSoftwareKeyStorageProvider, CngKeyOpenOptions.MachineKey));
and
CngKey.Open("mykeyRSA", CngProvider.MicrosoftSoftwareKeyStorageProvider, CngKeyOpenOptions.MachineKey));

Convert from RSACryptoServiceProvider to RSACng

I am currently using RSACryptoServiceProvider and I want to change to RSACng. I am using it to sign data. The reason for the change is that I am using Pkcs1 padding and I understand that Pss padding is preferred. We are undergoing security audits.
My question is how do I instantiate RSACng so that it uses the same private / public key each time?
With RSACryptoServiceProvider I am doing:
CspParameters cp = new CspParameters();
cp.KeyContainerName = "ContainerName";
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(cp);
passing in the container name means it uses the key that persists in the in the container store on the machine.
With RSACng, I tried this, but I get an exception: "The requested operation is not supported"
RSACng RSA = new RSACng(CngKey.Create(CngAlgorithm.Sha256, ContainerName));
I just need to be able to pass the store key name so it uses the same key each time instead of generating a new key.
If you want to create a named/persisted RSA key with CNG:
private static RSA CreatePersistedRSAKey(string name, int keySizeInBits)
{
CngKeyCreationParameters creationParameters = new CngKeyCreationParameters
{
// This is what an ephemeral key would have had
// (allows ExportParameters(true) to succeed). Adjust as desired.
//
// The default is not exportable (only applies to the private key)
ExportPolicy =
CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport,
};
creationParameters.Parameters.Add(
new CngProperty(
"Length",
BitConverter.GetBytes(keySizeInBits),
CngPropertyOptions.Persist));
// RSACng will extract the data it needs from this key object,
// but doesn't take ownership
using (CngKey key = CngKey.Create(CngAlgorithm.Rsa, name, creationParameters))
{
return new RSACng(key);
}
}
This skips the parts where you would do a try/catch around a call to CngKey.Open, or might want to delete the key (open it with CngKey.Open, and call Delete on the CngKey instance).
(CngAlgorithm.Rsa was added in net46. If you're on an older version then an equivalent would be new CngAlgorithm("RSA"))

Verify Private Key Protection before signing with RSACryptoServiceProvider

When signing data with RSACryptoServiceProvider in C#, I have a requirement to ensure the certificate was imported with strong key protection and a high security level to require the user enters the password every time they sign with the key. Here's a quick simplified sample of the signing code:
X509Store myCurrentUserStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
myCurrentUserStore.Open(OpenFlags.MaxAllowed);
X509Certificate2 currentCertificate = myCurrentUserStore.Certificates[4];
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
key.FromXmlString(currentCertificate.PrivateKey.ToXmlString(true));
byte[] signedData = Encoding.UTF8.GetBytes(originalFileContent);
byte[] signature = key.SignData(signedData, CryptoConfig2.CreateFromName("SHA256CryptoServiceProvider") as HashAlgorithm);
So what's the best way to go about checking how the certificate was installed so I can display an error message if it was not installed with strong private key protection with a high security level?
There are a couple things in your snippet that I don't understand.
Why you're opening with MaxAllowed. If you just want to read, use ReadOnly.
Why you're reading store.Certificates[4]. But presumably this is just a placeholder for "read a cert".
Why you're exporting and re-importing the key. (Especially since that would have had to prompt, which would defeat your "it needs to prompt" goal).
For #3 I'm assuming you are just looking to have a unique instance, in which case: Good news! .NET 4.6 added a GetRSAPrivateKey (extension) method to X509Certificate2 which always returns a unique instance. (And you might be excited to know about the new overload to SignData which doesn't encourage sending objects to the finalizer queue: https://msdn.microsoft.com/en-us/library/mt132675(v=vs.110).aspx)
Anyways, what I wrote here works for medium (consent) or high (password) protection. The CngKey based approach can distinguish medium from high, but the classic CAPI fallback can't tell which is which. (The classic CAPI fallback will only happen with obscure HSMs which don't have a CNG-compatible driver).
private static bool HasProtectedKey(X509Certificate2 cert)
{
if (!cert.HasPrivateKey)
{
return false;
}
using (RSA rsa = cert.GetRSAPrivateKey())
{
return HasProtectedKey(rsa);
}
}
private static bool HasProtectedKey(RSA rsa)
{
RSACng rsaCng = rsa as RSACng;
if (rsaCng != null)
{
return rsaCng.Key.UIPolicy.ProtectionLevel != CngUIProtectionLevels.None;
}
RSACryptoServiceProvider rsaCsp = rsa as RSACryptoServiceProvider;
if (rsaCsp != null)
{
CspKeyContainerInfo info = rsaCsp.CspKeyContainerInfo;
// First, try with the CNG API, it can answer the question directly:
try
{
var openOptions = info.MachineKeyStore
? CngKeyOpenOptions.MachineKey
: CngKeyOpenOptions.UserKey;
var cngProvider = new CngProvider(info.ProviderName);
using (CngKey cngKey =
CngKey.Open(info.KeyContainerName, cngProvider, openOptions))
{
return cngKey.UIPolicy.ProtectionLevel != CngUIProtectionLevels.None;
}
}
catch (CryptographicException)
{
}
// Fallback for CSP modules which CNG cannot load:
try
{
CspParameters silentParams = new CspParameters
{
KeyContainerName = info.KeyContainerName,
KeyNumber = (int)info.KeyNumber,
ProviderType = info.ProviderType,
ProviderName = info.ProviderName,
Flags = CspProviderFlags.UseExistingKey | CspProviderFlags.NoPrompt,
};
if (info.MachineKeyStore)
{
silentParams.Flags |= CspProviderFlags.UseMachineKeyStore;
}
using (new RSACryptoServiceProvider(silentParams))
{
}
return false;
}
catch (CryptographicException e)
{
const int NTE_SILENT_CONTEXT = unchecked((int)0x80090022);
if (e.HResult == NTE_SILENT_CONTEXT)
{
return true;
}
throw;
}
}
// Some sort of RSA we don't know about, assume false.
return false;
}

Categories