Search code examples
c#.net-corersa

Best way to store RSA key and use it in .Net core(looking for a cross-platform solution)


I'm currently using this link to store my RSA key in windows Key Container (machine-level) and it works fine, but I'm looking for a way that works for both Linux and windows because I will definitely deploy this project on Linux.

public static void StoreRSAKey(string containerName, string xmlKey)
{
#pragma warning disable CA1416 // Validate platform compatibility
    var parameters = new CspParameters
    {
        KeyContainerName = containerName
    };
#pragma warning restore CA1416 // Validate platform compatibility
    parameters.Flags = CspProviderFlags.UseMachineKeyStore;

    using RSACryptoServiceProvider? rsa = new RSACryptoServiceProvider(parameters);
    rsa.FromXmlString(xmlKey);
}

I have found several recommendations on the web but I need a more precise solution.

I'd be glad if anyone can help me through this.


Solution

  • Best way to store RSA key and use it in .Net core(looking for a cross-platform solution)

    IMO, the question should be, is it possible to use the RSA key in .net core for cross-platform?

    I have recently built an open-source encryption and decryption library, spending hours investigating the same question you asked. The short answer is that it is not possible to use CspParameters with Linux, it is for Windows OS (as this answer mentions). And because the answer is not possible, there is no best way.

    So to start with, let's see if we can answer the question of using the RSA key in the .net core for cross-platform.

    To do that it is straightforward, you need to do the following:

    Rsa = RSA.Create();
    Rsa.KeySize = 2048;
    

    This part does not require installing the library, it is part of netstandard2.0.

    That is it, now to export and import a key that you generate, you can do the following.

    When you RSA.Create() first you can export the key and store it anywhere safe for later usage.

    To export the private key it should be kept safe

    Rsa.ToXmlString(true);
    

    To export public key, to encrypt with

    Rsa.ToXmlString(false);
    

    When you need to import the key from a local store, you can do the following:

    Rsa.FromXmlString(asymmetricKey);
    

    This is a cross-platform compatible solution for Windows, Linux, or Mac computers.

    It is also possible to import certificates from a local computer using X509Certificate2 and use its public key for encryption and private key for decryption.

    It is also possible to import private key parameters to RSAParameters, which requires a helper method to translate XML tags from private key files:

    <RSAKeyValue>
        <Modulus>xxxx...</Modulus>
        <Exponent>xxxx</Exponent>
        <P>xxxx...</P>
        <Q>xxxx...</Q>
        <DP>xxxx...</DP>
        <DQ>xxxx...</DQ>
        <InverseQ>xxxx...</InverseQ>
        <D>xxxx...</D>
    </RSAKeyValue>
    

    But I find it easier to use FromXmlString and it is part of RSA class when creating RSA.Create(), so there is no need for a helper method, that said if performance means a lot for your project, you need to make a performance test to compare the results.

    So finally I provide a simple example of how to store and load keys:

    public static void Main(string[] args)
    {
        var rsa = RSA.Create();
        rsa.KeySize = 2048;
    
        // public key for decrypting
        var privateKey = rsa.ToXmlString(true);
        SaveKey(@"privateKey", privateKey);
    
        // public key for encrypting 
        var publicKey = rsa.ToXmlString(false);
        SaveKey(@"publicKey", publicKey);
    
        // initialize the private for use on another instance
        var rsaAnotherPlace = RSA.Create();
        rsaAnotherPlace.KeySize = 2048;
        rsaAnotherPlace.FromXmlString(LoadKey(@"privateKey"));
    }
    
    // store my keys
    public static void SaveKey(string filename, string content)
    {
        var bytes = Encoding.ASCII.GetBytes(content);
        using var fs = new FileStream(filename, FileMode.Create, FileAccess.Write);
        fs.Write(bytes, 0, bytes.Length);
    }
    
    // load key
    public static string LoadKey(string filename)
    {
        var bytes = File.ReadAllBytes(filename);
        return Encoding.ASCII.GetString(bytes);
    }
    

    I have tested the solution on Windows and Linux OS and it passes the macOS test on GitHub actions, but I have not tested it yet on macOS.

    Disclaimer: this is the open-source library I am working on.