user720694
user720694

Reputation: 2075

Secure File transmission in C# using AES

I'm trying to read a file on the server (in blocks of 5KB), encrypt the block using AES and send it to the client. On the client, i decrypt the received block, and append to a file to get back the original file.

However, my decrypted block size received on the client differs from the plaintext block which is encrypted on the server.

e.g. I have a 15.5 KB exe file, so i have 15.5*1024/5*1024 = 4 blocks (round figure) to encrypt and send to client (The first 3 blocks are of 5120 bytes and last block is 512 bytes in length). On the client however, the blocks decrypted are of size 5057, 4970, 5016 and 512 bytes which equals a file size of 15.1 KB (less than what was actually sent by the server).

Here is my code snippet:

Server (sends the file to client):

FileStream fs = new FileStream("lcd.exe", FileMode.Open, FileAccess.Read);

        //block size = 5KB
        int blockSize = 5 * 1024;

        //calculate number of blocks in data
        long numberOfBlocks = fs.Length / blockSize;

        if (fs.Length % blockSize != 0) numberOfBlocks++;

        byte[] numberOfBlocksBytes = BitConverter.GetBytes(numberOfBlocks);

        //send number of blocks to client
        SendMessage(sw, numberOfBlocksBytes);

        int count = 0, offset = 0, numberOfBytesToRead=0;

        Aes objAes = new Aes();

        while (count < numberOfBlocks)
        {
            byte[] buffer;

            numberOfBytesToRead = blockSize;

            if (fs.Length < offset + blockSize)
            {
                numberOfBytesToRead = (int)(fs.Length - offset);

            }

                buffer = new byte[numberOfBytesToRead];

                fs.Read(buffer, 0, numberOfBytesToRead);

            //encrypt before sending
            byte[] encryptedBuffer = objAes.Encrypt(buffer, Encoding.Default.GetBytes(sessionKey), initVector);

            SendMessage(sw, encryptedBuffer);

            offset += numberOfBytesToRead;

            count++;

        }

        fs.Close();

Client side code which receives the file:

byte[] numberOfBlocksBytes = ReadMessage(sr);

        long numberOfBlocks = BitConverter.ToInt64(numberOfBlocksBytes, 0);

        FileStream fs = new FileStream("lcd.exe", FileMode.Append, FileAccess.Write);

        //block size = 5KB
        int blockSize = 5 * 1024;

        Aes objAes = new Aes();

        int count = 0, offset = 0;

        while (count < numberOfBlocks)
        {

            byte[] encryptedBuffer = ReadMessage(sr);

            byte[] buffer = objAes.Decrypt(encryptedBuffer, sessionKey, initVector);

            fs.Write(buffer, 0, buffer.Length);

            offset += buffer.Length;

            count++;

        }

        fs.Close();

My AES code for encryption:

private const int StandardKeyLength = 16;

    public byte[] Encrypt(byte[] plainText, byte[] key, byte[] initVector)
    {
        if (key.Length != StandardKeyLength | initVector.Length != StandardKeyLength)
        {
            throw new ArgumentException("Key Length and Init Vector should be 16 bytes (128 bits) in size");
        }

        var bPlainBytes = plainText;

        var objRm = new RijndaelManaged();

        objRm.Key = key;
        objRm.IV = initVector;
        objRm.Padding = PaddingMode.PKCS7;
        objRm.BlockSize = 128;

        var ict = objRm.CreateEncryptor(objRm.Key, objRm.IV);

        var objMs = new MemoryStream();
        var objCs = new CryptoStream(objMs, ict, CryptoStreamMode.Write);

        objCs.Write(bPlainBytes, 0, bPlainBytes.Length);

        objCs.FlushFinalBlock();

        var bEncrypted = objMs.ToArray();

        return bEncrypted;
    }

