Search code examples
goencryptionaesaes-gcmnonce

Why AES-256 with GCM adds 16 bytes to the ciphertext size?


I'm using Golang's crypto package, crypto/aes specifically, with a 32 bytes key (so, AES-256) and the GCM method (Galois/Counter Mode).
I read from a file multiple chunks of 16384 bytes and generate a cipher block, a GCM method and a random nonce of 12 bytes.
Then, I prepend the nonce to the ciphertext in order to split them when decrypting, to access the nonce (because the size of 12 bytes is known).

One would expect that the generated ciphertext is 16384 + 12 bytes = 16396; but, when actually encrypting, I get a size of 16412 bytes, so 16 bytes are added. After I decrypt each chunk, I get the "normal" size of 16384.

Here's a fast example.

block, _ := aes.NewCipher([]byte("W9FLKnyv397R82kKuFpfp6y8usGRf49a"))
gcm, _ := cipher.NewGCM(block)
nonce = make([]byte, gcm.nonceSize()) // nonceSize is 12 bytes
_, _ = io.ReadFull(rand.Reader, nonce) // populate nonce with random data


for {
    src := make([]byte, 1024 * 16) // let's hypotise this src is a chunk of a file, full of 1024 * 16 bytes, so 16384

    encryptedBytes := gcm.Seal(nonce, nonce, src, nil) // this prepends the nonce to the src, thus adding 12 bytes in front of the encrypted string

    /*
    Now, encryptedBytes should be 16384 + 12 bytes long, but it is 16384 + 12 + 16.
    If I want to decrypt a chunk of the encrypted bytes, I need to use the size of 16384 + 12 + 16 and this makes it unpractical.
    */
}

It doesn't seem to be because of padding (also because GCM does not use padding).

So, why does AES add 16 bytes to my ciphertext?


Solution

  • AES-GCM provides confidentiality, integrity, and authentication. To provide the last two, one needs an authentication tag.

    The 16-byte tag size is always calculated, and in your case it is appended.


    More details;

    1. Ciphertext size: This is always equal to plaintext size since AES-GCM internally uses CTR mode for encryption that requires no padding.

    2. Nonce/IV size: GCM can accept large nonce sizes ( or small), however, 12-byte is recommended since it doesn't require an additional process. Any value other than 12-byte is processed with GHASH;

       if len(IV) = 96 then 
           J_0 = IV || 0^{31}1
       else 
           J_0=GHASH_H(IV||0^{s+64}||len(IV_64))
      

      Nonce is usually prepended to the message and this is the increase in the ciphertext size.

    3. Tag size: GCM always outputs 16-byte tag size. One can trim it, however, it will reduce the security against the forgeries.

      The tag is usually appended to the ciphertext.

      To comply with the NIST Special Publication 800-38D (page 8)

      The bit length of the tag, denoted t, is a security parameter, as discussed in Appendix B. In general, t may be any one of the following five values: 128, 120, 112, 104, or 96. For certain applications, t maybe 64 or 32; guidance for the use of these two tag lengths, including requirements on the length of the input data and the lifetime of the key in these cases, is given in Appendix C.

    Therefore you may see the output like (Nonce|ciphertext|tag)