Home > database >  Missing last half of bytes when decrypting using AES in .NET
Missing last half of bytes when decrypting using AES in .NET

Time:06-18

I'm attempting to implement some encryption of data in .NET with a user supplied password. As I understand this, I encrypt the file with a symetric key, and encrypt this key with another key that is generated by the user. This means that a password change does not require a change to the data, just an update to the encrypted key.

When testing out the AES functions, I can encrypt my 256 bit key, but when decrypting I only get the first 16 bytes back from .NET:

public static byte[] Salt = new byte[64];
        public static byte[] IV = new byte[16];
        public static string Password1 = "PWD";
        public static byte[] Key = new byte[32];

        static void Main(string[] args)
        {
            Salt = RandomNumberGenerator.GetBytes(64);
            IV = RandomNumberGenerator.GetBytes(16);
            Key = RandomNumberGenerator.GetBytes(32);
            var pwdK1 = RandomNumberGenerator.GetBytes(32);

            byte[] aKey1 = new byte[32];
            byte[] bKey1 = new byte[32];

            using (Aes aes = Aes.Create())
            {
                aes.Mode = CipherMode.CBC;
                aes.Key = pwdK1;        //use key generated by user pwd
                aes.IV = IV;

                var str = new MemoryStream(Key);

                using (var crypStr = new CryptoStream(str, aes.CreateEncryptor(), CryptoStreamMode.Read))
                {
                    int i = crypStr.Read(aKey1, 0, 32);
                }
            }

            using (Aes aes = Aes.Create())
            {
                aes.Mode = CipherMode.CBC;
                aes.Key = pwdK1;        //use key generated by user pwd
                aes.IV = IV;

                var str = new MemoryStream(aKey1);

                using (var crypStr = new CryptoStream(str, aes.CreateDecryptor(), CryptoStreamMode.Read))
                {
                    int i = crypStr.Read(bKey1, 0, 32);
                    var p = bKey1.ToArray();
                }
            }
            //we should have Key in p/bKey1, but we only have the first 16 bytes of Key.
        }

here, pwdK1 is actually generated using a 3rd party Argon2 library, code modified for this post.

The key and IV used are the same, the mode is the same, but when reading out the decrypted key in the decrypt stage, I only see the first 16 bytes that i see in Key stored in variable p. For the first crypStr.Read I get a full 32 bytes returned, but the decrypt Read returns only 16 bytes in i. The remaining 16 bytes are all 0.

Any ideas what I could be doing wrong?

CodePudding user response:

There are two bugs in the code:

  • In .NET 6, the behavior of Read() has changed. The new behavior no longer guarantees reading until the passed buffer is completely filled or the end of the stream is reached, s. here and here. Therefore, a read loop must be implemented (as already suggested in the first comment). Alternatively, CopyTo() can be used.
    With regard to the short plaintext, it is also worth considering not using streams at all, but TransformFinalBlock(), s. here. This saves the overhead associated with streams at runtime and also shortens the code significantly. For longer plaintexts, of course, the stream pattern should be applied again.

    Here are the options in comparison (for encryption):

    Read()-loop:

    using MemoryStream str = new MemoryStream(Key);
    using CryptoStream crypStr = new CryptoStream(str, aes.CreateEncryptor(), CryptoStreamMode.Read);
    int i = 0;
    while (i < aKey1.Length) 
        i  = crypStr.Read(aKey1, i, aKey1.Length - i);
    

    CopyTo():

    using MemoryStream str = new MemoryStream(Key);
    using CryptoStream crypStr = new CryptoStream(str, aes.CreateEncryptor(), CryptoStreamMode.Read);
    using MemoryStream cpyStr = new MemoryStream();
    crypStr.CopyTo(cpyStr); 
    aKey1 = cpyStr.ToArray();
    

    TransformFinalBlock():

    aKey1 = aes.CreateEncryptor().TransformFinalBlock(Key, 0, Key.Length); 
    
  • The Aes implementation applies PKCS#7 padding by default, i.e. in case of a plaintext of 32 bytes a full block (16 bytes with AES) is appended, so that the buffer for the ciphertext requires a size of 48 bytes. During decryption the padding is removed again, so that the buffer for the decrypted data may still correspond to the plaintext size of 32 bytes.
    Since the plaintext fulfills the size criterion, according to which the size of the plaintext must correspond to an integer multiple of the blocksize, it's also worth considering whether padding should be disabled. Then the buffer for the ciphertext may also be 32 bytes large. If the size criterion is not met, padding must of course be applied.

    Padding can be disabled with aes.Padding = PaddingMode.None;.


Full code without padding using TransformFinalBlock():

byte[] IV = RandomNumberGenerator.GetBytes(16);
byte[] Key = RandomNumberGenerator.GetBytes(32);
byte[] pwdK1 = RandomNumberGenerator.GetBytes(32);

byte[] aKey1 = new byte[32];
byte[] bKey1 = new byte[32];

using (Aes aes = Aes.Create())
{
    aes.Mode = CipherMode.CBC;
    aes.Key = pwdK1;        
    aes.IV = IV;
    aes.Padding = PaddingMode.None; // Fix: Disable padding

    aKey1 = aes.CreateEncryptor().TransformFinalBlock(Key, 0, Key.Length);  // Fix: TransformFinalBlock 
}

using (Aes aes = Aes.Create())
{
    aes.Mode = CipherMode.CBC;
    aes.Key = pwdK1;       
    aes.IV = IV;
    aes.Padding = PaddingMode.None;

    bKey1 = aes.CreateDecryptor().TransformFinalBlock(aKey1, 0, aKey1.Length);
}

Console.WriteLine(Convert.ToHexString(Key));
Console.WriteLine(Convert.ToHexString(aKey1));
Console.WriteLine(Convert.ToHexString(bKey1));
  • Related