Home > Net >  Should I be using Iterations in AesGcm?
Should I be using Iterations in AesGcm?

Time:06-28

Having read that RijndaelManaged is deprecated and AesGcm (introduced in .NET Core 3.1) is preferred over AesManaged, I'm trying to implement AesGcm using this tutorial and this answer.

Here's my code:

/// Perform AES Encryption, returning the result as a byte array.
/// </summary>
/// <param name="bytesToEncrypt">string, file or data represented as byte array</param>
/// <param name="passwordBytes">A unique password for the encryption (must be 32 bytes?)</param>
/// <returns>The data encrypted</returns>
public byte[] EncryptData(byte[] bytesToEncrypt, byte[] passwordBytes)
{
    // Based on https://stackoverflow.com/questions/60889345/using-the-aesgcm-class/60891115#60891115

    // Get parameter sizes
    int nonceSize = AesGcm.NonceByteSizes.MaxSize;
    int tagSize = AesGcm.TagByteSizes.MaxSize;
    int cipherSize = bytesToEncrypt.Length;

    // We write everything into one big array for easier encoding
    int encryptedDataLength = 4   nonceSize   4   tagSize   cipherSize;
    Span<byte> encryptedData = encryptedDataLength < 1024
                             ? stackalloc byte[encryptedDataLength]
                             : new byte[encryptedDataLength].AsSpan();

    // Copy parameters
    BinaryPrimitives.WriteInt32LittleEndian(encryptedData.Slice(0, 4), nonceSize);
    BinaryPrimitives.WriteInt32LittleEndian(encryptedData.Slice(4   nonceSize, 4), tagSize);
    var nonce = encryptedData.Slice(4, nonceSize);
    var tag = encryptedData.Slice(4   nonceSize   4, tagSize);
    var cipherBytes = encryptedData.Slice(4   nonceSize   4   tagSize, cipherSize);

    // Generate secure nonce
    RandomNumberGenerator.Fill(nonce);

    // Encrypt
    using var aes = new AesGcm(passwordBytes);
    aes.Encrypt(nonce, bytesToEncrypt.AsSpan(), cipherBytes, tag);


    return encryptedData.ToArray();

}


/// <summary>
/// Takes in an AES encrypted byte array, decrypts it and returns the resulting unencrypted byte array.
/// </summary>
/// <param name="encryptedBytes">A string, file or object represented as a byte array that's previously been encrypted.</param>
/// <param name="passwordBytes">The password used to encrypt the data. </param>
/// <returns></returns>
public byte[] DecryptData(byte[] encryptedBytes, byte[] passwordBytes)
{
    // Decode
    Span<byte> encryptedData = encryptedBytes.AsSpan();

    // Extract parameter sizes
    int nonceSize = BinaryPrimitives.ReadInt32LittleEndian(encryptedData.Slice(0, 4));
    int tagSize = BinaryPrimitives.ReadInt32LittleEndian(encryptedData.Slice(4   nonceSize, 4));
    int cipherSize = encryptedData.Length - 4 - nonceSize - 4 - tagSize;

    // Extract parameters
    var nonce = encryptedData.Slice(4, nonceSize);
    var tag = encryptedData.Slice(4   nonceSize   4, tagSize);
    var cipherBytes = encryptedData.Slice(4   nonceSize   4   tagSize, cipherSize);

    // Decrypt
    Span<byte> plainBytes = cipherSize < 1024
                          ? stackalloc byte[cipherSize]
                          : new byte[cipherSize];
    using var aes = new AesGcm(passwordBytes);
    aes.Decrypt(nonce, cipherBytes, tag, plainBytes);

    // Convert plain bytes back into string
    return plainBytes.ToArray();
}

One thing I've noticed, is there there doesn't seem to be a place for iterations.

For example, in AesManaged, I've always iteratated through like the following, as the iterations make attacks more complicated. I've followed a similar pattern for password hashing:

//Set Rijndael symmetric encryption algorithm
var AES = Aes.Create("AesManaged");
AES.KeySize = 256;
AES.BlockSize = 128;
AES.Padding = PaddingMode.PKCS7;

//http://stackoverflow.com/questions/2659214/why-do-i-need-to-use-the-rfc2898derivebytes-class-in-net-instead-of-directly
//"What it does is repeatedly hash the user password along with the salt." High iteration counts.
var key = new Rfc2898DeriveBytes(passwordBytes, salt, 100000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
...

I appreciate the nonce will have an impact, but everything I've done before has relied on multiple iterations, so it seems odd not to be doing this in AesGcm.

AesGcm doesn't seem to have a means of iterating. Should I be iterating a part somewhere? If I should be iterating, how should I do this please?

CodePudding user response:

The first important point is the your second sample generates the IV from the password. DO NOT DO THIS! The IV needs to be unique for each encryption, particularly if the key is re-used. You're doing this properly in your AES-GCM sample, where you generate a random nonce which is sent at the start of the ciphertext.

One basic example: imagine you encrypt two messages which start with the same text, using the same shared key. If you use the same IV for both, the two ciphertexts will also be identical. You do not want this.


AES requires a key which is exactly the right length. Rfc2898DeriveBytes is used to turn an arbitrary-length password into a key which has the correct number of bytes. In your second sample, passwordBytes may be any length.

In your AES-GCM sample, passwordBytes is passed directly to the AesGcm constructor. Therefore, passwordBytes must already be exactly the right length.

If you wish, you can use Rfc2898DeriveBytes on passwordBytes before passing the resulting key to the AesGcm constructor. This would let you use a passwordBytes of any length.

CodePudding user response:

Having read that RijndaelManaged is deprecated and AesGcm (introduced in .NET Core 3.1) is preferred over AesManaged, I'm trying to implement AesGcm using this tutorial and this answer.

RijndaelManaged is deprecated but it is an implementation class. You should have been using Aes.Create instead for a long time, preferring the no-argument constructor. Generally this will give you the optimized C/C version that can utilize the AES_NI instruction set that is - very likely - part of your CPU. The "Managed" classes are implemented using .NET and are comparably dog slow.

AesGcm can and should be used directly it seems, and adds authenticity and integrity to the cipher.

using var aes = new AesGcm(passwordBytes);

Yes, no, that's not right. A password is not a key, and AES operates on keys, not passwords. So the part where the key (and possibly IV) are derived from the password is missing. This should be copied from the previous example that you mentioned. However, you should use int nonceSize = AesGcm.NonceByteSizes.MaxSize as GCM works best for a 96 bit / 12 byte nonce (which is, I suppose what that gives you, in principle GCM doesn't have a functional maximum size for the nonce).

Of course, if performance permits, you can up the number of iterations. Calculating the nonce is fine, as long as you change the salt for each new ciphertext as it should. Beware that your scheme will horribly fail if the salt and nonce do repeat, so use a 128 bit / 16 byte salt just to be sure.


AesGcm doesn't seem to have a means of iterating. Should I be iterating a part somewhere? If I should be iterating, how should I do this please?

That's correct, but that's because it assumes a fully randomized key. If you have a password, you will have to perform PBKDF2 (which the badly named Rfc2898DeriveBytes class implements) or another password based key derivation function.


Beware that AES-GCM has an upper limit of 64 GiB (minus a few bytes).

Worse, it requires you to store all the plaintext / ciphertext in a buffer. It may not be the best option for file based encryption in that sense. You can encrypt chunks with GCM as well, but then you need to e.g. perform a HMAC over the authentication tags to make sure that the separately authenticated chunks are not reordered.

AES-CBC HMAC over IV and ciphertext could be another option.

  • Related