Why does self signed PFX X509Certificate2 private key raise a NotSupportedException? - c#

I created a self signed PFX X509Certificate2 (using this answer) but for some reason, the private key of the certificate is throwing a NotSupportedException despiste a true HasPrivateKey property.
string password = "MyPassword";
ECDsa ecdsa = ECDsa.Create();
CertificateRequest certificateRequest = new CertificateRequest("cn=foobar", ecdsa, HashAlgorithmName.SHA256);
X509Certificate2 cert = certificateRequest.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(5));
File.WriteAllBytes("e:\\mycert.pfx", cert.Export(X509ContentType.Pfx, password));
//I tried to load the with every flag without success...
X509Certificate2 loadedCert = new X509Certificate2("e:\\mycert.pfx", password);
if (loadedCert.HasPrivateKey)
{
//loadedCert.HasPrivateKey is true but loadedCert.PrivateKey raise a NotSupportedException...
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)loadedCert.PrivateKey)
{
byte[] encryptedBytes = rsa.Encrypt(Encoding.UTF8.GetBytes("Hello"), false);
byte[] decryptedBytes = rsa.Decrypt(encryptedBytes, false);
string result = Encoding.UTF8.GetString(decryptedBytes);
}
}
Some have mentioned that calling the Export of the certificate would fix the private key but it didn't work for me. I'm probably missing something but I can't figure what it could be. Is there a missing parameter somewhere?

You are creating ECDSA key pair, while X509Certificate2.PrivateKey supports only DSA and RSA private keys that are stored in legacy cryptographic service provider (CSP). ECDSA is always stored in key storage provider (KSP) which is not supported by this property. Instead, you must use GetECDsaPrivateKey extension method: GetECDsaPrivateKey(X509Certificate2)

there are two types of algorithms for public-key cryptography(RSA and ECC). the problem is you are creating an ECC (I.E ECDsa) and then you are trying to get it as an RSA private key. which is definitely not correct. what you should do here is to use one algorithm on both sides so.
2. if you only want to Encrypt and then Decrypt piece of data, why using X509Certificate2, use AES instead. which is meant for this purpose.

Related

How can I (properly) verify a file using RSA and SHA256 with .NET?

