Search code examples
c#encryptionaes

The input data is not a complete block when decrypting AES with specific offset?


I'm trying to write an encryption/decryption method for practice and after getting an initial run working, I decided to step it up and make it less vulnerable by encrypting the IV into the data. I got that working, and decided to step it up again by introducing an offset for the IV's location in the data, by adding some random data to the left hand side of the IV. Up until this point, everything was working fine, but now I'm receiving an error on decryption that states:

The input data is not a complete block.

Which with my limited knowledge of encryption and decryption is quite useless to me in debugging the issue. I've searched high and low, and none of the answers to this problem, that I've found seem to fix my issue. The answers are typically along the lines of the developer isn't decrypting a byte[] but instead something like a base 64 string.

private static Guid TestGuid = Guid.NewGuid();
private static DateTime Timestamp = DateTime.Now;
private static string key = "PPPQmyuzqKtjzYlWM3mP0aDxaxCzlsACajIkTVN4IjI=";
public static void Main()
{
    string data = TestGuid + "|" + Timestamp;
    Console.WriteLine("Request Parameter: " + data);
    string encryptedData = AESEncrypt(key, data, 1);
    Console.WriteLine("Encrypted: " + encryptedData);
    string decryptedData = AESDecrypt(key, encryptedData, 1);
    Console.WriteLine("Decrypted: " + decryptedData);
}

public static string AESEncrypt(string key, string data, int offset)
{
    if (string.IsNullOrWhiteSpace(data))
        throw new ArgumentException("Data");
    byte[] encryptedData;
    byte[] keyData = Convert.FromBase64String(key);
    using (Aes algo = Aes.Create())
    {
        algo.Key = keyData;
        algo.GenerateIV();
        algo.Padding = PaddingMode.PKCS7;
        byte[] iv = new byte[offset + 16];
        Random r = new Random();
        using (MemoryStream ms = new MemoryStream())
        {
            for (int i = 0; i < offset; i++)
                iv[i] = (byte)r.Next(1, 200);
            for (int i = 0; i < algo.IV.Length; i++)
                iv[offset + i - 1] = algo.IV[i];
            ICryptoTransform encryptor = algo.CreateEncryptor(algo.Key, algo.IV);
            using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
            {
                using (BinaryWriter bw = new BinaryWriter(cs))
                {
                    bw.Write(iv, 0, offset);
                    ms.Write(iv, offset, algo.IV.Length);
                    bw.Write(data);
                    cs.FlushFinalBlock();
                }

                encryptedData = ms.ToArray();
            }
        }
    }

    if (encryptedData != null)
        return Convert.ToBase64String(encryptedData);
    throw new Exception("An unxpected error occurred and the provided data was not encrypted.");
}

public static string AESDecrypt(string key, string data, int offset)
{
    if (string.IsNullOrWhiteSpace(data))
        throw new ArgumentException("Data");
    string decryptedData;
    byte[] keyData = Convert.FromBase64String(key);
    using (Aes algo = Aes.Create())
    {
        algo.Key = keyData;
        algo.Padding = PaddingMode.PKCS7;
        byte[] decodedData = Convert.FromBase64String(data);
        using (MemoryStream ms = new MemoryStream(decodedData))
        {
            byte[] ivData = new byte[offset + 16];
            ms.Read(ivData, 0, offset + 16);
            List<byte> iv = new List<byte>();
            for (int i = offset - 1; i < ivData.Length - 1; i++)
                iv.Add(ivData[i]);
            algo.IV = iv.ToArray();
            ICryptoTransform decryptor = algo.CreateDecryptor(algo.Key, algo.IV);
            List<byte> dataToDecrypt = new List<byte>();
            for (int i = 0; i + offset < decodedData.Length; i++)
                dataToDecrypt.Add(decodedData[i + offset]);
            using (MemoryStream ds = new MemoryStream(dataToDecrypt.ToArray()))
            {
                using (CryptoStream cs = new CryptoStream(ds, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader sr = new StreamReader(cs))
                    {
                        decryptedData = sr.ReadToEnd();
                    }
                }
            }
        }
    }

    if (!string.IsNullOrWhiteSpace(decryptedData))
        return decryptedData;
    throw new Exception("An unxpected error occurred and the provided data was not decrypted.");
}

