68

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)**);

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
kalrashi
  • 1,423
  • 3
  • 14
  • 15

7 Answers7

155

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);
Iridium
  • 23,323
  • 6
  • 52
  • 74
  • 12
    Thanks! You saved me a ton of trouble! :) – conciliator Jun 03 '14 at 07:32
  • 2
    8 years later, this saved my insanity! Thank you from the bottom of my heart. – arvind.d Jun 13 '20 at 14:46
  • What are the negative implications/gotchas from setting this exportable flag? – ElFik Jul 09 '20 at 23:22
  • 2
    @ElFik - Since the private key is used for decryption and signing, it's important that it's kept secure. When the Exportable flag is set, the private key can be saved to a file and copied elsewhere, and whilst this is good if you want to be able to e.g. copy the key to another machine, etc., it does reduce the security somewhat, as it can also be more easily extracted by an unauthorized 3rd party (e.g. if you leave your PC logged in and unlocked). This is not to say that with the Exportable flag *not* set that it is *impossible* to extract the key, but it certainly makes it more difficult. – Iridium Jul 10 '20 at 07:42
  • Got it, so the guidance could be summarized as "only use exportable if you need it, but if you need it, feel free to use it"? – ElFik Jul 10 '20 at 23:03
  • @ElFik - I think that's a pretty reasonable summary. – Iridium Jul 11 '20 at 10:17
  • @Iridium Private key is used for encryption. Its the public key that is used to decrypt.The private key ensures payload came from the source you expect it to come from, only the private key owner can encrypt. – George Oct 08 '20 at 17:05
  • @George - No, the private key is used for decryption and signing. The public key is used for encryption, and verifying signatures. – Iridium Oct 08 '20 at 18:40
  • @Iridium I sit corrected. Since certificate resides on the server that has the private key and that initiates connection to the destination that only has the public key, I assumed the private key encrypts the payload, while the public key decrypts it. This article explains it well: https://www.digicert.com/ssl/ – George Oct 09 '20 at 20:48
  • Unfortunately, this sometimes makes no difference at all. It may make the key exportable, but not *plain-text exportable*, which is apparently required to be able to export the parameters. – qid Feb 17 '21 at 14:42
24

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

Cert import wizard

will webster
  • 574
  • 5
  • 11
16

I've met some similar issue, and X509KeyStorageFlags.Exportable solved my problem.

user1187372
  • 193
  • 1
  • 9
3

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);
Gijs
  • 79
  • 6
3

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."

Jeroen Jacobs
  • 1,423
  • 3
  • 16
  • 32
  • Sorry if I have not clarified myself very well, I can break this line: string foo = DecryptRSA(ed, (x.PrivateKey as RSACryptoServiceProvider).ExportParameters(true)); into: var pvk = (x.PrivateKey as RSACryptoServiceProvider); var pvkParam = pvk.ExportParameters(true); string foo = DecryptRSA(ed, pvkParam); ...and if I do that pvk.ExportParameters(true); will throw. – kalrashi Feb 20 '12 at 08:55
  • This error is one of the most annoying errors ever written. It can mean so many things. For me, it was as Jeroen said: my message text was too large to be encrypted by the key (it's the downside of asynchronous encryption). – Aejay Feb 20 '13 at 21:57
1

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.

poupou
  • 43,413
  • 6
  • 77
  • 174
-1

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