Encrypting large data with asymmetric RSACryptoServiceProvider

The .NET framework provides an easy way to encrypt and decrypt sensitive data using the RSACryptoServiceProvider. There are only a few steps necessary to get the encryption working via several machines:

  • Create the key container on one machine and allow to export the private key:
    aspnet_regiis -pc <KeyContainerName> -exp
  • Export the key container:
    aspnet_regiis -px <KeyContainerName> <PathToExportXML>
  • Import the key container on the other machines:
    aspnet_regiis -pi <KeyContainerName> <PathToExportXML>
  • Grant access for appropriate accounts:
    aspnet_regiis -pa <KeyContainerName> <AccountName>

Encrypting and decrypting are straight forward API-calls:

public class RSAEncryptionService
{
    private const string ProviderName = "Microsoft Strong Cryptographic Provider";
    private const int ProviderType = 1;

    protected RSACryptoServiceProvider CreateProvider()
    {
        return new RSACryptoServiceProvider(new CspParameters(ProviderType, ProviderName)
        {
            KeyContainerName = "KeyContainerName",
            Flags = CspProviderFlags.UseExistingKey |
            CspProviderFlags.UseMachineKeyStore
        });
    }

    public string Encrypt(string decryptedData)
    {
        var encryptedBytes = CreateProvider().Encrypt(Encoding.Default.GetBytes(decryptedData), true);
        return Encoding.Default.GetString(encryptedBytes);
    }

    public string Decrypt(string encryptedData)
    {
       var decryptedBytes = CreateProvider().Decrypt(Encoding.Default.GetBytes(encryptedData), true);
       return Encoding.Default.GetString(decryptedBytes);
    }
}

So as you see with these steps you can set up an application server environment that ensures, that your applications encryption logic is working on every machine.

But what´s the problem when you run into a “Bad Length” CryptographicException? I got such an exception lately at Tekaris in a customer project. You won´t face this problem if you only encrypt “small” data like passwords with a widely seen length of 8-16 characters. You face it when you try to encrypt a string with more than 87 characters. The reason is that RSA can only encrypt data blocks that are shorter than the key length. One the one hand you could switch over to symmetric encryption but sometimes it´s not what you intentionally wanted. On the other hand you can stay with asymmetric encryption and adjust the above code example:

public class RSAEncryptionService
{
    private const string ProviderName = "Microsoft Strong Cryptographic Provider";
    private const int ProviderType = 1;
    private const int SegmentLength = 85;
    private const int EncryptedLength = 128;

    protected RSACryptoServiceProvider CreateProvider()
    {
        return new RSACryptoServiceProvider(new CspParameters(ProviderType, ProviderName)
                                                {
                                                    KeyContainerName = "KeyContainerName",
                                                    Flags = CspProviderFlags.UseExistingKey |
                                                            CspProviderFlags.UseMachineKeyStore
                                                });
    }

    public string Encrypt(string decryptedData)
    {
        var length = decryptedData.Length/SegmentLength + 1;
        var sb = new StringBuilder();

        for (var i = 0; i < length; i++)
        {
            int lengthToCopy;
            if (i == length - 1 || decryptedData.Length < SegmentLength)
                lengthToCopy = decryptedData.Length - (i*SegmentLength);
            else
                lengthToCopy = SegmentLength;

            var segment = decryptedData.Substring(i*SegmentLength, lengthToCopy);
            sb.Append(Encrypt(CreateProvider(), segment));
        }

        return sb.ToString();
    }

    public string Decrypt(string encryptedData)
    {
        var length = encryptedData.Length/EncryptedLength;
        var sb = new StringBuilder();

        for (var i = 0; i < length; i++)
        {
            var segment = encryptedData.Substring(i*EncryptedLength, EncryptedLength);
            sb.Append(Decrypt(CreateProvider(), segment));
        }

        return sb.ToString();
    }

    protected string Encrypt(RSACryptoServiceProvider rsa, string decryptedData)
    {
        var encryptedBytes = rsa.Encrypt(Encoding.Default.GetBytes(decryptedData), true);
        return Encoding.Default.GetString(encryptedBytes);
    }

    protected string Decrypt(RSACryptoServiceProvider rsa, string encryptedData)
    {
        var decryptedBytes = rsa.Decrypt(Encoding.Default.GetBytes(encryptedData), true);
        return Encoding.Default.GetString(decryptedBytes);
    }
}

The solution is to split the data, encrypt the segments and join them again. Of course the performance is not as good as with symmetric encryption. But there are two things to mention:

  • Check how often you are really using this logic in your application lifecycle and hence is it really performance critical?
  • You can remain with your (eventually environment depending) decision to use asymmetric encryption

An example is to execute an operation with an impersonated account and the credentials of that account are stored in your configuration database. Do you really retrieve the password from the database every time you need to impersonate or does your configuration service internally cache the decrypted setting? Well if not, the account must be a companywide-godfather-account to need such a complex password 🙂

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s