Hacker News new | ask | show | jobs
by Sjoerd 1006 days ago
When doing symmetric encryption you usually need a nonce or IV, which is also sent to the other party along with the ciphertext and authentication tag. Why does the API for libsodium allow you to specify your own nonce and keeps it separate from the ciphertext? The function crypto_secretbox_easy includes the authentication tag in the ciphertext, but you still have to provide the nonce yourself and it is not included in the ciphertext. Wouldn't it be easier still if the nonce was generated within this function and also added to the ciphertext?
7 comments

Many protocols define how the nonce must be computed.

And the nonce is not sent explicitly; the different parties compute it themselves the same way they agree on a shared secret.

When a ciphertext is received, the recipient knows what the nonce is expected to be, instead of having to trust an arbitrary one.

In the context of a stream of messages, it binds the nonce to a position in the stream, allowing detection of frames that have been replayed, reordered or lost.

But when the intent is to send independent messages, the nonce should be included with the ciphertext.

For constructions with a short nonce size, the ability to choose the nonce is also very useful to improve the security bounds, by using a key derivation function to derive a new key and nonce from a larger nonce.

By the way, for streams, libsodium has the `crypto_stream_*()` functions that greatly simplifies the implementation of protocols and file encryption.

In addition to what sibling comments said, keeping stuff separate helps making the C API a bit more explicit and easier to intuit out of the box. For instance: https://monocypher.org/manual/aead

  void
  crypto_aead_lock(
      uint8_t       *cipher_text,
      uint8_t        mac  [16],
      const uint8_t  key  [32],
      const uint8_t  nonce[24],
      const uint8_t *ad,         size_t ad_size,
      const uint8_t *plain_text, size_t text_size);
Here you see that both `nonce` and `mac` are separate (libsodium calls that "detached"), which can be annoying considering you always need the mac. On the other hand, the plaintext and ciphertext buffers have the same size, and the sizes of the mac and nonce are explicit.

Yet another reason to let users specify the nonce is that portability stops me from accessing an RNG. Can’t produce random nonces without it, so I have to pass the burden to the user. (On the other hand, I get to run on tiny microcontrollers that don’t even have `malloc()`).

---

Speaking of streaming encryption, I have that too now, and unlike libsodium it’s compatible with the one-shot interface. Like libsodium, you only need to provide your nonce once.

The docs have a page on encrypting multiple messages[0]. In it they use a single random IV and then increment it for each message. Under this scheme you only need to transmit or negotiate the IV once.

If you use something like TLS, The key exchange is expanded to produce an IV. Then each record has an incremented number[1] and this number is mixed with the IV to form a unique IV[2] for the message.

[0]: https://doc.libsodium.org/secret-key_cryptography/encrypted-... [1]: https://www.rfc-editor.org/rfc/rfc8446#section-5.3 [2]: https://www.rfc-editor.org/rfc/rfc8446#appendix-E.2

I agree. If you look at the Swift version of the library, it will generate and prepend the nonce for you automatically (if you use the right overloaded method). It will also strip it and use it during decryption.

My guess is they wanted to maintain compatibility with NaCl.

That would probably be good for the overwhelming majority of users.

More rarely, you want to save space/reduce overhead by using a deterministic IV scheme. Either all 0 because you picked a random key and you _really_ care about space saving, or you're very very sure you can safely count IVs (0, 1, 2, ..) without ever reusing a single one.

In those rare cases you don't want to transmit the IV, you just know what the value is supposed to be on both sides. More complexity and danger, but a small percentage of users get to save a few bytes.

A important benefit is that it keeps the function hermetic. Reproducibility is very important for fuzzing and debugging purposes. If the function lets me provide the nonce, I can use my own CSPRNG to generate it, and ensure that my program executes deterministically if fed the same random seed.
Nonce could be an implicit counter in which case it's not transmitted.