Importing and Exporting RSA Keys in C#

Viewed 1227

I am trying to export and reimport public and private RSA keys. I do it using the System.Security.Cryptography.RSA class. But it does not work as expected. Please assume the following code example

RSA rsa = RSA.Create();
File.WriteAllBytes("TestPrivateKey", rsa.ExportRSAPrivateKey());
File.WriteAllBytes("TestPublicKey", rsa.ExportRSAPublicKey());
rsa.ImportRSAPrivateKey(File.ReadAllBytes("TestPrivateKey"), out _);
rsa.ImportRSAPublicKey(File.ReadAllBytes("TestPublicKey"), out _);

string textToBeEncrypted = "Hello World";
byte[] bytesToBeEncrypted = Encoding.UTF8.GetBytes(textToBeEncrypted);
byte[] encryptedBytes = rsa.Encrypt(bytesToBeEncrypted, RSAEncryptionPadding.OaepSHA256);

byte[] decryptedBytes = rsa.Decrypt(encryptedBytes, RSAEncryptionPadding.OaepSHA256);
string result = Encoding.UTF8.GetString(decryptedBytes);

The encrytion and decryption works fine when I do not use the export and import. But when I use them I get an Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException stating that the key was not found on rsa.Decrypt function. I am not an expert on cryptography and I know there are other ways to achieve that I am doing (there are lots of examples out there), but they are all somewhat complex. This here seems pretty straight forward. But why does it not work? Should the key not be the same after reimport?

1 Answers

RSA.Create() returns an object that encapsulates a private key. The type of the object is platform dependent, e.g. under .NET Core 3.1 on a Windows 10 machine a RsaCng instance is created that encapsulates a private 2048 bit key. In the following the object is denoted as RSA object.

From such a created RSA object, the private RSA key can be exported in different formats. Since the private key also contains the public key data, the public key can also be exported.
In the same way, private or public RSA keys can be imported into the RSA object. Thereby the previous key will be overwritten. This also happens in the case of a double import. The key of the second import overwrites the key of the first import (1).
For completeness: The export/import methods used export/import private and public keys in PKCS#1 format, DER encoded. They are supported only in .NET Core and here only from version 3.0.

In addition, .NET allows encryption with an RSA object that encapsulates a private key (2). This is purely formal, for the encryption the data of the public key is used (which, as already said, also the private key contains), i.e. effectively also in this case it is encrypted with the public key.

With this, the exception thrown in the posted code can be explained:
With RSA.Create() an RSA object with a private key is created. The private and the public key are exported and imported again, first the private then the public key. The double import causes the private key to be overwritten by the public key, so effectively only the public key is imported (s. 1). This allows encryption but not decryption, which results in a corresponding exception.

As already noted in the comment, it makes sense to use a separate RSA object for encryption and decryption so that only one import is necessary per RSA object. This automatically avoids that one import overwrites the other at the wrong time. Furthermore, this implementation is more realistic, since encryption and decryption are generally implemented separately and therefore inevitably use different RSA objects.


Edit: Regarding the scenarios in the comments:

The scenarios described in the comments can also be explained:
If the import takes place in reverse order, i.e. first the public key and then the private, effectively only the private key is imported (s. 1). This can be used for encryption (s. 2) and decryption.
If no export/import takes place at all, the RSA object encapsulates the initial private key, which can also be used to encrypt and decrypt.
So both work and do not throw an exception.

Related