7

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?

DanielVip3
  • 179
  • 1
  • 2
  • 12

1 Answers1

10

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 may be 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)

kelalaka
  • 5,064
  • 5
  • 27
  • 44
  • Thanks for the helpful explaination! May I ask if it's then okay to read a bigger chunk (adding 16 bytes + 12) to decrypt the same chunk size, assuming that the tag will always be 16 bytes and nonce will always be 12? Also, another question: can AES-GCM add a single tag and nonce, when encrypting in chunks? Instead of adding a nonce and a tag and to each chunk, and after joining them, just joining chunks and then adding a single nonce and a tag. – DanielVip3 Apr 10 '21 at 11:10
  • first of all, one must [be very careful when using GCM](https://crypto.stackexchange.com/q/84357/18298). Your comments are not clear for me could you elaborate? Are you trying to do streaming and the record size is the problem for you? In that case, the are [some threats that you may consider](https://stackoverflow.com/a/54422153/1820553). Also, [ibsodium's secretstream API does this internally](https://doc.libsodium.org/secret-key_cryptography/secretstream). – kelalaka Apr 10 '21 at 11:27
  • Thanks for the advices about GCM, I actually am following all of them. I am encrypting big files (1 to 16 GB) and so I do it in different goroutines, in smaller chunks of 1024 * 16 bytes to avoid loading everything in memory, then I write the encrypted chunks to another file. When, decrypting, though, I want to read using the same chunk size of 1024 * 16 bytes, but obviously it increases by 12 + 16 bytes, so that's why I was wondering about the size. What I mean is: since all chunks are part of the same file, only encrypted in blocks, can I put in the file only a single nonce and a single tag? – DanielVip3 Apr 10 '21 at 12:07
  • It is obvious that when you limit the chunk size to `x`, you must consider the IV and tag, too. Normally, you can continue using the IV, so you only need one IV for the chunks of the file. For your question: What if the order of the chunks is changed? What are the threats? – kelalaka Apr 10 '21 at 13:10
  • I succesfully applied my "theory" of the single nonce/IV and it worked fine: I prepend the nonce only a single time to the file and not for each chunk. Can I do it for the tag too? For your question, fi the order of the chunks is changed, the meaning of the file is changed; for example, if it's an image, if you swap middle chunks you can get a totally different combination of pixels, or if you swap the first chunk you won't get a readable image. So, chunks order is crucial. – DanielVip3 Apr 10 '21 at 16:55
  • yes, you can create only one. If you look at the [GCM image](https://en.wikipedia.org/wiki/Galois/Counter_Mode) it is possible, however, you need to implement it, too. Instead, you can insert the previous [tag as associated data to the chain](https://crypto.stackexchange.com/a/86081/18298) and only send the last tag. – kelalaka Apr 10 '21 at 17:17