My AES code for decryption:

    public byte[] Decrypt(byte[] cipherText, byte[] key, byte[] initVector)
    {
        if (key.Length != StandardKeyLength | initVector.Length != StandardKeyLength)
        {
            throw new ArgumentException("Key Length and Init Vector should be 16 bytes (128 bits) in size");
        }

        var bCipherBytes = cipherText;

        var objRm = new RijndaelManaged();
        objRm.Key = key;
        objRm.IV = initVector;
        objRm.Padding = PaddingMode.PKCS7;
        objRm.BlockSize = 128;

        var ict = objRm.CreateDecryptor(objRm.Key, objRm.IV);
        var objMs = new MemoryStream(bCipherBytes);
        var objCs = new CryptoStream(objMs, ict, CryptoStreamMode.Read);

        var streamobj = new StreamReader(objCs);

        var strDecrypted = streamobj.ReadToEnd();

        return (Encoding.Default.GetBytes(strDecrypted));
    }

These are the results i got while debugging the while loop which sends file blocks on the server:

Actual File Size sent: 15.5 KB = 15872 bytes

Buffer size(plaintext)    Encrypted Buffer Size(Sent)       Offset   Count

5120                           5136                          5120      0

5120                           5136                         10240      1

5120                           5136                         15360      2

 512                            528                         15872      3

These are the results i got while debugging the while loop which receives file blocks on the client:

Actual File Size received: 15.1 KB = 15555 bytes


Received Buffersize    Decrypted Buffer Size       Offset   Count

5136                           5057                 5057      0

5136                           4970                10027      1

5136                           5016                15043      2

 528                            512                15555      3

It is evident that the sending and receiving code is working fine (since encrypted buffer size which is sent = received buffer size). However, the decrypted buffer size does not match the buffer size (plaintext) at all except for the last block which is of length 512 bytes.

What can be possibly wrong with decryption because of which i'm not receiving the file completely on the client side?

Upvotes: 3

Views: 973

Answers (3)

Wolfwyrd
Wolfwyrd

Reputation: 15916

You're being tripped up because in your Decrypt statement you are treating your ciphertext as if it is a string. Specifically, these lines:

var streamobj = new StreamReader(objCs);
var strDecrypted = streamobj.ReadToEnd();

return (Encoding.Default.GetBytes(strDecrypted));

Instead you want to be calling Read on your CryptoStream to read a raw byte array into a buffer. You can then return that buffer without attempting to coerce it into a string (which is what is happening by using the stream reader).

You should use something more like:

public byte[] Decrypt(byte[] cipherText, byte[] key, byte[] initVector)
{
    if (key.Length != StandardKeyLength | initVector.Length != StandardKeyLength)
    {
        throw new ArgumentException("Key Length and Init Vector should be 16 bytes (128 bits) in size");
    }

    var bCipherBytes = cipherText;

    var objRm = new RijndaelManaged();
    objRm.Key = key;
    objRm.IV = initVector;
    objRm.Padding = PaddingMode.PKCS7;
    objRm.BlockSize = 128;

    var ict = objRm.CreateDecryptor(objRm.Key, objRm.IV);
    var objMs = new MemoryStream(bCipherBytes);
    var objCs = new CryptoStream(objMs, ict, CryptoStreamMode.Read);

    var buffer = new byte[cipherText.Length];
    int readBytes = objCs.Read(buffer, 0, cipherText.Length);

    var trimmedData = new byte[readBytes];
    Array.Copy(buffer, trimmedData, readBytes);
    return trimmedData;
}

I would also suggest you take a look at the encryption utilities I maintain on Snipt. Specifically the Symmetric Encrypt and Decrypt methods. Your code as it stands has a lot of using blocks missing and a number of potential resource leaks.

Upvotes: 2

Hans Passant
Hans Passant

Reputation: 942438

    var streamobj = new StreamReader(objCs);

That's pretty unlikely to work well. The StreamReader will assume that the decrypted data is utf-8 encoded text. There is no hint whatsoever that this is actually the case from the code that encrypts the data, it takes a byte[].

Use a FileStream instead so no conversion is made at all. Also helps you avoid the Encoding.Default.GetBytes() data randomizer.

Upvotes: 1

Rahul Kumar
Rahul Kumar

Reputation: 66

Quick observation, which may just be my ignorance: Encrypt() method uses default encoding to get the session key bytes. On the receiving end the Decrypt() method uses the sessionKey itself as second parameter, i.e., without getting bytes?

Upvotes: -1

Related Questions