Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using the AesGcm class

I just noticed that .NET Standard 2.1/.NET Core 3.0 finally added a class for AES-GCM encryption.

However, its API seems to be slightly different from the usual .NET crypto classes: Its Encrypt function asks for pre-allocated byte arrays for the cipher text and the tag, instead of providing them itself. Unfortunately there is no example in the docs showing proper usage of that class.

I know how to calculate the expected cipher text size for an AES encryption in theory, but I wonder whether it is really the intended approach to kind of "guess" a buffer size for the cipher text there. Usually crypto libraries provide functions that take care of those calculations.

Does someone have an example on how to properly encrypt a byte array using AesGcm?

like image 935
janw Avatar asked Mar 27 '20 15:03

janw


People also ask

How does AES-GCM work?

AES-GCM have two main functions are block cipher encryption and multiplication over the field . The authenticated encryption operation takes Initialization Vector (IV), Additional Authenticated Data (AAD),secret key and plaintext as an input in128-bit and gives a 128-bit ciphertext and authentication tag,T.

Does GCM need IV?

The AAD is not encrypted. GCM mode requires that the IV is a nonce, i.e., the IV must be unique for each execution of the mode under the given key. The steps for GCM encryption are: The hash subkey for the GHASH function is generated by applying the block cipher to the “zero" block.

What is authentication tag in AES-GCM?

The tag is sometimes called the message authentication code (MAC) or integrity check value (ICV). This Toolkit provides authenticated encryption using AES-GCM according to "RFC 5116 An Interface and Algorithms for Authenticated Encryption" [RFC 5116].

What is nonce in AES-GCM?

GCM. Nonce. A value used once during a cryptographic operation and then discarded.


1 Answers

I figured it out now.

I forgot that in GCM, the cipher text has the same length as the plain text; contrary to other encryption modes like CBC, no padding is required. The nonce and tag lengths are determined by the NonceByteSizes and TagByteSizes properties of AesGcm, respectively.

Using this, encryption can be done in the following way:

public string Encrypt(string plain)
{
    // Get bytes of plaintext string
    byte[] plainBytes = Encoding.UTF8.GetBytes(plain);
    
    // Get parameter sizes
    int nonceSize = AesGcm.NonceByteSizes.MaxSize;
    int tagSize = AesGcm.TagByteSizes.MaxSize;
    int cipherSize = plainBytes.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(_key);
    aes.Encrypt(nonce, plainBytes.AsSpan(), cipherBytes, tag);
    
    // Encode for transmission
    return Convert.ToBase64String(encryptedData);
}

Correspondingly, the decryption is done as follows:

public string Decrypt(string cipher)
{
    // Decode
    Span<byte> encryptedData = Convert.FromBase64String(cipher).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(_key);
    aes.Decrypt(nonce, cipherBytes, tag, plainBytes);
    
    // Convert plain bytes back into string
    return Encoding.UTF8.GetString(plainBytes);
}

See dotnetfiddle for the full implementation and an example.

Note that I wrote this for network transmission, so everything is encoded into one, big base-64 string; alternatively, you can return nonce, tag and cipherBytes separately via out parameters.

The network setting is also the reason why I send the nonce and tag sizes: The class might be used by different applications with different runtime environments, which might have different supported parameter sizes.

like image 65
janw Avatar answered Oct 22 '22 11:10

janw