I want to protect my RSA private key with a password (who wouldn't) but the following C# fails:
SecureString pw = new SecureString();
pw.AppendChar('x');
CspParameters prms = new CspParameters();
prms.KeyPassword = pw;
RSACryptoServiceProvider crypto = new RSACryptoServiceProvider(prms);
byte[] encrypted = crypto.Encrypt(Encoding.ASCII.GetBytes("encryptme"), true);
...with the CryptographicException: "Invalid type specified". If I take the KeyPassword assignment out it works fine.
What am I, or Microsoft, doing wrong?
Setting CspParameters.KeyPassword is equivalent to calling CryptSetProvParam with PP_KEYEXCHANGE_PIN (or PP_SIGNATURE_PIN). This flag is not supported by the default Microsoft crypto-service-provider (it is intended for use with smartcard-based CSPs).
You might want to try setting
prms.Flags = CspProviderFlags.UseUserProtectedKey;
or alternatively generating a non-persistent key-pair, exporting it and encrypting it with a key derived from a password yourself.
Related
I am learning about Cryptography in .NET and I wrote the following function as a test:
byte[] foo(byte[] input, string keyContainerName)
{
CngKey key = CngKey.Open(keyContainerName);
RSACng rsa = new RSACng(key);
rsa.KeySize = 2048;
byte[] v = rsa.Encrypt(input, RSAEncryptionPadding.OaepSHA512);
CngKey keyb = CngKey.Open(keyContainerName);
RSACng rsab = new RSACng(keyb);
rsab.KeySize = 2048;
return rsab.Decrypt(v, RSAEncryptionPadding.OaepSHA512);
}
When I try executing it, rsab.Decrypt() throws a Cryptographic exception with the message: "The parameter is incorrect.".
Why is this happening? Where did I go wrong?
P.S. I previously created a key pair in the KSP with CngKey.Create(). foo is called with keyContainerName beeing the keyName passed to CngKey.Create().
If you want to create an app that does symmetric and asymmetric encryption and decryption, You can try integrating ExpressSecurity library via NuGet
More info: https://github.com/sangeethnandakumar/Express-Security-Library
AES - Symetric Encryption (For files)
var password = "sangeeth123";
var inputPath = "C:\sample.txt";
var outputPath = "C:\sample.txt.aes";
//AES Encription
AESEncription.AES_Encrypt(inputPath, password);
//AES Description
AESEncription.AES_Decrypt(outputPath, password);
RSA - Asymmetric Encryption (For strings and text)
//Generate Keys
var publicKeyPath = "C:\public_key.rsa";
var privateKeyPath = "C:\private_key.rsa";
RSAEncription.MakeKey(publicKeyPath, privateKeyPath);
var input = "sangeeth"
//RSA Encription
var ciphertext = RSAEncription.EncryptString(input, publicKeyPath);
//RSA Description
input = RSAEncription.DecryptString(ciphertext, privateKeyPath);
I am fetching my certificate from Azure Key Vault using GetSecretAsync() method and then I am expecting to get the byte[] of the private key and the certificate eventually.
I have my application in .netcore3.1
This is how my code looks like :
var certWithPrivateKey = Client.GetSecretAsync(ConfigurationSettings.AppSettings["AKVEndpoint"], ConfigurationSettings.AppSettings["CertName"]).GetAwaiter().GetResult();
var privateKeyBytes = Convert.FromBase64String(certWithPrivateKey.Value);
X509Certificate2 x509Certificate = new X509Certificate2(privateKeyBytes);
var privateKey = x509Certificate.GetRSAPrivateKey() as RSA;
I get a valid privateKey of type RSACng, but any operation (tried ExportRSAPrivateKey()) on that throws an error of "'privateKey.ExportRSAPrivateKey()' threw an exception of type 'Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException'" and "The requested operation is not supported."
I am not sure how to proceed next here to get the byte[] of the private key and certificate.
Since you do actually seem to need to export: Your current code doesn't load the private key as exportable, so it can't be exported. The fix is to assert exportability:
X509Certificate2 x509Certificate =
new X509Certificate2(privateKeyBytes, "", X509KeyStorageFlags.Exportable);
If that's not enough, then you're encountering the difference between CAPI exportability and CNG exportability (Windows older, and newer, crypto libraries). If the private key from a PFX/PKCS#12 gets loaded into CNG it's only "encrypted exportable", but ExportParameters is plaintext-export.
There's a workaround, though... export it encrypted, then import that somewhere else with a more flexible export policy, then export again.
This snippet uses the .NET Core 3.0+ ExportPkcs8PrivateKey() method, since that's the format you want your data in, and new .NET 5 PemEncoding class to simplify turning the DER encoded output into PEM+DER output. If your exporter is on .NET Framework, this is a more complex problem. For .NET Standard 2.0 there's not really a clean solution (reflect call the methods for .NET Core/.NET 5, otherwise use the Windows-specific version for .NET Framework?).
byte[] pkcs8PrivateKey;
using (RSA privateKey = x509Certificate.GetRSAPrivateKey())
{
pkcs8PrivateKey = ExportPrivateKey(privateKey);
}
File.WriteAllText(
"tls.cer",
new string(PemEncoding.Write("CERTIFICATE", x509Certificate.RawData));
File.WriteAllText(
"tls.key",
new string(PemEncoding.Write("PRIVATE KEY", pkcs8PrivateKey));
...
private static byte[] ExportPrivateKey(RSA privateKey)
{
try
{
// If it's plaintext exportable, just do the easy thing.
return privateKey.ExportPkcs8PrivateKey();
}
catch (CryptographicException)
{
}
using (RSA exportRewriter = RSA.Create())
{
// Only one KDF iteration is being used here since it's immediately being
// imported again. Use more if you're actually exporting encrypted keys.
exportRewriter.ImportEncryptedPkcs8PrivateKey(
"password",
privateKey.ExportEncryptedPkcs8PrivateKey(
"password",
new PbeParameters(
PbeEncryptionAlgorithm.Aes128Cbc,
HashAlgorithmName.SHA256,
1)),
out _);
return exportRewriter.ExportPkcs8PrivateKey();
}
}
I have a X509Certificate2 variable and I'm trying to cast the private key of the variable to a RSACryptoServiceProvider
RSACryptoServiceProvider pkey = (RSACryptoServiceProvider)cert.PrivateKey;
However I get this exception.
System.InvalidCastException: 'Unable to cast object of type 'System.Security.Cryptography.RSACng' to type 'System.Security.Cryptography.RSACryptoServiceProvider'.'
It's weird that this happens because other answers in SO suggested the same procedure as mine but I get an exception. Any solutions to this?
So after a few tries and discussions in the comments I came up with the following solution.
RSA rsa = (RSA)cert.PrivateKey;
(cert.PrivateKey as RSACng).Key.SetProperty(
new CngProperty(
"Export Policy",
BitConverter.GetBytes((int)CngExportPolicies.AllowPlaintextExport),
CngPropertyOptions.Persist));
RSAParameters RSAParameters = rsa.ExportParameters(true);
AsymmetricCipherKeyPair keypair = DotNetUtilities.GetRsaKeyPair(RSAParameters);
The problem was that the variable rsa wasn't exportable. To change this I set a new CngProperty for the export policy. Works perfectly now
Just wanted to note that there's also an extension method that can be used:
using System.Security.Cryptography.X509Certificates;
...
//certificate is a X509Certificate2
using (var rsa = certificate.GetRSAPrivateKey())
{
//the var rsa is an RSA object
//...
}
In my case the same problem was happening when trying to convert local store certificate to RSACryptoServiceProvider as below:
RSACryptoServiceProvider encryptProvider =
certificate.PrivateKey as RSACryptoServiceProvider;
Issue fixed by using RSA instead of RSACryptoServiceProvider.
Putting instructions here in case if someone will be curious how to do that )).
To store some certificate into your machine open Visual Studio Developer Command and type following:
makecert -n "CN=JohnDoe" -sr currentuser -ss someCertStore
...where you can specify and values instead of "JohnDoe" and "demoCertStore".
Now you can use the below code to access certificates from the local certificate store:
public class Program
{
static void DumpBytes(string title, byte[] bytes)
{
Console.Write(title);
foreach (byte b in bytes)
{
Console.Write("{0:X} ", b);
}
Console.WriteLine();
}
static void Main(string[] args)
{
// This will convert our input string into bytes and back
var converter = new ASCIIEncoding();
// Get a crypto provider out of the certificate store
// should be wrapped in using for production code
var store = new X509Store("someCertStore", StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
// should be wrapped in using for production code
X509Certificate2 certificate = store.Certificates[0];
RSA rsa = (RSA)certificate.PrivateKey;
(certificate.PrivateKey as RSACng)?.Key.SetProperty(
new CngProperty(
"Export Policy",
BitConverter.GetBytes((int)CngExportPolicies.AllowPlaintextExport),
CngPropertyOptions.Persist));
string messageToSign = "This is the message I want to sign";
Console.WriteLine("Message: {0}", messageToSign);
byte[] messageToSignBytes = converter.GetBytes(messageToSign);
// need to calculate a hash for this message - this will go into the
// signature and be used to verify the message
// Create an implementation of the hashing algorithm we are going to us
// should be wrapped in using for production code
DumpBytes("Message to sign in bytes: ", messageToSignBytes);
HashAlgorithm hasher = new SHA1Managed();
// Use the hasher to hash the message
byte[] hash = hasher.ComputeHash(messageToSignBytes);
DumpBytes("Hash for message: ", hash);
// Now sign the hash to create a signature
byte[] signature = rsa.SignHash(hash, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
DumpBytes("Signature: ", messageToSignBytes);
// Now use the signature to perform a successful validation of the mess
bool validSignature = rsa.VerifyHash(hash: hash,
signature: signature,
hashAlgorithm: HashAlgorithmName.SHA1,
padding: RSASignaturePadding.Pss);
Console.WriteLine("Correct signature validated OK: {0}", validSignature);
// Change one byte of the signature
signature[0] = 99;
// Now try the using the incorrect signature to validate the message
bool invalidSignature = rsa.VerifyHash(hash: hash,
signature: signature,
hashAlgorithm: HashAlgorithmName.SHA1,
padding: RSASignaturePadding.Pss);
Console.WriteLine("Incorrect signature validated OK: {0}", invalidSignature);
Console.ReadKey();
}
You can avoid the code that is setting the export policy altogether by simply creating the certificate with the export policy already being correct. I used the New-SelfSignedCertificate PowerShell utility to create a certificate that was exportable from inception.
PS C:>New-SelfSignedCertificate -CertStoreLocation "Cert:\CurrentUser\" -Subject "CN=JUSTIN" -KeyExportPolicy Exportable
This negates the need for:
(certificate.PrivateKey as RSACng)?.Key.SetProperty(new CngProperty("Export Policy", BitConverter.GetBytes((int)CngExportPolicies.AllowPlaintextExport),CngPropertyOptions.Persist));
I am trying to sign some data using a certificate private key. The issue I'm finding is that the signature is different depending on if I'm executing it locally or on a server.
I'm using the following code as a test, running under the same user both locally and on the server:
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace TestSignature
{
class Program
{
static void Main(string[] args)
{
var key = SigningKeyFromCertificate(StoreName.My, StoreLocation.LocalMachine, X509FindType.FindByThumbprint, "thumbprint");
var alg = CryptoConfig.MapNameToOID("SHA256");
var data = Encoding.UTF8.GetBytes("test");
var sig = key.SignData(data, alg);
Console.WriteLine(Convert.ToBase64String(sig));
}
private static RSACryptoServiceProvider SigningKeyFromCertificate(StoreName storeName, StoreLocation storeLocation, X509FindType findType, string findValue)
{
X509Store store = new X509Store(storeName, storeLocation);
store.Open(OpenFlags.ReadOnly);
var certs = store.Certificates.Find(findType, findValue, false);
if (certs?.Count > 0)
{
var cert = certs[0];
if (cert.HasPrivateKey)
{
// Force use of Enhanced RSA and AES Cryptographic Provider to allow use of SHA256.
var key = cert.PrivateKey as RSACryptoServiceProvider;
var enhanced = new RSACryptoServiceProvider().CspKeyContainerInfo;
var parameters = new CspParameters(enhanced.ProviderType, enhanced.ProviderName, key.CspKeyContainerInfo.UniqueKeyContainerName);
return new RSACryptoServiceProvider(parameters);
}
else
{
throw new Exception($"No private key access to cert '{findValue}.'");
}
}
else
{
throw new Exception($"Cert '{findValue}' not found!");
}
}
}
}
Locally, I get the following signature:
YUjspKhLl7v3u5VQkh1PfHytMTpEtbAftxOA5v4lmph3B4ssVlZp7KedO5NW9K5L222Kz9Ik9/55NirS0cNCz/cDhEFRtD4daJ9qLRuM8oD5hCj6Jt9Vc6WeS2he+Cqfoylnv4V9plfi1xw8y7EyAf4C77BGkXOdyP5wyz2Xubo=
On the server, I get this one instead:
u1RUDwbBlUpOgNNkAjXhYEWfVLGpMOa0vEfm6PUkB4y9PYBk1lDmCAp+488ta+ipbTdSDLM9btRqsQfZ7JlIn/dIBw9t5K63Y7dcDcc7gDLE1+umLJ7EincMcdwUv3YQ0zCvzc9RrP0jKJManV1ptQNnODpMktGYAq1KmJb9aTY=
Any idea of what could be different? I would think, with the same certificate, the same code, and the same data, the signature should be the same.
(The example is written in C# 4.5.2.)
You have some code to reopen the CAPI key handle under PROV_RSA_AES:
// Force use of Enhanced RSA and AES Cryptographic Provider to allow use of SHA256.
var key = cert.PrivateKey as RSACryptoServiceProvider;
var enhanced = new RSACryptoServiceProvider().CspKeyContainerInfo;
var parameters = new CspParameters(
enhanced.ProviderType,
enhanced.ProviderName,
key.CspKeyContainerInfo.UniqueKeyContainerName);
return new RSACryptoServiceProvider(parameters);
But key.CspKeyContainerInfo.UniqueKeyContainerName isn't the name of the key (it's the name of the file on disk where the key lives), so you're opening a brand new key (you're also generating a new ephemeral key just to ask what the default provider is). Since it's a named key it persists, and subsequent application executions resolve to the same key -- but a different "same" key on each computer.
A more stable way of reopening the key is
var cspParameters = new CspParameters
{
KeyContainerName = foo.CspKeyContainerInfo.KeyContainerName,
Flags = CspProviderFlags.UseExistingKey,
};
(since the provider type and name aren't specified they will use the defaults, and by saying UseExistingKey you get an exception if you reference a key that doesn't exist).
That said, the easiest fix is to stop using RSACryptoServiceProvider. .NET Framework 4.6 (and .NET Core 1.0) have a(n extension) method on X509Certificate2, GetRSAPrivateKey(), it returns an RSA (which you should avoid casting) which is usually RSACng (on Windows), but may be RSACryptoServiceProvider if only CAPI had a driver required for a HSM, and may be some other RSA in the future. Since RSACng handles SHA-2 better there's almost never a need to "reopen" the return object (even if it's RSACryptoServiceProvider, and even if the type isn't PROV_RSA_AES (24), that doesn't mean the HSM will fail to do SHA-2).
We're trying to do this for our C# (.net 3.5) application on our XP SP2 machines (Win7 later).
In our installer (created by VS2008), we're encrypting our connection string with AES key and iv, and then creating a RSA key-pair and storing them in MachineKeyStore. The installer will use the RSA public key to encrypt the AES key and iv, and store the encrpted key and iv with the encrypted connection string.
After installation, our application will read the encrypted connection string with the encrypted AES key and iv back, and use the RSA private key (from MachineKeyStore) to decrypt the AES key and iv and then decrypt the connection string with the AES key and iv.
The installer and our application share a constant string for the container name of the MachineKeyStore.
I know where the key-pair is stored, so I can monitor it to see if key-pair is deleted, updated or created.
I did some tests and found some interesting things (unexpected) but don't know why it is like that. My user account is Admin account.
The installer can delete the stored key-pair created by our application and create a new one immediately with the same container name;
The installer can update key-pair created by our application (not delete and create again, it is overwriting, I think - but this should not happen according to the doc)
Our application cannot delete the key-pair created by the installer: CryptographicException: Keyset does not exist. exception will happen when the key-pair actually exists there;
Our application cannot create a new one when the installer-created key-pair is there: CryptographicException: Keyset does not exist.
Our application cannot access the key-pair created by the installer,
CryptographicException: Access is denied. will happen in that case. The encryption in the installer works with AES and RSA public key. When the application tries to use the stored private key to do decryption, an "Access is denied" exception will occur.
Our code follows:
public static void CreateRSAKeyPair(string keyContainerName)
{
DeleteRSAKeyPair(keyContainerName);
CspParameters cspParams = new CspParameters();
cspParams.KeyContainerName = keyContainerName;
cspParams.Flags |= CspProviderFlags.UseMachineKeyStore;
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cspParams))
{
rsa.PersistKeyInCsp = false;
}
}
public static void DeleteRSAKeyPair(string keyContainerName)
{
CspParameters cspParams = new CspParameters();
cspParams.KeyContainerName = keyContainerName;
cspParams.Flags |= CspProviderFlags.UseMachineKeyStore;
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cspParams))
{
rsa.PersistKeyInCsp = false;
try
{
rsa.Clear();
}
catch (CryptographicException ex)
{
Log.logItem(LogType.Exception, "RSA key clear error, can be ignored", "SecurityMgr::DeleteRSAKeyPair()", "CryptographicException msg=" + ex.ToString());
}
}
}
Code to access private key for decryption:
private static byte[] RSADecrypt(byte[] inputData, string keyContainerName)
{
byte[] resultData = null;
try
{
CspParameters cspParams = new CspParameters();
cspParams.Flags |= CspProviderFlags.UseMachineKeyStore;
cspParams.KeyContainerName = keyContainerName;
using (RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParams))
{
//rsaProvider.PersistKeyInCsp = true;
//private key
RSAParameters rsaParams = rsaProvider.ExportParameters(true);
rsaProvider.ImportParameters(rsaParams);
resultData = rsaProvider.Decrypt(inputData, false);
}
}
catch (CryptographicException ex)
{
string msg = "CryptographicException: keyContainerName=" + keyContainerName + "\nmsg=" + ex.ToString();
Log.logItem(LogType.Exception, "RSA decryption exception", "SecurityMgr::RSADecrypt()", msg);
}
return resultData;
}
Can RSA asymmetric encryption be used like this?
EDIT:
Doing the same thing (with AES and RSA encryption) for the connection string within our application (without the installer involved) works fine.
The actual question is not very clear. However, I see a few things in your code:
You export the key pair (private and public) from the Provider into the parameters. People get confused about the boolean parameter. IT does not mean it exports ONLY the private key. If you set it to true (export private key) both your PUBLIC and PRIVATE keys will be exported.
The same keys you exported from your RSA Provider instance, you are importing back into the same provider. That does not make any sense.
Remove the ExportParameters and ImportParameters lines, they accomplish nothing. Your key(s) should already be into the RSA Provider if the container name you specified in the constructor is valid and exists.
With asymmetric cryptography you use the PRIVATE key to encrypt because that you do not share. You then use the PUBLIC key to decrypt because the other party (receiver) should only hold your PUBLIC key to be able to decrypt. If they have your PRIVATE key the whole scheme is compromised.