AlexMelw
AlexMelw

Reputation: 2624

Encrypting data larger than the RSA key (modulus) size. Encryption of splitted data into chunks, seems not to be working. Any ideas?

Please, read this first:

  1. Generate random key for symmetric algorithm
  2. Encrypt the data using symmetric algorithm and the random key
  3. Encrypt the random key using your public key and store it in the encrypted form next (or before) the data.

But I must accomplish the given task by all means. Thanks for understanding.

I greatly abstracted and simplified the example to make it clear about the problem I'm struggling with.

string data = "message 700-1300 letters long";
byte[] bytes = data.ToUtf8EncodedByteArray();

// I'm splitting utf8 encoded bytes into chunks of 32 bytes each.
IEnumerable<IEnumerable<byte>> batches = bytes.ChunkBy(32);

LinkedList<byte[]> encryptedChunks = new LinkedList<byte[]>();
LinkedList<byte[]> decryptedChunks = new LinkedList<byte[]>();

using (var rsa = new RSACryptoServiceProvider(2048))
{
    rsa.PersistKeyInCsp = false;
    _publicKey = rsa.ExportParameters(false);
    _privateKey = rsa.ExportParameters(true);

    rsa.ImportParameters(_publicKey);

    byte[] encryptedBatch = null;
    foreach (IEnumerable<byte> batch in batches)
    {
        encryptedBatch = rsa.Encrypt(batch.ToArray(), true);
        encryptedChunks.AddLast(encryptedBatch);
    }
    // Then encryptedChunks.ToArray() will be send over the network


    // When encrypted bytes are received at another endpoint, they have to be decrypted.
    rsa.ImportParameters(_privateKey);
    byte[] decryptedBatch = null;
    foreach (byte[] chunk in encryptedChunks)
    {
        decryptedBatch = rsa.Decrypt(chunk, true);
        decryptedChunks.AddLast(chunk);
    }
}

// After decryption of each encrypted chunk of data, I project it back into an array of bytes.
byte[] decrypted = decryptedChunks.SelectMany(chunk => chunk).ToArray();

// When trying to display decoded bytes as a string (as it was initially), instead of original message I get hieroglyphs.
Console.Out.WriteLine($"Decrypted message: {decrypted.ToUtf8String()}");

Here are extension methods (that are used within my code), if needed:

public static IEnumerable<IEnumerable<TElement>> ChunkBy<TElement>(
        this IEnumerable<TElement> source, int chunkSize)
{
    return source
        .Select((x, i) => new { Index = i, Value = x })
        .GroupBy(x => x.Index / chunkSize)
        .Select(x => x.Select(v => v.Value).ToArray())
        .ToArray();
}

public static byte[] ToUtf8EncodedByteArray(this string source)
{
    return Encoding.UTF8.GetBytes(source.ToCharArray());
}

public static string ToUtf8String(this byte[] sourceBytes)
{
    return Encoding.UTF8.GetString(sourceBytes);
}

P.S. I also tried before encrypting the data (text), encoding it into ASCII, instead of UTF8. This didn't help as well.

Any ideas? (except switching to encryption with symmetric key. As I mentioned above, I'm not allowed to do it.)

Upvotes: 0

Views: 916

Answers (1)

Michael
Michael

Reputation: 2123

I've slightly modified (look for the changed-comments) your code, it works:

public static IEnumerable<IEnumerable<TElement>> ChunkBy<TElement>(this IEnumerable<TElement> source, int chunkSize)
{
    return source
        .Select((x, i) => new { Index = i, Value = x })
        .GroupBy(x => x.Index / chunkSize)
        .Select(x => x.Select(v => v.Value).ToArray())
        .ToArray();
}

public static byte[] ToUtf8EncodedByteArray(this string source)
{
    // Changed: instead of source.ToCharArray() use source directly
    return Encoding.UTF8.GetBytes(source);
}

public static string ToUtf8String(this byte[] sourceBytes)
{
    return Encoding.UTF8.GetString(sourceBytes);
}


[STAThread]
public static void Main()
{
    // Changed: Generate some sample data...
    string data = string.Join(string.Empty, Enumerable.Repeat<string>("abcdefghijklmnopqrstuvwxyz0123456789<>!?", 100));
    byte[] bytes = data.ToUtf8EncodedByteArray();

    // I'm splitting utf8 encoded bytes into chunks of 32 bytes each.
    IEnumerable<IEnumerable<byte>> batches = bytes.ChunkBy(32);

    LinkedList<byte[]> encryptedChunks = new LinkedList<byte[]>();
    LinkedList<byte[]> decryptedChunks = new LinkedList<byte[]>();

    using (var rsa = new RSACryptoServiceProvider(2048))
    {
        rsa.PersistKeyInCsp = false;
        var _publicKey = rsa.ExportParameters(false);
        var _privateKey = rsa.ExportParameters(true);

        rsa.ImportParameters(_publicKey);

        byte[] encryptedBatch = null;
        foreach (IEnumerable<byte> batch in batches)
        {
            encryptedBatch = rsa.Encrypt(batch.ToArray(), true);
            encryptedChunks.AddLast(encryptedBatch);
        }

        rsa.ImportParameters(_privateKey);
        byte[] decryptedBatch = null;
        foreach (byte[] chunk in encryptedChunks)
        {
            decryptedBatch = rsa.Decrypt(chunk, true);
            // Changed (critical): instead of chunk (the encrypted data) use the decrypted data
            decryptedChunks.AddLast(decryptedBatch);
        }
    }

    // After decryption of each encrypted chunk of data, I project it back into an array of bytes.
    byte[] decrypted = decryptedChunks.SelectMany(chunk => chunk).ToArray();

    var data2 = decrypted.ToUtf8String();
    // Changed: Verify that input and output are the same
    var equals = data.Equals(data2);
}

Upvotes: 2

Related Questions