xstream
xstream copied to clipboard
Should XSTREAM bind identities? (Yes)
The key derivation used by XSTREAM is inspired by the NaCl crypto_box()
function, which does the following (for "curve25519xsalsa20poly1305"):
-
sk
: private scalar -
pk
: X25519 public key (i.e. Montgomery-u coordinate)
s = scalarmult(sk, pk)
k = hsalsa20(0, s)
Notable about both this construction and XSTREAM is that they do not bind the identity of the static D-H key when performing key derivation. Considering the use case (offline/data-at-rest encryption) I am not particularly concerned, but perhaps it's worth re-evaluating and e.g. including the static D-H key as an input to HKDF.
One option would be to eliminate the user-supplied salt and use the static D-H public key as the HKDF salt. There aren't particularly compelling use cases for the salt otherwise: a random user-supplied salt would most likely come from the same RNG as the ephemeral key and therefore would not help in the event of an RNG failure.
Alternatively, rather than completely eliminating the salt parameter, we could do something like this for the HKDF salt:
static_key || user_salt
Personally I'm in favor of eliminating the salt parameter from the user-facing API. XSTREAM is intended to provide a simple, high-level API which is hard to misuse, and as such eliminating parameters that provide dubious value while being one more thing to learn seems good to me.
I'm going to draw a distinction here - HKDF exposes salt
and info
, and this has only discussed salt
.
I definitely agree that salt
shouldn't be exposed to the user, in part because it has some subtle properties that are not suited for end users (specifically, secret salt
s aren't kosher salt
:P). Feeding the public key in via salt
seems like something with little to no downside, and has the potential to mitigate a risk.
At that point, it's worth asking whether info
should be exposed instead. While salt
shouldn't be used for secret inputs, I don't know if the same is true for info
(I don't think it is?). Having a user-specified parameter for key generation allows them to make the entire stream depend on whatever other data is passed; randomness is far from the only valid input. While CHAIN could get the same effect by including that data in the first chunk's AD, STREAM requires it be included in every chunk's AD.
There are three relevant parameters:
-
salt
andinfo
: HKDF parameters -
nonce
: 8-byte nonce prefix used by STREAM
They are presently:
-
salt
: optional (per HKDF spec, with zeros as a default) user-supplied value -
info
: the stringXSTREAM_X25519_HKDF
as a domain separation/personalization measure -
nonce
: 8-bytes of0
(since we always generate a random ephemeral D-H key)
I'm open to changing any of them. I'm somewhat leaning toward:
-
salt
: static D-H key of the recipient -
info
:XSTREAM_X25519_HKDF
-
nonce
: optional user-supplied 8-byte value, defaulting to 8-bytes of0
Though I could see the value in a user-supplied info
parameter as well, as otherwise if we use salt
for binding the static D-H key used, there is no option for any user-supplied input to the KDF, be it a random nonce, personalization string, mutually committed upon public value, or what have you.
One thing to keep in mind here is I am trying to keep the end-user API as high level as possible and would like to minimize unnecessary knobs. That said I'm not sure there's value in exposing the nonce
in a construction which derives a unique key per usage, and perhaps only like exposing it to the user because it feels slightly less dirty as an optional parameter instead of a hardcoded constant.
Why not just an aggregate impl, with methods to change anything that doesn't justify being in the "constructor"? (e.g. the salt)
@xorxornop: Because that's a colossal footgun that undermines the entire point of this library.
The point of the miscreant project as a whole is an extension of the NaCl intent to provide a carefully-curated selection of sensible tools that, as much as is possible, avoid exposing footguns to the user. One of the few places NaCl fell down in that goal is nonces; this is pretty much exactly what this library aims to fix.
While the decision of what to pass here has multiple options with tradeoffs, it also has nontrivial security implications, and that is exactly why it'd be a failure in the goals of this library to abdicate responsibility and shove the burden of that choice (and of correctly implementing that choice) onto the user.