I was following this great tutorial on digitally signing/verifying data with .NET. I modified that example code to use SHA256 and hit the "Invalid algorithm specified" exception, which led me to this SO question about signing data with SHA256 in .NET 4.0.
One of the answers from that post helped me figure out how to properly generate the digital signature by explicitly loading a SHA256-capable crypto provider without relying on an exportable private key (see the code towards the bottom of the following method where the RSACryptoServiceProvider is constructed):
static string mKeyContainerName;
static byte[] SignText(string text, string publicCertPath)
{
// Access Personal (MY) certificate store of current user
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
// Load the certificate we'll use to verify the signature from a file.
X509Certificate2 publicCert = new X509Certificate2(publicCertPath);
publicCert.Verify();
string publicHash = publicCert.GetCertHashString();
// Find the certificate we'll use to sign
X509Certificate2 privateCert = null;
foreach(X509Certificate2 cert in store.Certificates)
{
if(cert.GetCertHashString() == publicHash)
{
// We found it. Get its associated private key
privateCert = cert;
break;
}
}
store.Close();
if(privateCert == null)
{
throw new Exception("No valid private cert was found");
}
// Hash the string
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] data = encoding.GetBytes(text);
SHA256Managed sha256 = new SHA256Managed();
byte[] hash = sha256.ComputeHash(data);
// The basic crypto provider only supports SHA-1.
// Force Enhanced RSA and AES Cryptographic Provider which supports SHA-256.
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)privateCert.PrivateKey;
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
mKeyContainerName = csp.CspKeyContainerInfo.KeyContainerName;
var cspparams = new CspParameters
(
enhCsp.ProviderType, enhCsp.ProviderName, mKeyContainerName
);
csp = new RSACryptoServiceProvider(cspparams);
// Sign the hash
return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA256"));
}
It may be worth noting that I am using a self-signed cert from makecert.exe. According to another answer in that same post, these problems wouldn't occur if I included the proper -sp or -sy flags in makercert.exe. However, even after specifying either of these flags (currently using -sy 24) I still have to perform the workaround.
This implementation differs slightly from the accepted answer in that post (again, as our private key is not exportable). But that answer does indicate that the verification can be done without explicitly loading a SHA256-capable crypto provider. Thus, I should have been able to do this before returning in the method above:
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)privateCert.PrivateKey;
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
mKeyContainerName = csp.CspKeyContainerInfo.KeyContainerName;
var cspparams = new CspParameters
(
enhCsp.ProviderType, enhCsp.ProviderName, mKeyContainerName
);
csp = new RSACryptoServiceProvider(cspparams);
// Sign the hash
byte[] signature = csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA256"));
// Test to verify the signed hash with public cert
csp = (RSACryptoServiceProvider)publicCert.PublicKey.Key;
if (!csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), signature))
throw new CryptographicException();
return signature;
However, this won't verify (BTW, I've tried both SignData/VerifyData and SignHash/VerifyHash). The only way I can get it to verify is if I again explicitly load a SHA256-capable crypto provider. Unfortunately, the KeyContainerName member of the CspKeyContainerInfo constructed from a public certificate is always null. Thus, the only way I can get the data to verify is if I cache (or hard-code) the private key's KeyContainerName. Hence the reason for the mKeyContainerName field in the above method and snippet below:
// Test to verify the signed hash with public cert
csp = (RSACryptoServiceProvider)publicCert.PublicKey.Key;
enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
cspparams = new CspParameters
(
enhCsp.ProviderType, enhCsp.ProviderName, mKeyContainerName
);
csp = new RSACryptoServiceProvider(cspparams);
if (!csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), signature))
throw new CryptographicException();
This does verify, but I don't like having to hard-code the private key's KeyContainerName. The private key won't be available on the machine that is doing the verification.
Does anyone know of a better way to accomplish this? Thanks!
I stumbled on my own answer quite by accident. Turns out using a self-signed cert created with makecert.exe is the culprit here. If I use a cert created with OpenSSL or a commercial cert, I no longer have to explicitly load a SHA256-capable crypto provider. Thus I don't have to hard code the container name in the CspParameters object used to instantiate the RSACryptoServiceProvider. This signing code now works as expected:
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)privateCert.PrivateKey;
// Sign the hash
byte[] signature = csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA256"));
The same is true on the verify side:
// Test to verify the signed hash with public cert
csp = (RSACryptoServiceProvider)publicCert.PublicKey.Key;
I never found out what was different about the cert generated by makecert.exe, but it was not important for me as we are signing with a commercial cert now.

RSA encryption by supplying modulus and exponent

