Hints
How does crypto_box work?
crypto_boxuses a Diffie-Hellman key exchange on the two keys and hashes the result. Then it uses that as the key forsecret_box.crypto_boxis equivalent tocrypto_box_beforenmfollowed bycrypto_box_afternm.crypto_box_beforenmis the hashed key-exchange which works as described in the Curve25519 paper, using elliptic curve Diffie-Hellman key exchange on Curve25519 hashing the result with HSalsa. This yields a 32 byte shared key.k = Hash(Curve25519(b, A)) = Hash(Curve25519(a, B))crypto_box_afternmis identical tocrypto_secret_box. It takes a 24 byte nonce and a 32 byte key. It's an authenticated stream cipher using XSalsa20 for encryption and Poly1305 as MAC. The first 32 bytes of the output of XSalsa20 are used for the MAC, the rest are xored into the plaintext to encrypt it.
What happens if you use it multiple times?
If you take two fixed key-pairs, the result of the key exchange will always be the same.
But the symmetric part secret_box is secure even when you use a key several times, as long as you never reuse a nonce for that key, i.e. the (key, nonce) pair must be unique.
This property is pretty much the same for all modern authenticated stream ciphers, such as AES-GCM or XSalsa20-Poly1305.
Common ways to create a unique nonce are:
- Use an 8 byte prefix and a random 16 byte value (stateless, random 16 bytes are most likely unique)
- Use a 16 byte prefix and a 8 byte counter (stateful, useful in a connection where you increment for each packet)
from https://stackoverflow.com/questions/13663604/questions-about-the-nacl-crypto-library
What key length does nacl::auth need?
Any. nacl::auth prepares the key the way RFC 2104 section 2 prescribes, so the
length of the key you pass in does not have to match anything:
- longer than the block size of the hash, that is 64 bytes for
-hmac224and-hmac256and 128 bytes for the other three, the key is replaced by its hash under the hash of its own variant, SHA-224 for-hmac224and SHA-384 for-hmac384 - shorter, it is padded with zero bytes on the right
- exactly 32 bytes, the case NaCl itself defines, the result is the same as it has always been
That matters wherever the secret is not generated locally: JWT, webhook signatures of GitHub, Stripe or GitLab, and API HMACs all use a secret of arbitrary length that the other side decides on.
RFC 2104 recommends a key of at least the hash output length but does not require
it, and neither does nacl::auth. If a minimum key length matters in your
context, check it before you call. nacl::randombytes auth -key produces 32
bytes.
Which of the five variants should be used?
| option | hash | tag | JWT |
|---|---|---|---|
-hmac224 |
SHA-224 | 28 bytes | -- |
-hmac256 |
SHA-256 | 32 bytes | HS256 |
-hmac384 |
SHA-384 | 48 bytes | HS384 |
-hmac512256 |
SHA-512 | 32 bytes | NaCl crypto_auth, the default |
-hmac512 |
SHA-512 | 64 bytes | HS512 |
-hmac512256 is the NaCl primitive crypto_auth_hmacsha512256, HMAC-SHA-512
truncated to 32 bytes. It is the default of nacl::auth and the right choice
when both sides are yours.
The other four are the HMACs RFC 4231 defines test vectors for, and they are what
interoperability calls for: -hmac256, -hmac384 and -hmac512 are the HS256,
HS384 and HS512 of JWT, and -hmac256 is behind nearly every webhook
signature. None of them are interchangeable, the same key and message produce
different tags.
Two pairs invite confusion. -hmac512 and -hmac512256 are the same
computation, the second cut to 32 bytes, so one tag is the prefix of the other --
handing a truncated tag over as HS512 is a mismatch no peer accepts.
-hmac224 and -hmac384 on the other hand are not prefixes of -hmac256
and -hmac512: SHA-224 and SHA-384 begin from a different initial state.
Why use nacl::auth verify instead of comparing the tags?
nacl::auth verify compares in constant time, through crypto_verify_32 and,
for the longer tags, a second such call whose result is folded in with a bitwise
or so that both halves are always looked at. The duration of the comparison
therefore does not reveal how many leading bytes were correct.
Comparing two tags with string equal or eq stops at the first difference and
leaks exactly that. It returns 0 when the tag matches and -1 when it does not.