Search code examples
c#socketsencryptionclient-serverfile-transfer

C# Send byte[] to server, decrypt content and send it as byte[] back to client


My problem is the following:

I have a client/server application connected over sockets. My client´s task is to send a file byte-wise to the server. The server gets the bytes, decrypt them, send it back to the client and he writes them in a new file on disk.

I get everytime a serverside exception (System.Security.Cryptography.Exception: Padding is invalid and cannot be removed) at this line of code: plaintext = sr.ReadToEnd();

Could somebody help me to solve my problem?

Here is the decryption code:

public byte[] Dec(byte[] content, byte[] Key, byte[] IV, int fileLength, string filepath, int chunkSize, int bytesToRead)
    {
        byte[] contentDec;
        string plaintext = null;
        System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
        if (Key == null || Key.Length <= 0)
            throw new ArgumentNullException("Key");
        if (IV == null || IV.Length <= 0)
            throw new ArgumentNullException("IV");

        using (RijndaelManaged rijAlg = new RijndaelManaged())
        {
            rijAlg.Key = Key;
            rijAlg.IV = IV;

            ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);


            using (MemoryStream ms = new MemoryStream(content))
            {
                using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader sr = new StreamReader(cs))
                    {
                        plaintext = sr.ReadToEnd();
                        cs.FlushFinalBlock();
                    }
                    contentDec = encoding.GetBytes(plaintext);
                }
            }
        }
        return contentDec;
    }

Here is my encryption code:

    public byte[] Enc(byte[] content,byte[] Key, byte[] IV, int fileLength,string filepath, int chunkSize, int bytesToRead)
    {
        byte[] contentEnc;
        if (Key == null || Key.Length <= 0)
            throw new ArgumentNullException("Key");
        if (IV == null || IV.Length <= 0)
            throw new ArgumentNullException("IV");
        using (RijndaelManaged rijAlg = new RijndaelManaged())
        {
            rijAlg.Key = Key;
            rijAlg.IV = IV;
            ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
            using (MemoryStream ms = new MemoryStream())
            {
                using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter sw = new StreamWriter(cs))
                    {
                        sw.Write(content);
                    }
                    contentEnc = ms.ToArray();
                }
            }
        }
        return contentEnc;
    }

On client side I call encryption method like this

        int chunkSize = 1024;
        byte[] chunk = new byte[chunkSize];
        using (FileStream fileReader = new FileStream(plainPath, FileMode.Open, FileAccess.Read))
        using (FileStream filewriter = new FileStream(pathEncrypt, FileMode.Create, FileAccess.ReadWrite))
        using (BinaryReader binaryReader = new BinaryReader(fileReader))
        using (RijndaelManaged myRijndael = new RijndaelManaged())
        {
            myRijndael.GenerateKey();
            myRijndael.GenerateIV();
            Key = myRijndael.Key;
            IV = myRijndael.IV;
            int bytesToRead = (int)fileReader.Length;
            do
            {
                chunk = service.Enc(binaryReader.ReadBytes(chunkSize), Key, IV,(int)fileReader.Length, 
                    fileReader.Name, chunkSize, bytesToRead);
                filewriter.Write(chunk, 0, chunk.Length);
                bytesToRead -= chunkSize;
            } while (bytesToRead > 0);
        }

Key and IV are declared as private byte[]

On client side I call decryption method like this

        int chunkSize = 1024;
        byte[] chunk = new byte[chunkSize];
        using (FileStream fileReader = new FileStream(pathEncrypt, FileMode.Open, FileAccess.Read))
        using (FileStream filewriter = new FileStream(pathDecrypt, FileMode.Create, FileAccess.ReadWrite))
        using (BinaryReader binaryReader = new BinaryReader(fileReader))
        {
            int bytesToRead = (int)fileReader.Length;

            do
            {
                chunk = service.Dec(binaryReader.ReadBytes(chunkSize), Key, IV, (int)fileReader.Length, 
                    fileReader.Name, chunkSize, bytesToRead);
                filewriter.Write(chunk, 0, chunk.Length);
                bytesToRead -= chunkSize;
            } while (bytesToRead > 0);
        }

Edit: This is my connection establishment between client and server.

Server: var host = new ServiceHost(typeof(Service), new Uri("net.pipe://localhost"));

        host.AddServiceEndpoint(typeof(TiService),
                                new NetNamedPipeBinding(), "TestService");
        host.Open();
        Console.WriteLine("Server connection established...");
        Console.ReadKey();