I am creating a C# Winforms application which POSTs data to a server over HTTPS.
The login mechanism is supposed to be like this:
I send the username to the server, it responds with rsa-modulus and rsa-exponent
I encrypt the password using these given parameters and send username + password to the server for authentication
I have tried the RSACryptoServiceProvider class, but I cannot find samples or anything said on how we can do the encryption using a given modulus and exponent?.
I think that without specifying any values, its doing default encryption parameters..
So if anybody has done this before, can they give me some hints please? thanks
UPDATE: according to the suggestion by Mr. Carsten Konig, . I have tried to do it with RSAParameters and RSA.ImportParameters, but it returns a "BAD DATA" error with cryptographic exception. My code is given below.
I have also tried RSA.FromXmlString(mykey); (where mykey contains an xml string with modulus and exp) but I also get a "BAD DATA" errror with cryptographic exception... any idea anybody? or if its some microsoft bug, can anyone suggest some other decent library to do this easily?
RSAParameters rsaparam = new RSAParameters();
rsaparam.Modulus = modbytes;
rsaparam.Exponent = expbytes;
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider() ;
RSA.ImportParameters(rsaparam);
byte[] encryptedData = RSA.Encrypt(dataToEncrypt, false)
You can do this by using the RSACryptoServiceProvider.Encrypt method. You will also need to use the RSACryptoServiceProvider.ImportParameters method and pass it an RSAParameters structure (this is where you set the exponent, modulus, etc).
Please have a look at the documentation in the link for the RSAParameters - it's very well documented what parameter you have to pass for what structure-field - should be no problem if you now the algorithm.
EDIT: here is the example straight from the MSDN-site:
class RSACSPSample
{
static void Main()
{
try
{ //initialze the byte arrays to the public key information.
byte[] PublicKey = {214,46,220,83,160,73,40,39,201,155,19,202,3,11,191,178,56,
74,90,36,248,103,18,144,170,163,145,87,54,61,34,220,222,
207,137,149,173,14,92,120,206,222,158,28,40,24,30,16,175,
108,128,35,230,118,40,121,113,125,216,130,11,24,90,48,194,
240,105,44,76,34,57,249,228,125,80,38,9,136,29,117,207,139,
168,181,85,137,126,10,126,242,120,247,121,8,100,12,201,171,
38,226,193,180,190,117,177,87,143,242,213,11,44,180,113,93,
106,99,179,68,175,211,164,116,64,148,226,254,172,147};
byte[] Exponent = {1,0,1};
//Values to store encrypted symmetric keys.
byte[] EncryptedSymmetricKey;
byte[] EncryptedSymmetricIV;
//Create a new instance of RSACryptoServiceProvider.
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
//Create a new instance of RSAParameters.
RSAParameters RSAKeyInfo = new RSAParameters();
//Set RSAKeyInfo to the public key values.
RSAKeyInfo.Modulus = PublicKey;
RSAKeyInfo.Exponent = Exponent;
//Import key parameters into RSA.
RSA.ImportParameters(RSAKeyInfo);
//Create a new instance of the RijndaelManaged class.
RijndaelManaged RM = new RijndaelManaged();
//Encrypt the symmetric key and IV.
EncryptedSymmetricKey = RSA.Encrypt(RM.Key, false);
EncryptedSymmetricIV = RSA.Encrypt(RM.IV, false);
Console.WriteLine("RijndaelManaged Key and IV have been encrypted with RSACryptoServiceProvider.");
}
//Catch and display a CryptographicException
//to the console.
catch(CryptographicException e)
{
Console.WriteLine(e.Message);
}
}
}
Please note that only the key/iv gets encrypted - not arbitrary bytes - the length of those bytes is important too!
The allowed length is described in MSDN an depends on the OS!
If you are using RSACryptoServiceProvider.ToXmlString to export the modulus and exponent that the server sends, you need to use Convert.FromBase64String.
public RSAParameters SetPublicKey(string modulus, string exponent)
{
RSAParameters result = new RSAParameters();
result.Modulus = Convert.FromBase64String(modulus);
result.Exponent = Convert.FromBase64String(exponent);
return result;
}
One additional hint that was very useful for me:
In this line,
//Set RSAKeyInfo to the public key values.
SAKeyInfo.Modulus = PublicKey;
PublicKey can also be a direct, straightforward, array of bytes that you can get from the "Public Key" field of a X509 Certificate (directly).

CryptographicException "Key not valid for use in specified state." while trying to export RSAParameters of a X509 private key

