[noise] Resumption PSKs

Christopher Wood christopherwood07 at gmail.com
Tue Jun 12 08:53:47 PDT 2018


On Fri, Jun 8, 2018 at 11:00 PM str4d <str4d at i2pmail.org> wrote:
>
> On 06/08/2018 03:54 AM, Christopher Wood wrote:
> > On Thu, Jun 7, 2018 at 12:02 AM Trevor Perrin <trevp at trevp.net> wrote:
> >>
> >> On Tue, Jun 5, 2018 at 2:38 PM, Christopher Wood
> >> <christopherwood07 at gmail.com> wrote:
> >>> I'm not advocating
> >>> for an API that allows clients to pump out keying material
> >>> indefinitely from the same root. I was
> >>> just hoping to avoid overloading an already well-defined and clearly
> >>> specified API.
> >>
> >> Makes sense as a goal, but if you don't want to change Split()'s
> >> interface, I think the options are:
> >>
> >> (A) A function that can be called multiple times, after Split(), to
> >> create new key chains or additional keys.  This probably requires
> >> storing a single key and deriving from it repeatedly, thus defeating
> >> the key-independence / forward-secrecy goal.
> >>
> >> (B) A function that can be called once after Split() to create a new
> >> set of key chains, by deriving them from an extra key that is then
> >> destroyed.  This wouldn't change the underlying crypto, but it would
> >> mean that uses of Split() that don't require this extra key would have
> >> to derive and store it, in case this later function was called.
> >>
> >> Since changing Split() to take additional optional arguments is just a
> >> notation / API thing, it seems better than these alternatives which
> >> would have actual downsides in security properties or efficiency.
> >
> > In both examples, you note that Split() is always invoked. I
> > envisioned the new function being called in lieu of Split(), since
> > Split() does not modify the SymmetricState (right?). From the spec, it
> > only seems to be called when message patterns are finished.
> > Applications call this as needed during the handshake. At the end of
> > the handshake, they may call Split() to get CipherState objects for
> > encrypting/decrypting transport data, or, as suggested, get derived
> > keys to use for other purposes.
>
> This doesn't cover the case where you want to do both (such as in my use
> case where I want both transport keys and parallel key material).
> Applications would need to be able to call both functions (and the API
> will likely need to support calling them in either order).

That's correct. Could you not use Foo() derive keys and then create
CipherState objects manually? That seems to be one way to avoid
calling both.

>
> >
> > For clarity, below is what I think you proposed. Please correct me if
> > it's wrong!
> >
> > ~~~
> > Split(labels [][]byte) {
> >   Sets temp_k1, temp_k2, temp_k3 = HKDF(ck, zerolen, 3)
> >   If HASHLEN is 64, then truncates temp_k1, temp_k2, temp_k2 to 32 bytes
> >   Creates two new CipherState objects c1 and c2
> >   Calls c1.InitializeKey(temp_k1) and c2.InitializeKey(temp_k2)
> >   Create list `keychains` that stores label keys
> >   For each label_i = labels[i], create ck_i = HMAC(temp_k3, h ||
> > label_i), and set keychains[i] = ck_i
> >   Returns list (c1, c2, keychains)
> > }
> > ~~~
>
> This almost matches my draft implementation, except that temp_k3 is not
> truncated before it is used as an input to HMAC() (I assume that's what
> you meant by "truncates temp_k1, temp_k2, temp_k2 to 32 bytes"), and h
> isn't used as a prefix yet.
>
> >
> > Here's what I was proposing:
> >
> > ~~~
> > Split() {
> >    // Same as today
> > }
> >
> > Foo(labels [][]byte) {
> >   Sets temp_k1 = HKDF(ck, zerolen, 1)
> >   Create list `keychains` that stores label keys
> >   For each label_i = labels[i], create ck_i = HMAC(temp_k1, h ||
> > label_i), and set keychains[i] = ck_i
> >   Returns keychains
> > }
> > ~~~
>
> I don't think this is compatible with wanting to use both Split() and
> Foo(labels). temp_k1 will be the same in both functions, meaning that if
> you call "Split(); Foo();" then the key chains are all derived from c1's
> key. Granted, if CIPHERKEYLEN < 64 then c1 does not store the entire
> key, but they are still not really independent, and that leaves a
> footgun in place triggerable by the user's choice of cipher.

Fair point, and as you describe below, there are workarounds. One
could generate three keys and then discard the first two.

>
> >
> > At the end of the day, this boils down to cleanliness of the API, I
> > think, so this distinction is likely not critical. I do think it's
> > worth considering, though.

Agreed.

>
> Here are the various API options as I see them:
>
> 1) Split() takes optional argument (labels), and returns as many key
> chains as it is given labels.
>
> 2) A new function Foo(labels) is added, that requires calling Split() first.
>
> - This can be split (no pun intended) into options (A) and (B) that
> Trevor proposed.
>
> 3) A new function Foo(labels) is added, that doesn't require calling
> Split().
>
> - Split() and Foo() need to be completely independent (see above). Being
> distinct functions, we'd either need to use domain separation (though
> ensuring that Foo() is appropriately domain-separated without altering
> Split() could be tricky), or derive three keys in Foo() and discard the
> first two.
>
> - Implementation-wise, this would probably mean that a library's
> HandshakeState is instantiated with a flag per function, to enable or
> disable calling it (which could be an Option<Arguments> for Foo()).
>
> 4) A new function SplitAk(labels) is added, which is identical to
> Split() from 1).
>
> - Requires duplicating the internal logic of Split() :(
>
> - Implementations could just toggle between the two with a single flag
> (or an Option<labels>).
>
> - Possibly a middle-ground between reducing configuration complexity,
> and avoiding changes to Split()'s API.
>
>
> If this is likely to be the only addition to Split() then I'm happy with
> 1).

This is a fine way to articulate the core issue. Rather than overload
Split() now, it seems prudent to think about extensibility in the
future.

> Implementation-wise, it was pretty straightforward, and in the case
> where no labels are provided, it would return an empty array, which I
> don't think is at all onerous. It isn't necessarily a good template for
> how to make further undefined changes to Split(), however - though with
> key chains enabling arbitrary amounts of parallel key material to be
> derived, it's not clear to me if there's _anything_ more that might want
> to be added.
>
> I agree with Trevor's thoughts on 2).
>
> 3) is the most extensible, but future extensions would similarly need to
> ensure they are completely independent of both Split() and Foo().
> Depending on how Foo() is made independent, this could start becoming
> unwieldy.
>
> 4) seems like the most conservative approach from an API standpoint. I'm
> not particularly fond of it, even though I originally proposed it (:
>
> Cheers,
> str4d
>
> _______________________________________________
> Noise mailing list
> Noise at moderncrypto.org
> https://moderncrypto.org/mailman/listinfo/noise


More information about the Noise mailing list