Search code examples
c#encryptionrsaencryption-asymmetricrsacryptoserviceprovider

What is the "limit" to the variety of strings that can be encrypted/decrypted using RSACryptoServiceProvider (C#)?


Following is my implementation of Rsa encryption and decryption methods, to do it I based myself on microsoft documentation.

https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsacryptoserviceprovider?redirectedfrom=MSDN&view=net-6.0

public string Encrypt(string plainText)
{
    rsaCsp.ImportParameters(publicKey);

    byte[] data = Encoding.UTF8.GetBytes(plainText);
    byte[] cypher = rsaCsp.Encrypt(data, fOAEP: true);

    string cypherText = Convert.ToBase64String(cypher);

    return cypherText;
}

enter image description here

public string Decrypt(string cypherText)
{
    rsaCsp.ImportParameters(privateKey);

    byte[] data = Convert.FromBase64String(cypherText);
    byte[] decrypted = rsaCsp.Decrypt(data, fOAEP: true);

    string plainText = Encoding.UTF8.GetString(decrypted);

    return plainText;
}

enter image description here

I am injecting the keys and the provider so that I can reuse specific keys, so that I can even decrypt strings that were encrypted in other executions of the program. (Example: I encrypt in a test and save in a database and in another test I search in the database the encrypted string and decrypt). If I allowed public and private keys to be generated automatically this behavior would not be possible.

private readonly RSAParameters privateKey;
private readonly RSAParameters publicKey;

private readonly RSACryptoServiceProvider rsaCsp;

public RsaCryptoService(
    RSACryptoServiceProvider rsaCsp,
    RSAParameters privateKey,
    RSAParameters publicKey)
{
    this.rsaCsp = rsaCsp;
    this.privateKey = privateKey;
    this.publicKey = publicKey;
}

enter image description here

Below is the construction of the dependencies:

public static void InjectRsaCryptoService(this IServiceCollection services, string userName = "default", int keySize = 2048)
        {
            Services = services;

            RsaKeyPair rsaKeyPair = SecuritySettings.RsaKeys.SingleOrDefault(p => p.UserName == userName);
            if (rsaKeyPair == null)
                throw new lsilvpin_securityException(
                    $"O usuário {userName} não está autorizado a utilizar as ferramentas de segurança.");

            RSAParameters privateKey = rsaKeyPair.PrivateKey.AsParameter();
            RSAParameters publicKey = rsaKeyPair.PublicKey.AsParameter();

            RSACryptoServiceProvider rsaCsp = new RSACryptoServiceProvider(keySize);

            rsaCsp.ImportParameters(publicKey);
            rsaCsp.ImportParameters(privateKey);

            Services.AddTransient(sp =>
            {
                IRsaCryptoService rsa = new RsaCryptoService(rsaCsp, privateKey, publicKey);

                return rsa;
            });
        }

enter image description here

At first I thought it was working fine, but to be safe, I decided to do a performance test, sending random strings to be encrypted and decrypted.

Here's my performance test:

[Fact]
public void TestRsaCryptionPerformance()
{
    for (int i = 0; i < 1000; i++)
    {
        var plainText = RandomWordGenerator.Next(new RandomWordParameters(WordMaxLength: 495)) + "@eA8";

        IRsaCryptoService rsa = Services
        .BuildServiceProvider()
        .GetRequiredService<IRsaCryptoService>();

        string publicKey = rsa.GetPublicKey();
        string cypherText = rsa.Encrypt(plainText);
        string decrypted = rsa.Decrypt(cypherText);

        Assert.True(!string.IsNullOrWhiteSpace(publicKey));
        Assert.True(!string.IsNullOrWhiteSpace(cypherText));
        Assert.True(!string.IsNullOrWhiteSpace(decrypted));
        Assert.True(plainText.Equals(decrypted));

        Debug.WriteLine($"PublicKey: {publicKey}");
        Debug.WriteLine($"CypherText: {cypherText}");
        Debug.WriteLine($"Decrypted: {decrypted}");
    }
}

enter image description here

I'm generating random words of size between 100 and 499 with the following method:

public static string Next(RandomWordParameters? randomWordParameters = null)
        {
            randomWordParameters ??= new RandomWordParameters();
            if (randomWordParameters.CharLowerBound <= 0 || randomWordParameters.CharUpperBound <= 0)
                throw new RandomGeneratorException("Os limites inferior e superior devem ser positivos.");

            if (randomWordParameters.CharLowerBound >= randomWordParameters.CharUpperBound)
                throw new RandomGeneratorException("O limite inferior deve ser menor do que o limite superior.");

            var rd_char = new Random();
            var rd_length = new Random();
            var wordLength = rd_length.Next(randomWordParameters.WordMinLength, randomWordParameters.WordMaxLength);
            var sb = new StringBuilder();
            int sourceNumber;
            for (int i = 0; i < wordLength; i++)
            {
                sourceNumber = rd_char.Next(randomWordParameters.CharLowerBound, randomWordParameters.CharUpperBound);
                sb.Append(Convert.ToChar(sourceNumber));
            }
            var word = sb.ToString();
            return word;
        }

enter image description here

Note: I use characters obtained by passing integers between 33 and 126 to the Convert.ToChar(int) method.

When I run a loop of 1000 random words, I always find one that throws the exception below:

Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException: 'Comprimento inválido.' (Invalid length)

enter image description here

String under test: a_BdH[(4/6-9m>,9a_J/^t2GCsxo{{W#j*!R![h;TMi/42Yw7Z0yWOb3f15&P:NEI7!vTFm8W.5Q1,d?I9DR>u{M0,$YxP1Cd ]MC(gnd$){x[`@L9C7E>z()PS,>w:|?<|j3!KCkC%usV']958^}a2P(SHun'=VR?^qLcj_1nu"UUR|Bu{UlP =mEJ0RIJP#9O$a^g7&I|Q^]_pG0!h}RjiEu_Df8*(@eA8

enter image description here

I want to use RSA encryption on a system I'm implementing, but this test made me unsure. Does anyone see where this problem comes from?

Note: The encrypt/decrypt methods worked perfectly with smaller and simpler strings. (Password simulations up to about 20 in length, from weak to very strong passwords)


Solution

  • Based on comments from Klaus Gütte and Topaco, I'm satisfied with the response!

    The problem is caused by the size of the input string, the limit of the input so is the size of the input according to the question linked by Klaus, and this size depends on the size configured for the encryption key as mentioned by Topaco!

    Link to cause clarification: System.Security.Cryptography.CryptographicException : Bad length in RSACryptoserviceProvider

    Link to clarify input size limit: https://crypto.stackexchange.com/questions/42097/what-is-the-maximum-size-of-the-plaintext-message-for-rsa-oaep/42100# 42100

    More specifically: https://www.rfc-editor.org/rfc/rfc8017#section-7.1.1

    Print RSAES-OAEP-ENCRYPT standars

    enter image description here

    In my specific case, the maximum length of strings accepted by the encrypt/decrypt methods was 214 (Length). Test with 100k random strings and it worked perfectly! However, passing a string size >= 215 already generates the "Bad length" exception.

    Success with length <= 214

    enter image description here

    Fail with 215

    enter image description here

    Thanks a lot for the help guys!!