RustCrypto / AEADs

Authenticated Encryption with Associated Data Algorithms: high-level encryption ciphers
670 stars 140 forks source link

Consider adding tag size of 4 and 8 to AES GCM #541

Open d3lm opened 11 months ago

d3lm commented 11 months ago

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?

tarcieri commented 11 months ago

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.

d3lm commented 11 months ago

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.

tarcieri commented 11 months ago

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.

d3lm commented 11 months ago

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?

tarcieri commented 11 months ago

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.

d3lm commented 11 months ago

I wonder how Node.js does that then 🤔

tarcieri commented 11 months ago

My guess would be: unsafely

d3lm commented 11 months ago

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 commented 11 months ago

But that is prolly not a good idea is it?

d3lm commented 11 months ago

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().

tarcieri commented 11 months ago

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.

d3lm commented 11 months ago

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

d3lm commented 11 months ago

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.

tarcieri commented 11 months ago

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

d3lm commented 11 months ago

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

d3lm commented 11 months ago

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.

tarcieri commented 11 months ago

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

d3lm commented 11 months ago

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.

tarcieri commented 11 months ago

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

d3lm commented 11 months ago

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 => ...
  ...
}
tarcieri commented 11 months ago

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?

d3lm commented 11 months ago

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.

tarcieri commented 11 months ago

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.

d3lm commented 11 months ago

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.

tarcieri commented 11 months ago

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.

d3lm commented 11 months ago

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.

mouse07410 commented 11 months ago

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.