-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MessageEncryptor design concerns #35
Comments
The conventional answer for how to handle arbitrary-sized keys as an input to a function like this would be HKDF, right? It appears to me, given the terminology this code is using, that the key wrapping is taking from JWE. JWE does this in order to implement hybrid encryption with RSA and ECC, where you can't encrypt the whole message with the "outer" key. It doesn't really make sense for GCM (or any other AEAD). |
Yeah. It doesn't look like the underlying crypto library has an HKDF implementation though. Given that users might give passwords instead of keys to this API, I feel like PBKDF is fine. |
Thank you! I will drop more comments and a PR tomorrow but for additional context:
|
/cc @potatosalad |
I couldn't sleep, here is a PR: #36 The PR no longer uses the sign secret. However, we still keep it around for decrypting old messages. The goal is to release it as v2. This means v2 messages cannot be decrypted by v1, but v2 can still decrypt v1. Feedback is welcome! |
Key wrapping in this context does provide some benefits against key exhaustion. Specifically, AES-128-GCM unfortunately suffers from the problem where the same nonce used with the same key can result in a "catastrophic" leak where other ciphertexts may be decrypted (see https://crypto.stackexchange.com/a/68525 and https://datatracker.ietf.org/doc/html/rfc8645). Is the primary goal to reduce the overall size of the encrypted output or is it simply to reduce complexity of the implementation? I agree that the user-provided keying information should be derived, and HKDF is probably the right option for that use-case (PBKDF2 also works, but it's much slower and designed for passwords). OTP 26 also provides XOF functions for SHAKE128 and SHAKE256, which could potentially be used for KDF, too (see https://www.erlang.org/doc/man/crypto#hash_xof-3). |
@potatosalad our usage always derive the keys and we can make it extra clear in the docs. Do we need key wrapping even if using KDF? Hopefully the PR can provide a reference point but I am also glad to drop it.
Unfortunately the KDF is not part of the signed/encrypted value, so we cannot change it between versions. We could allow users to opt-in into new implementations. In any case, we do cache the key derivation parts, so performance may not be a major concern? |
It just depends, if the KDF output doesn't change very often, it's eventually the same as using the original or master key without KDF.
KDF in this context would be better than |
If we wanted to drop the key wrapping entirely, it might make sense to have the "v2" use something like xchacha20-poly1305 which doesn't suffer from the same catastrophic failure (2^96 messages can be sent with the same key versus 2^32 for others before the birthday paradox problem is reached). |
The current nonce is 12 bytes, so you'd get ~2^48 messages before there's 50% chance of a collision. If |
@btoews You're correct, I mixed up the collision problem with the guidance specific to AES-GCM found in NIST SP 800-38D:
By comparison, AES-GCM-SIV is designed to allow for some IV collisions without the "catastrophic failure" found in AES-GCM and can support 250 messages, depending on the size of the messages, if you want to be below the 2-32 chance of collision. XChaCha20-Poly1305 has a 296 message limit with a 280 message limit below the 2-32 chance of collision. Reference of my current understanding:
|
I have updated the pull request to use CHACHA20 Poly1305, can you please review and let me know? Thank you. |
The link for convenience: #36 |
@josevalim Thanks for listening to my concerns and responding to them so quickly! ❤️ |
I was reading through the
MessageEncryptor
implementation and have some concerns. Nothing appears to be outright broken, so I feel like it's appropriate to have this conversation in public.The thing closest to being broken is that long keys are being truncated to 32 bytes:
plug_crypto/lib/plug/crypto/message_encryptor.ex
Line 60 in 5884b46
This is pretty sketchy because you're reducing the user's key size without their awareness. AES has fixed key sizes, so it would be much better to return an error if a key of the wrong size is given. If you want to keep the API easy to use, you should pass keys through a KDF. Erlang's
:crypto.pbkdf2_hmac/5
function would be suitable for this.To give a concrete example of why this is concerning, imagine a user storing their key in PEM format for some reason:
Passing that into
MessageEncryptor
, their key is now functionally-----BEGIN AES-256-GCM KEY-----\n
.My other concerns are with the overall encryption scheme. In pseudocode, the scheme is
There's a fair bit of undue complexity here. Most notably,
sign_secret
is serving no purpose as far as I can tell. GCM is an authenticated mode, so the ciphertext is already protected from tampering. I can't think of a scenario where users would benefit from the ability to roll these secrets separately. Requiringsign_secret
does make the API more difficult to use though. Unless there's some purpose I'm missing, I'd recommend removing it. If there is a purpose, it would be nice to have it docummented.I also question the key wrapping scheme. If you were expecting users to decompose the returned blob and store the first and second ciphertexsts separately, there's concievably some value to it. This is meant to be a black-box API though, so I don't imagine users are doing that. As with the
sign_secret
, I'd recommend docummenting any purpose to the key wrapping scheme and removing it if there is none.In the same pseudocode, I think you'd be better served by the following implementation:
The text was updated successfully, but these errors were encountered: