Re: Define how keys are derived from secret values for deriveKey

On Wed, Feb 26, 2014 at 12:28 PM, Mark Watson <watsonm@netflix.com> wrote:

>
>
>
> On Wed, Feb 26, 2014 at 11:24 AM, Ryan Sleevi <sleevi@google.com> wrote:
>
>>
>>
>>
>> On Tue, Feb 25, 2014 at 4:35 PM, Mark Watson <watsonm@netflix.com> wrote:
>>
>>>
>>>
>>>
>>> On Tue, Feb 25, 2014 at 3:46 PM, Ryan Sleevi <sleevi@google.com> wrote:
>>>
>>>>
>>>>
>>>>
>>>> On Tue, Feb 25, 2014 at 3:33 PM, Mark Watson <watsonm@netflix.com>wrote:
>>>>
>>>>>
>>>>>
>>>>>
>>>>> On Tue, Feb 25, 2014 at 12:47 PM, Ryan Sleevi <sleevi@google.com>wrote:
>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>> On Tue, Feb 25, 2014 at 12:01 PM, Mark Watson <watsonm@netflix.com>wrote:
>>>>>>
>>>>>>> https://www.w3.org/Bugs/Public/show_bug.cgi?id=24811
>>>>>>>
>>>>>>> The deriveKey operation derives a key targeted at a specified algorithm. Both ECDH and DH algorithms output a Secret Value.
>>>>>>>
>>>>>>> It is not yet specified how to map from the Secret Value to a key for the specified target algorithm.
>>>>>>>
>>>>>>> It seems intuitive to use the "raw" import format for the target algorithm with the Secret Value as the raw input. If we do this we must define how to provide the length of the key and how to convert the secret value to that length. Presently, raw import for symmetric keys e.g. AES-GCM derives the key length from the provided data and fails if the provided data is not one of the supported lengths.
>>>>>>>
>>>>>>> It seems valuable to be able to specify the length of the required key independently from the length of the Secret Value.
>>>>>>>
>>>>>>>
>>>>>> I'm not sure I follow what you're proposing here regarding separate
>>>>>> lengths.
>>>>>>
>>>>>
>>>>> The length of the secret value is whatever the (EC)DH outputs. The
>>>>> length of the AES key you want could be 128, 192 or 256.
>>>>>
>>>>> We can either say the AES key length is some function of the secret
>>>>> value length, or we could allow the AES key length to be specified directly.
>>>>>
>>>>>
>>>>>>
>>>>>>>  So, one possibility is to allow the length of the symmetric key to be specified as an input to the import operation and have that operation define the mapping from arbitrary length raw value to a key of the requested length. The deriveKey operations can then refer directly to the "raw" import operations for the derived key algorithm.
>>>>>>>
>>>>>>>  ...Mark
>>>>>>>
>>>>>>
>>>>>> Yes, I agree that for the case of deriving symmetric keys - whether
>>>>>> "directly" (eg: by treating the DH Phase 2 output as a direct input to a
>>>>>> key) or "indirectly" (eg: by treating the DH Phase 2 output as an input
>>>>>> into another KDF, like HDKF/Concat) - it's necessary to specify additional
>>>>>> parameters.
>>>>>>
>>>>>> You suggest that it's "one possibility", but I'm curious if you see
>>>>>> there being any other possibilities.
>>>>>>
>>>>>
>>>>> Not really - I just said "one possibility" to emphasize the tentative
>>>>> nature of the proposal. But what you suggest below is different from what I
>>>>> suggested ...
>>>>>
>>>>
>>>> I don't know if it's a matter of not having coffee handy, but I'm at a
>>>> loss still for parsing some of this email, so I'm again going to try to get
>>>> clarifications.
>>>>
>>>>
>>>>>
>>>>>
>>>>>> Import:
>>>>>>   - If length is present
>>>>>>     - If length is 'consistent' with the import data - success
>>>>>>     - If length is 'inconsistent' with the import data - failure
>>>>>>  - If length is absent
>>>>>>   - ?? Fail? Or derived from import data? How is this similar to or
>>>>>> different than the JWK import cases where alg is optional?
>>>>>>
>>>>>
>>>>> For "pure" import, the length of the data itself is an explicit
>>>>> indication of the required key length. It can't be absent. We do not have a
>>>>> separate AesImportParams dictionary and we don't need one (yet - see
>>>>> below).
>>>>>
>>>>
>>>>> It's different from JWK "alg", because alg is optional and because we
>>>>> *do* have a place in the method parameters to specify the information
>>>>> that might be in the alg field. Thus we have to specify the requirement for
>>>>> consistency.
>>>>>
>>>>>
>>>>>> Derive:
>>>>>>   - If length is present
>>>>>>     - If length is less than the maximum output of the key derivation
>>>>>> step (if any) - create a key from the first (length) bits and feed to
>>>>>> import("raw")
>>>>>>    - If length is greater than the maximum output of the key
>>>>>> derivation step - fail
>>>>>>  - If length is absent
>>>>>>    - Fail
>>>>>>
>>>>>> Have I missed any other edge conditions?
>>>>>>
>>>>>
>>>>> No. I think what you've written is the right approach, but it's a
>>>>> little tricky to see how we implement this in the specification.
>>>>>
>>>>> The derive logic above could be part of the (EC)DH derive operation.
>>>>> It has the truncation step which happens before the "raw" AES import
>>>>> operation. The truncation step needs access to a length field. The
>>>>> intuitive place to specify this length is in the deriveKeyType method
>>>>> parameter. e.g.:
>>>>>
>>>>> p = deriveKey( { name: "DH", public: PV }, dhPrivateKey, { name:
>>>>> "AES-GCM", *length: 256* }, false, [ "encrypt", "decrypt" ] )
>>>>>
>>>>> However, what is the subclass of the derivedKeyType field ? Presumably
>>>>> it is something like an AesImportParams.
>>>>>
>>>>> So, then, we have an operation which is part of the (EC)DH "derive"
>>>>> procedures (the truncation) which needs information from an AES-specific
>>>>> structure. Awkward.
>>>>>
>>>>
>>>> To be clear, my understanding of your proposal is that we add an
>>>> AesImportParams, and that length is specified. That's why I sought
>>>> clarification, above, as to what to do. Is "length" optional (implied from
>>>> context) or is it required (even when it can be inferred?) This is EXACTLY
>>>> the same conversation we had regarding JWK - a parameter which can be
>>>> inferred for some operations (AES 'raw' import, JWK with an 'alg'
>>>> parameter) but not for other operations (AES 'derived' import, JWK without
>>>> an 'alg' parameter).
>>>>
>>>> Please explain why you find this awkward, however, as you didn't really
>>>> qualify it. derivedKeyType is specifying the "shape" of the key to be
>>>> derived. The length is part of that shape. Arguably, however, so is the
>>>> algorithm. For example, it's entirely possible (as discussed on past calls)
>>>> that things like parity bits might enter the equation if talking 3DES keys
>>>> - they're both fundamentally shapes of what the derived key is going to
>>>> look like.
>>>>
>>>
>>> That's not what I find awkward. I'll try and explain that better below.
>>> I think (correct me if I'm wrong), that we're both moving towards having an
>>> AesImportParams which explicitly specifies the length and could be used
>>> both with importKey and deriveKey and then the behaviour is as you
>>> suggested.
>>>
>>>
>>>>
>>>>
>>>>>
>>>>> We could resolve this by putting the truncation operation into the
>>>>> "raw" AES import procedures and adding the length as an input to these. If
>>>>> we wanted true import to work as you have specified, then we'd need those
>>>>> procedures to have two modes "strict length" mode, in which a length
>>>>> mis-match is an error and "non-strict length" mode in which truncation can
>>>>> be performed. This could be a boolean input parameter which is "strict"
>>>>> when the "raw" input procedures are called from importKey and "non-strict"
>>>>> when they are called from the derive operation of another algorithm.
>>>>>
>>>>> Any better ideas ?
>>>>>
>>>>> ...Mark
>>>>>
>>>>>
>>>> I do not parse this at all, but it sounds overly complex and very
>>>> different than what I was proposing - and how we handle other parameters of
>>>> a similar type (eg: JWK)
>>>>
>>>> I don't see where the issue is with keeping truncation as part of key
>>>> derivation. That is, it has always been my understanding that a deriveKey()
>>>> operation is comprised of
>>>>
>>>> [1. run the KDF] + [2. do some special steps _within the 'kdf' alg] +
>>>> [3. import a key with the result]
>>>>
>>>> The truncation step is [2]. Parity bits would be [2]. Any other
>>>> 'shaping' would be [2].
>>>>
>>>> If the question is where do you specify the parameters for [2], I still
>>>> believe that derivedKeyType is the right place.
>>>>
>>>
>>> I mostly agree with this. My problem is just that step [2] is specific
>>> to the key type. Do we need to have a switch in the derive procedures for
>>> the different derived key types ? e.g. in the derive operation of DH, do we
>>> have:
>>>
>>> [1]: do the DH phase II, resulting in secret value
>>> [2]:
>>> If the name property of normalizedDerivedKeyType is a case-sensitive
>>> string match for "AES-CTR", "AES-CBC", "AES-GCM", "AES-CMAC", "AES-CFB-8":
>>>      <truncation steps for AES using length parameter of
>>> normalizedDerivedKeyAlgorithm which is of type AesImportParams>
>>> If the name property of normalizedDerivedKeyType is a case-sensitive
>>> string match for "HMAC":
>>>     <truncation steps for HMAC using length and hash parameters
>>> of normalizedDerivedKeyAlgorithm which is of type HmacImportParams>
>>> etc.
>>> [3]: call the import operation of normalizedDerivedKeyAlgorithm with
>>> format "raw", ...
>>>
>>> The problem being that step [2] will be duplicated exactly for
>>> everything that supports deriveKey (ECDH, DH, HKDF, CONCAT KDF, PBKDF2).
>>>
>>> My suggestion was just to roll [2] into the import procedure for each
>>> algorithm. Alternatively, we could define a separate "Import derived key"
>>> operation for each algorithm that is called from the derive operations and
>>> that does steps [2] and [3] combined.
>>>
>>> ...Mark
>>>
>>>
>> I'm inclined to see something like a separate "import derived key". I
>> don't think we need to duplicate [3] into it. I think the "import derived
>> key" is really a statement of "Perform the algorithm-specific part of [2]"
>>
>
> Yes, I came to the same conclusion too.
>
>
>>
>> So the description for deriveKey (in general/ alg agnostic)
>>
>> [1] Perform the derivation defined by deriveKey-KDF
>> [2] Perform the formatting defined by formatDerivedKey-derivedKeyAlg
>> [3] Perform the importing defined by importKey-derivedKeyAlg
>>
>
>> I think this is fairly similar to what Vijay described.
>>
>> The question is what is the relation with "derivedKeyAlg"'s algorithm
>> parameters for "formatDerivedKey" and "importKey". This does imply that
>> we'd need some sort of derive-algorithm for every algorithm name - even if
>> it's just a class that is multi-implementing AlgImportParams and
>> DeriveLengthParams.
>>
>
> Not sure if I correctly parsed that question. I think we have two choices
> (using AES as an example):
> (a) we define an AesImportParams which contains a length field. Both the
> "import key" operation and the "import derived key" operation use this same
> structure to specify the length
> (b) we define an AesDeriveParams which contains a length field. Only the
> "import derived key" operation uses this structure to specify the length.
> For "import key" we use the length of the supplied data (as today).
>
>
>>
>> Another alternative is that AesImportParams (et-al) do exist and _do_
>> define a length. the "ImportKey-Alg" template is changed to also take an
>> additional parameter "truncatable". When importKey-Alg is called by
>> importKey-generic, truncatable=false. When importKey-Alg is called by
>> deriveKey-generic, truncatable=true.
>>
>>
>>
> Yes, this was exactly my suggestion above, except I called it "strict
> length mode" instead of "truncatable=false". This approach is only
> compatible with (a) above, but otherwise it is just a specification style
> thing.
>
> However, there is one nit with algorithms like HKDF. HKDF (as defined in
> the RFC) takes as an input the length of the required keying material, L.
> It's not presently included in the HkdfCtrParams, presumably because,
> intuitively, you'd like it just to generate the right amount of keying
> material for the target derived key type (rather than have it generate
> more, that will just be truncated, or less, that will just fail).
>
> So, if the length is only specified in the derived-key-algorithm-specific
> deriveKeyType, how does the key derivation algorithm (HKDF) access it ?
>
> It seems that the required output length of the secret bits - which is
> equal to the required key length - needs to be available to the algorithm
> on which deriveKey is called. Whether you get there by truncation or by
> having that length as an input parameter depends on the algorithm ((EC)DH
> will do truncation, HKDF has an input parameter). But still, it makes
> intuitive sense for the length to be in the deriveKeyType parameter to the
> deriveKey method.
>
> This gets worse when we consider HMAC. The intuitive thing to do to use
> HKDF to derive an HMAC key is the following:
>
> p = deriveKey( { name: "HKDF", ... }, masterkey, { name: "HMAC", *hash:
> "SHA-256"* }, false, [ "sign", "verify" ] )
>
> As with HMAC key generation, we expect the HMAC key length to default to
> the hash function block size. HKDF needs this number as an input.
>
> The only way I can see to implement this pattern in the specification is
> to have text in each algorithm description which says "how to derive the
> required length for key derivation from derivedKeyType". So, for example,
> we might have in the deriveKey method:
>
> - Let length be the result of running the "obtain derived key length"
> operation for the algorithm identified by derivedKeyType
> - Let secret be the result of running the "derive key" operation for the
> algorithm identified in normalizedAlgorithm using length, ...
> - Let result be the result of running the "import derived key" operation
> for the algorithm identified by derivedKeyType using secret
>
> Then, in AES sections we would have:
>
> *Obtain derived Key Length*
> 1. If any of the properties of AesDerivedKeyParams are not present in
> derivedKeyType, terminate ...
> 2. Return the value of the length property of derivedKeyType
>
> In HMAC we would have:
>
> *Obtain derived Key Length*
> 1. If the hash property HmacDerivedKeyParams is not present in
> derivedKeyType, terminate ...
> 2. If the length property of derivedKeyType is present, return the length
> property of derivedKeyType
> 3. Otherwise, return the block size of the hash algorithm identified by
> the hash property of derivedKeyType
>
> In the (EC)DH section we would have:
>
> *Derive Key*
> ... (all the steps to derive the secret value)
> - If the number of bits in the secret value is less than length, terminate
> - Otherwise, return the first length bits of secret value
>
> And in the HKDF section we would have
>
> *Derive Key*
> - ...
> - Perform the HKDF key derivation algorithm using length ...
> - ...
>
> ...Mark
>
>
>
I think that works for me.

Received on Wednesday, 26 February 2014 20:36:12 UTC