I am staring at this for quite a while and thanks to the MSDN documentation I cannot really figure out what's going. Basically I am loading a PFX file from the disc into a X509Certificate2 and trying to encrypt a string using the public key and decrypt using the private key.
Why am I puzzled: the encryption/decryption works when I pass the reference to the RSACryptoServiceProvider itself:
byte[] ed1 = EncryptRSA("foo1", x.PublicKey.Key as RSACryptoServiceProvider);
string foo1 = DecryptRSA(ed1, x.PrivateKey as RSACryptoServiceProvider);
But if the export and pass around the RSAParameter:
byte[] ed = EncryptRSA("foo", (x.PublicKey.Key as RSACryptoServiceProvider).ExportParameters(false));
string foo = DecryptRSA(ed, (x.PrivateKey as RSACryptoServiceProvider).ExportParameters(true));
...it throws a "Key not valid for use in specified state." exception while trying to export the private key to RSAParameter. Please note that the cert the PFX is generated from is marked exportable (i.e. I used the pe flag while creating the cert). Any idea what is causing the exception?
static void Main(string[] args)
{
X509Certificate2 x = new X509Certificate2(#"C:\temp\certs\1\test.pfx", "test");
x.FriendlyName = "My test Cert";
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
try
{
store.Add(x);
}
finally
{
store.Close();
}
byte[] ed1 = EncryptRSA("foo1", x.PublicKey.Key as RSACryptoServiceProvider);
string foo1 = DecryptRSA(ed1, x.PrivateKey as RSACryptoServiceProvider);
byte[] ed = EncryptRSA("foo", (x.PublicKey.Key as RSACryptoServiceProvider).ExportParameters(false));
string foo = DecryptRSA(ed, (x.PrivateKey as RSACryptoServiceProvider).ExportParameters(true));
}
private static byte[] EncryptRSA(string data, RSAParameters rsaParameters)
{
UnicodeEncoding bytConvertor = new UnicodeEncoding();
byte[] plainData = bytConvertor.GetBytes(data);
RSACryptoServiceProvider publicKey = new RSACryptoServiceProvider();
publicKey.ImportParameters(rsaParameters);
return publicKey.Encrypt(plainData, true);
}
private static string DecryptRSA(byte[] data, RSAParameters rsaParameters)
{
UnicodeEncoding bytConvertor = new UnicodeEncoding();
RSACryptoServiceProvider privateKey = new RSACryptoServiceProvider();
privateKey.ImportParameters(rsaParameters);
byte[] deData = privateKey.Decrypt(data, true);
return bytConvertor.GetString(deData);
}
private static byte[] EncryptRSA(string data, RSACryptoServiceProvider publicKey)
{
UnicodeEncoding bytConvertor = new UnicodeEncoding();
byte[] plainData = bytConvertor.GetBytes(data);
return publicKey.Encrypt(plainData, true);
}
private static string DecryptRSA(byte[] data, RSACryptoServiceProvider privateKey)
{
UnicodeEncoding bytConvertor = new UnicodeEncoding();
byte[] deData = privateKey.Decrypt(data, true);
return bytConvertor.GetString(deData);
}
Just to clarify in the code above the bold part is throwing:
string foo = DecryptRSA(ed, (x.PrivateKey as RSACryptoServiceProvider)**.ExportParameters(true)**);
I believe that the issue may be that the key is not marked as exportable. There is another constructor for X509Certificate2 that takes an X509KeyStorageFlags enum. Try replacing the line:
X509Certificate2 x = new X509Certificate2(#"C:\temp\certs\1\test.pfx", "test");
With this:
X509Certificate2 x = new X509Certificate2(#"C:\temp\certs\1\test.pfx", "test", X509KeyStorageFlags.Exportable);
For the issue I encountered a code change was not an option as the same library was installed and working elsewhere.
Iridium's answer lead me to look making the key exportable and I was able to this as part of the MMC Certificate Import Wizard.
Hope this helps someone else. Thanks heaps
I've met some similar issue, and X509KeyStorageFlags.Exportable solved my problem.
I'm not exactly an expert in these things, but I did a quick google, and found this:
http://social.msdn.microsoft.com/Forums/en/clr/thread/4e3ada0a-bcaf-4c67-bdef-a6b15f5bfdce
"if you have more than 245 bytes in your byte array that you pass to your RSACryptoServiceProvider.Encrypt(byte[] rgb, bool fOAEP) method then it will throw an exception."
For others that end up here through Google, but don't use any X509Certificate2, if you call ToXmlString on RSACryptoServiceProvider but you've only loaded a public key, you will get this message as well. The fix is this (note the last line):
var rsaAlg = new RSACryptoServiceProvider();
rsaAlg.ImportParameters(rsaParameters);
var xml = rsaAlg.ToXmlString(!rsaAlg.PublicOnly);
AFAIK this should work and you're likely hitting a bug/some limitations. Here's some questions that may help you figure out where's the issue.
How did you create the PKCS#12 (PFX) file ? I've seen some keys that CryptoAPI does not like (uncommon RSA parameters). Can you use another tool (just to be sure) ?
Can you export the PrivateKey instance to XML, e.g. ToXmlString(true), then load (import) it back this way ?
Old versions of the framework had some issues when importing a key that was a different size than the current instance (default to 1024 bits). What's the size of your RSA public key in your certificate ?
Also note that this is not how you should encrypt data using RSA. The size of the raw encryption is limited wrt the public key being used. Looping over this limit would only give you really bad performance.
The trick is to use a symmetric algorithm (like AES) with a totally random key and then encrypt this key (wrap) using the RSA public key. You can find C# code to do so in my old blog entry on the subject.
Old post, but maybe can help someone.
If you are using a self signed certificate and make the login with a different user, you have to delete the old certificate from storage and then recreate it. I've had the same issue with opc ua software

How to use c# to decrypt an MD5 hash that is encrypted using RSA1024

I trying to verify the integrity of a file at work and an having a hard time of it. I'm not very well versed with encryption and hashing, so bear with me.
I have some files that have an MD5 hash located at the end of them. I have written code to grab the bytes that I think are the hash and they seen to be uniformly 128 bytes long. In the file, just before the hash, is the keyword "RSA1024", which I have taken to mean the hash is encrypted using RSA 1024.
I have what I know is the RSA key in a file, and have read out the bytes (always 258 bytes long). I have seen many tutorials which use FromXmlString() to pull in the key, but this RSA key was not generated using the .net framework, and is not in an XML format.
I have written the following method to decrypt the hash data using the key, and it throws this error when executing ImportCspBlob() - System.Security.Cryptography.CryptographicException: Bad Version of provider.
Any ideas?
public byte[] DecryptRSA(byte[] encryptedData, byte[] keyData)
{
CspParameters param = new CspParameters();
param.Flags = CspProviderFlags.UseExistingKey;
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(param);
rsaProvider.ImportCspBlob(keyData);
byte[] decryptedData = rsaProvider.Decrypt(encryptedData, false);
return decryptedData;
}
Basic Algorithm
It may sound strange to want to "decrypt an MD5 hash", and especially when one says that they want to "decrypt it with a public key". But that is how digital signatures work. With RSA you can:
encrypt with private key
decrypt with the public key
The message digest is encrypted with the private key, and can then only be decrypted with the public key. That way you know that only the person with the private key could have signed the message.
Your key is most likely not a CSP-type key (it is most likely DER encoded). You can decrypt it using Bouncy Castle with the DER key like this:
RsaPrivateCrtKeyParameters privateKey = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(key);
byte[] rv = null;
RsaEngine eng = new RsaEngine();
eng.Init(false, privateKey);
int size = eng.GetOutputBlockSize();
rv = eng.ProcessBlock(cipher, 0, cipher.Length);
EDIT: to addressing GregS scenario that it may be a signature verify operation
If you are trying to verify a signature, you would need a certificate used to verify a message, the original message text, and the existing message signature to compare against.
What you do is pass in the original message text (minus the signature), the bytes of the message signature, and the path to the certificate you will use to verify the passed in signature.
Then, you will hash the original message and compare the result against the passed in signature.
Here is some code to illustrate:
private bool VerifySignature(string messageText, byte[] messageSignature, string certificatePath)
{
// Load the certificate from a file
X509Certificate2 cert = new X509Certificate2(certificatePath);
// Get public key
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PublicKey.Key;
// Next, hash the messageText
SHA1Managed sha1 = new SHA1Managed();
byte[] messageBytes = Encoding.Unicode.GetBytes(messageText);
byte[] hash = sha1.ComputeHash(messageBytes);
// Verify the signature with the hash
return csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), messageSignature);
}
MD5 is one-way hash. But you might check out hashing algorithm. There are some ways to break this hash, just do some research ;)