Client:

var callback = new Callback();
var context = new InstanceContext(callback);
var pipeFactory =
new DuplexChannelFactory<TiService>(context,
new NetNamedPipeBinding(),
new EndpointAddress("net.pipe://localhost/TestService"));

service = pipeFactory.CreateChannel();
service.Connect();

Solution

  • Your problem start from using StreamWriter in the encryption. It's meant for writing Text file, not arbitrary file. When you call sw.Write(content), it simply call content.ToString(), which return "System.Byte[]", instead what you'd probably expect, each byte of the array. To fix it, simply write the CryptoStream, no need to use StreamWriter, like this :

    using (var rijAlg = new AesCng())
    {
        rijAlg.Key = Key;
        rijAlg.IV = IV;
        ICryptoTransform encryptor = rijAlg.CreateEncryptor();
        using (MemoryStream ms = new MemoryStream())
        {
            using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
            {
                cs.Write(content, 0, content.Length);               
            }
            contentEnc = ms.ToArray();
        }
    }
    

    You probably noticed I used AesCng instead of RijndaelManaged. Why? Because it's much faster in my test, and unless you really need non-standard block, there's no benefit of using RijndaelManaged. Also, I use the parameterless CreateEncryptor because you already set the Key & IV on the previous lines anyway.

    Same deal in the decryption. You shouldn't treat them as text, thus :

    var buffer = new byte[content.Length]; //at first its size is actual size+padding
    
    using (var rijAlg = new AesCng())
    {
        rijAlg.Key = Key;
        rijAlg.IV = IV;
    
        ICryptoTransform decryptor = rijAlg.CreateDecryptor();
    
        using (MemoryStream ms = new MemoryStream(content))
        {
            using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
            {
                var actualSize = cs.Read(buffer, 0, content.Length);
                //we write the decrypted content to the buffer, and get the actual size
    
                Array.Resize(ref buffer, actualSize);
                //then we resize the buffer to the actual size
            }
        }
    }
    return buffer;
    

    Also, your usage of the Enc and Dec is needlessly complex. It's already able to handle the whole file by itself. So to encrypt the file, simply use

    var original = File.ReadAllBytes("originalPath");
    var enc = Enc(original, rM.Key, rM.IV);
    File.WriteAllBytes("encryptedPath", enc);
    

    And to decrypt the file, just use

    var enc = File.ReadAllBytes("encryptedPath");
    var dec = Dec(enc, rM.Key, rM.IV);
    File.WriteAllBytes("decryptedPath", dec);
    

    As you can see, I throw away the fileLength,filepath, chunkSize, and bytesToRead on Enc & Dec, because your current code doesn't actually use them anyway. I've tried the code with short text file on ASCII, Unicode and UTF-8, and with large binary files, all encrypted & decrypted successfully with identical hash on the final decrypted files.

    Edit :

    Turning the code into direct filestream writing affair actually makes everything so much simpler.

    public static void Transform(string source, string target, ICryptoTransform transf)
    {
        var bufferSize = 65536;
        var buffer = new byte[bufferSize];
    
    
        using (var sourceStream = new FileStream(source, FileMode.Open))
        {
            using (var targetStream = new FileStream(target, FileMode.OpenOrCreate))
            {
                using (CryptoStream cs = new CryptoStream(targetStream, transf, CryptoStreamMode.Write))
                {
                    var bytesRead = 0;
                    do
                    {
                        bytesRead = sourceStream.Read(buffer, 0, bufferSize);
                        cs.Write(buffer, 0, bytesRead);
                    } while (bytesRead != 0);
                }
            }
    
        }
    
    
    }
    
    public static void Enc(string source, byte[] Key, byte[] IV, string target)
    {
        using (var rijAlg = new AesCng())
        {
            rijAlg.Key = Key;
            rijAlg.IV = IV;
            ICryptoTransform encryptor = rijAlg.CreateEncryptor();
            Transform(source, target, encryptor);
        }
    
    }
    public static void Dec(string source, byte[] Key, byte[] IV, string target)
    {
        using (var rijAlg = new AesCng())
        {
            rijAlg.Key = Key;
            rijAlg.IV = IV;
            ICryptoTransform decryptor = rijAlg.CreateDecryptor();
            Transform(source, target, decryptor);
        }
    
    }
    

    Usage is :

    Enc(@"originalPath", key, iv, @"encryptedPath");
    Dec(@"encrypedPath", key, iv, @"decryptedPath");