What is causing this error, why is it causing the error, how do I resolve the error, and why does that resolution work?


Solution

  • Messing with the final encryption stream during the encryption process (feeding into it raw bytes) is a fatal mistake and should be avoided

    The problem in question occurs because

    bw.Write(iv, 0, offset);
    ms.Write(iv, offset, algo.IV.Length);
    

    Feeding the first random bytes of the modified IV to the encryption stream and the rest of it to the raw output stream, the first bytes of the modified iv will not be written to the memory stream immediately and instead will be part of the first encryption block, thus, the size of the cipher during decryption will lack some bytes, for example it lacks 1 byte where offset = 1

    But you expect them to be written immediately as independent bytes, because in the decryption part you read offset + 16 bytes from the stream and thus you read into the encrypted block and cause it to be less than the block size for AES. You can see this if you debug the code. The final size of the encrypted bytes is 0x50 while the size of bytes for decryption is 0x50 - offset = 0x4f (offset = 1)

    For solution,

    1. You can derive the IV from the Key (which is not secure if you are reusing the key) and will not have to include it in your cipher.
    2. You can prepend the IV (and your random bytes) to your encrypted buffer and read it first then use it for decryption,

    like:

    public static string AESEncrypt(string key, string data, int offset)
    {
        if (string.IsNullOrWhiteSpace(data))
            throw new ArgumentException("Data");
        byte[] encryptedData;
        byte[] keyData = Convert.FromBase64String(key);
        using (Aes algo = Aes.Create())
        {
            algo.Key = keyData;
            algo.GenerateIV();
            algo.Padding = PaddingMode.PKCS7;
            Random r = new Random();
            using (MemoryStream ms = new MemoryStream())
            {
                for (int i = 0; i < offset; i++)
                {
                    ms.WriteByte((byte)r.Next(0, 200));
                }
                ms.Write(algo.IV, 0, 16);
    
                ICryptoTransform encryptor = algo.CreateEncryptor(algo.Key, algo.IV);
                using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                {
                    using (BinaryWriter bw = new BinaryWriter(cs))
                    {
                        bw.Write(data);
                        cs.FlushFinalBlock();
                    }
    
                    encryptedData = ms.ToArray();
                }
            }
        }
    
        if (encryptedData != null)
            return Convert.ToBase64String(encryptedData);
        throw new Exception("An unxpected error occurred and the provided data was not encrypted.");
    }
    
    public static string AESDecrypt(string key, string data, int offset)
    {
        if (string.IsNullOrWhiteSpace(data))
            throw new ArgumentException("Data");
        string decryptedData;
        byte[] keyData = Convert.FromBase64String(key);
        using (Aes algo = Aes.Create())
        {
            algo.Key = keyData;
            algo.Padding = PaddingMode.PKCS7;
            byte[] decodedData = Convert.FromBase64String(data);
            using (MemoryStream ms = new MemoryStream(decodedData))
            {
                for (int i = 0; i < offset; i++) ms.ReadByte();
                byte[] iv = new byte[16];
                ms.Read(iv, 0, 16);
    
                algo.IV = iv.ToArray();
    
                ICryptoTransform decryptor = algo.CreateDecryptor(algo.Key, algo.IV);
    
                using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader sr = new StreamReader(cs))
                    {
                        decryptedData = sr.ReadToEnd();
                    }
                }
            }
        }
    
        if (!string.IsNullOrWhiteSpace(decryptedData))
            return decryptedData;
        throw new Exception("An unxpected error occurred and the provided data was not decrypted.");
    }