C# RSA encrypt/decrypt throws exception

I'm trying to set up a simple server side RSA encryption of a small chunk of info which is to be decrypted on the client side. Just as a proof of concept I wrote a few lines to ensure that the public and private key could be loaded from xml. However, I'm struggling to make even the most simple stuff work on my machine:
byte[] bytes = Encoding.UTF8.GetBytes("Some text");
bool fOAEP = true;
// seeding a public and private key
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
var publicKey = rsa.ToXmlString(false);
var privateKey = rsa.ToXmlString(true);
//server side
RSACryptoServiceProvider rsaServer = new RSACryptoServiceProvider();
rsaServer.FromXmlString(privateKey);
var encrypted = rsaServer.Encrypt(bytes, fOAEP);
//client side
RSACryptoServiceProvider rsaClient = new RSACryptoServiceProvider();
rsaClient.FromXmlString(publicKey);
var decrypted = rsaClient.Decrypt(encrypted, fOAEP);
The last call to Decrypt throw a CryptographicException with the message "Error occurred while decoding OAEP padding.". I must be missing something totally obvious here. Do I need more setup of the rsa instances or maybe the initial rsa seeding instance?
You should use public key for encryption and private key for decryption.
Take a look here: RSACryptoServiceProvider decrypt with public key
Now, let's get back to the
RSACryptoServiceProvider class. The
Encrypt method ONLY encrypts using
the public key and the Decrypt method
only decrypts using the private key.

Categories

Resources