AEADs icon indicating copy to clipboard operation
AEADs copied to clipboard

Consider adding tag size of 4 and 8 to AES GCM

Open d3lm opened this issue 2 years ago • 27 comments

I have noticed that AesGcm only accepts tag sizes >= 12 and <= 16. However, Node.js for example also allows tag sizes of 4 and 8 (see here). Could we implement SealedTagSize for consts::U4 and consts::U8 as well?

d3lm avatar Jul 17 '23 17:07 d3lm

NIST SP 800-38D Appendix C provides guidelines for the use of short tags, noting the following potential attack up front:

Absent the requirements and guidelines in this appendix, it may be practical for the attack to produce the hash subkey, H, after which the authentication assurance is completely lost.

While we could potentially support them, using them securely comes with a whole host of caveats described in that section which makes their usage fall into more of a "hazmat" category which requires short lived, frequently-rotated keys with bounds on how many times they can safely be used for decryption without being vulnerable to a targeted forgery attack:

For any implementation that supports 32-bit or 64-bit tags, one of the rows in Table 1 or Table 2, respectively, shall be enforced. In particular, the supported lengths for the plaintext/ciphertext and the AAD shall ensure that every valid packet satisfies the length restriction in the row, and the controlling protocol/system shall ensure that the key is changed before the authenticated decryption function is invoked more than the maximum that is given in the row. A smaller maximum may also be enforced.

tarcieri avatar Jul 17 '23 17:07 tarcieri

Oh ok, I wasn't actually aware of that - thanks for pointing that out. I guess it'd be more effort than to support short tags. It might be ok for us to restrict the tag size and ignore short tags. I also don't know if there's applications that really use short tags and it's just something I noticed while I was implementing AES-GCM that complies to Node.js using aes_gcm.

d3lm avatar Jul 17 '23 17:07 d3lm

It sounds like they are buffering and perhaps incrementally encrypting/authenticating the data.

We only have one-shot APIs at the moment, though you could largely achieve the same effect by buffering the data into a Vec<u8> and then encrypting it with the one-shot API.

tarcieri avatar Jul 18 '23 13:07 tarcieri

Yes, that's what I am doing for encryption and it works, but I have issues with incrementally decrypt data. Because parts of the encrypted data will throw an aead::Error likely because the tag isn't correct and corresponds to the tag for the entire data. Any idea what I can do?

d3lm avatar Jul 19 '23 17:07 d3lm

It's not possible to safely decrypt individual AEAD messages incrementally. At the very least, the tag needs to be checked before decryption can begin.

tarcieri avatar Jul 19 '23 17:07 tarcieri

I wonder how Node.js does that then 🤔

d3lm avatar Jul 19 '23 17:07 d3lm

My guess would be: unsafely

tarcieri avatar Jul 19 '23 17:07 tarcieri

Oh my, I just realized, with every call to update(<partial_aead_message>) they don't even check the tag only once you call final and have all the "chunks".

d3lm avatar Jul 19 '23 17:07 d3lm

But that is prolly not a good idea is it?

d3lm avatar Jul 19 '23 17:07 d3lm

Hm I guess maybe it is because only after calling final() the entire decryption process is finished and I would expect that you can not expect it to be fully checked before calling final().

d3lm avatar Jul 19 '23 17:07 d3lm

Operating on unauthenticated data can be a source of chosen ciphertext attacks which completely undermine AEAD security.

These sorts of streaming decryption APIs are "hazmat" which is difficult to use correctly with high misuse potential.

tarcieri avatar Jul 19 '23 17:07 tarcieri

Thanks a lot for your input on this 🙏 I am not a crypto expert myself so appreciate it.

d3lm avatar Jul 19 '23 17:07 d3lm

Maybe you can still consider adding short tags, that would be really nice. But for now it's fine and I can work around it and only support 128 bit tags.

d3lm avatar Jul 19 '23 18:07 d3lm

We could add an API for it, but it would require a special decryptor object which caps the max message size and number of decryptions allowed under a given key

tarcieri avatar Jul 19 '23 18:07 tarcieri

I think that sounds reasonable as long as those numbers are decently high or maybe configurable?

d3lm avatar Jul 19 '23 19:07 d3lm

I think generally I am fighting a bit the fact that the nonce length and tag length are both configured "statically" and I can't pass them as arguments dynamically.

d3lm avatar Jul 19 '23 19:07 d3lm

Do you have a particular use case for dynamic tag lengths which isn't limited to a small number of possibilities?

tarcieri avatar Jul 19 '23 21:07 tarcieri

I think it's just the ability to pass in the tag length from the passed u8 slice for the tag instead of statically defining the tag length via a type.

d3lm avatar Jul 20 '23 11:07 d3lm

That wasn’t what I was asking. What is the use case for dynamic tag sizes? What are you doing that demands them?

tarcieri avatar Jul 20 '23 13:07 tarcieri

The use case is that I can create the "handle" so AesGcm::<Aes, NonceLength, TagSize>::new_from_slice(key) once and not every time I encrypt partial data which is what I need to implement the same interface as Node.js. I suppose creating the handle is not expensive so it should be somewhat fine? It'd be really nice if it was possible to do

AesGcm::<Aes, NonceLength, TagSize>::new_from_slice(key, nonce_size, tag_size)

instead of passing the sizes as types. Curious to hear your thoughts because right now I have a match for the tag size but for nonce sizes it's a bit unfortunate because I'd have to define every size and then pass the right type, so

match nonce.len() {
  1 => AesGcm::<Aes, aes::cipher::consts::U1, TagSize>::new_from_slice(key)
  2 => ...
  ...
}

d3lm avatar Jul 21 '23 09:07 d3lm

Sorry, that's not what I'm asking. Why are you working with arbitrary tag sizes in the first place? Who is choosing them? Why are they arbitrary? Why aren't they some fixed, low-cardinality set as specified by a protocol you're implementing?

tarcieri avatar Jul 21 '23 12:07 tarcieri

The user is choosing the tag size. In Node.js you can do:

const cipher = crypto.createCipheriv('aes-128-gcm', key, iv, { authTagLength: 4 });

So when the cipher object is created they can pass in the auth tag size. By default it's 16. Node allows 4, 8, and 12 - 16 byte auth tags.

d3lm avatar Jul 21 '23 13:07 d3lm

I'm afraid that still doesn't answer my question.

I am asking for specific examples of scenarios where being able to dynamically select the IV size using a runtime parameter would be helpful.

You gave a code example which uses a constant literal. That's not only not an example of a use case/scenario, but also an example of the sort which is very much amenable to being a compile time constant.

tarcieri avatar Jul 21 '23 13:07 tarcieri

Hm I am not sure I understand. It's a constant literal in the user's code, but in our runtime we don't know those values upfront and we run arbitrary code that can use any IV length or different tag sizes.

d3lm avatar Jul 21 '23 13:07 d3lm

So your use case is you're implementing a Node.js interpreter and need to support this kind of dynamic selection?

If so, please file a new issue specific to that request.

tarcieri avatar Jul 21 '23 13:07 tarcieri

Yea exactly. We are building a Node.js runtime for the browser so you can run Node.js entirely in your browser without relying on cloud servers. Should have said that in the beginning, sorry about that. Will file a new issue for that.

d3lm avatar Jul 21 '23 13:07 d3lm

One example of where variable tag size may help is a noisy link, where you adjust the tag size based on the available practical bandwidth and the point in the protocol flow.

mouse07410 avatar Jul 21 '23 18:07 mouse07410