KDF API review, round 2

Jamil Nimeh jamil.j.nimeh at oracle.com
Mon Nov 27 19:16:30 UTC 2017



On 11/27/2017 10:09 AM, Michael StJohns wrote:
> On 11/27/2017 1:03 AM, Jamil Nimeh wrote:
>>
>>>
>>>>
>>>> One additional topic for discussion: Late in the week we talked 
>>>> about the current state of the API internally and one item to 
>>>> revisit is where the DerivationParameterSpec objects are passed. It 
>>>> was brought up by a couple people that it would be better to 
>>>> provide the DPS objects pertaining to keys at the time they are 
>>>> called for through deriveKey() and deriveKeys() (and possibly 
>>>> deriveData).
>>>>
>>>> Originally we had them all grouped in a List in the init method. 
>>>> One reason for needing it up there was to know the total length of 
>>>> material to generate.  If we can provide the total length through 
>>>> the AlgorithmParameterSpec passed in via init() then things like:
>>>>
>>>> Key deriveKey(DerivationParameterSpec param);
>>>> List<Key> deriveKeys(List<DerivationParameterSpec> params);
>>>>
>>>> become possible.  To my eyes at least it does make it more clear 
>>>> what DPS you're processing since they're provided at derive time, 
>>>> rather than the caller having to keep track in their heads where in 
>>>> the DPS list they might be with each successive deriveKey or 
>>>> deriveKeys calls.  And I think we could do away with 
>>>> deriveKeys(int), too.
>>>
>>> See above - the key stream is logically produced in its entirety 
>>> before any assignment of that stream is made to any cryptographic 
>>> objects because the mixins (except for the round differentiator) are 
>>> the same for each key stream production round.   Simply passing in 
>>> the total length may not give you the right result if the KDF 
>>> requires a per component length (and it should to defeat (5) or it 
>>> should only produce a single key).
>> From looking at 800-108, I don't see any place where the KDF needs a 
>> per-component length.  It looks like it takes L (total length) as an 
>> input and that is applied to each round of the PRF.  HKDF takes L 
>> up-front as an input too, though it doesn't use it as an input to the 
>> HMAC function itself.  For TLS 1.3 that component length becomes part 
>> of the context info (HkdfLabel) through the HKDF-Expand-Label 
>> function...and it's only doing one key for a given label which is 
>> also part of that context specific info, necessitating an init() 
>> call.  Seems like the length can go into the APS provided via init 
>> (for those KDFs that need it at least) and you shouldn't need a DPS 
>> list up-front.
>>
>
> HKDF and SP800-108 only deal with the creation of the key stream and 
> ignore the issues with assigning the key stream to cryptographic 
> objects.  In the TLS version of HDKF, the L value is mandatory and 
> only a single object is assigned per init/call to the KDF.   An HSM 
> can look at the HKDF label information and set the appropriate 
> policies for the assigned cryptographic object (because if any of the 
> label data changes, the entire key stream changes).  That's not the 
> case for the raw HKDF nor for any KDF that allows for multiple objects 
> to be extracted out of a single key stream.  Hence the per-component 
> length values.
>
> Ideally, there should be a complete object spec for each object to be 
> generated that is part of the mixins (label and context) for any 
> KDF.   That allows an HSM to rely upon the object spec when setting 
> policy controls for each generated object - and incidentally allows 
> for a KDF to generate both public and non-public data in a secure way.
>
> So as long as you allow for the specification of all of the production 
> objects as part of the .init() I'm good.   A given KDF might not 
> require this - but I can't see any way of fixing the current KDFs to 
> work in HSMs without something like this.
>
>> As far as your (5) scenario goes, I can see how you can twiddle the 
>> lengths to get the keystream output with zero-length keys and large 
>> IV buffers.  But that scenario really glosses over what should be a 
>> big hurdle and a major access control issue that stands outside the 
>> KDF API: That the attacker shouldn't have access to the input keying 
>> material in the first place.  Protect the input keying material 
>> properly and their attack cannot be done.
>
> Let me give you an example.   I'm running an embedded HSM - to protect 
> TLS keys and to do all of the crypto.  An attacker compromises the TLS 
> server and now has access to the HSM.  No problem - I'm going to 
> notice if the attacker starts extraditing large amounts of data from 
> the server (e.g. copies of the TLS in the clear but possibly 
> reencrypted data stream) so this isn't a threat or is it?  Smart 
> attacker does an extraction attack on the TLS 1.2 and before KDF and 
> turns all of the key stream material into IV material and exports it 
> from the HSM.  The attacker now has the much smaller key material so 
> he can send a few messages with those keys and allow for the passive 
> external interception of the traffic and decryption thereof without 
> the risk of detection of all that traffic being sent.  Alternately, I 
> can place the key material in a picture via steganography and publish 
> it as part of the server data.
>
> The idea is to protect extraction of the key material from an HSM 
> _*even from authorized users of that key material*_.
>
>  KDFs don't currently do this well.  Adding the overall length and per 
> component length stuff as well as a per component spec to the data 
> used to derive the key stream means that 1) changes to any of those 
> change the entire key stream, 2) the per component spec data may be 
> used by the security module policy engine to enforce restrictions and 
> 3) because of (1) and (2) calling the KDF a second time gets me 
> exactly the same objects rather than just the same key stream.  The 
> last isn't very important in a software based security domain, but 
> turns out to have real implications for policy enforcing security modules.
>
> This gets worse when you realize that the KDF key is under it all 
> either a HASH HMAC or CMAC key and all of those algorithms produce 
> public data.   Ideally you need a way of preventing a KDF key from 
> calling the raw HASH/HMAC/CMAC functions directly (and vice versa).
>
>>
>> I would rather see the DPS provided in the deriveKey.  It couples 
>> what you want out with the call that makes the object and it makes a 
>> lot more sense to keep those two together than try to remember where 
>> in the submitted list of DPS objects you are.
>>>
>>> 95% of the time this will be a call to produce a single key. 4% of 
>>> the time it will be a call to produce multiple keys. Only 1% of the 
>>> time will it need to intermix key, data and object productions. 
>>> Anybody who is doing that is going to write a wrapper around this 
>>> class to make sure they get the key and data production order 
>>> correct for each call.  So I'm not all that bothered by keeping the 
>>> complexity as a price for keeping flexibility.
>>>
>>> You could have a Key deriveKey(Key k, DerivationParameterSpec param) 
>>> for some things like TLS1.3 (where you can only make a single call 
>>> to derive key between inits) , but then you'd also need at least a 
>>> byte[] deriveData (Key k, DerivationParameterSpec param) and an 
>>> Object deriveObject(Key k, DerivationParameterSpec param).
>> I don't think those are necessary.  If you're just doing HKDF-Expand 
>> (for the HKDF-Expand-Label TLS 1.3 key derivation) then you can 
>> provide the input key, label and max length and any other context 
>> info that goes into that HkdfLabel structure...all of that would go 
>> into init().  Then provide the key alg and desired length via the DPS 
>> at deriveKey time.  Any subsequent keys in the TLS 1.3 key schedule 
>> would need a new init call anyway since the labels change and 
>> possibly the output length.
>>
>> Over the next day or so I'm going to have to make some final 
>> decisions on this API as there are internal projects that are waiting 
>> on this API to proceed.  I'm already past the cut-off date I set, but 
>> I recognize these discussions are important to have and I appreciate 
>> the input you and others have provided.
>>
>> --Jamil
>>
>
> Reading this last I think I've lost the context.   Here's where I 
> think we are:
>
> 1) Get instance gets the default configuration of a given KDF (and 
> that default will be attached to the instance name defintion)
> 2) .setParameter() may be used to update the KDF configuration - once.
I thought that we had ditched setParameter in favor of putting these 
parameters in getInstance.  IIRC we were headed toward an algorithm 
naming convention of <KDF>/<PRF>, plus APS in the getInstance (which may 
be null (and might be for most KDFs that we start with: HKDF and 
possibly TLS-PRF).

For those I could see naming conventions:
HKDF would need a PRF specifier, so HKDF/HmacSHA256, HKDF/HmacSHA384.  
Basically for that PRF field I want to see values that line up with Mac 
algorthms in the standard names document.
TLS-PRF would probably allow a default "TLS-PRF" would be TLS-PRF used 
in 1.1 and earlier.  "TLS-PRF/SHA256" would be P_SHA256 from RFC 5246.  
Or we could make it also follow the Mac standard name, so 
"TLS-PRF/HmacSHA256".  I'm fine with that too.  Basically each 
implementation

> 3) .init() takes at least the key, it may optionally take a set of 
> derivation parameters.   The derivation parameters provided in .init() 
> are intended for use in forming the label and context mixins for the 
> KDF.   They may provide - for example - the total length of the key 
> stream, the objects to be derived, the length of the objects, 
> protection parameters for each of the objects etc.
Okay.  I think you've made a pretty strong case for the 
DerivationParameterSpec objects up-front.
> 4) A kdf generate a free-running or fixed length key stream depending 
> on the derivation parameters (e.g. if "L" is not a mixin to the KDF 
> then it is free-running and may produce as much key stream as desired 
> or if the production object specifications are not part of the 
> derivation mixins).
>
> Doing (4) is mostly not a good idea, but someone might want to do 
> this.   In that case it may make the most sense to just allow them to 
> do deriveData(int length) calls as the only function (a keyed PRNG 
> basically).
There's a couple ways we could do this:
byte[] deriveData(int length);
int deriveData(byte[] buf, int offset, int length);

I don't think we'll add these for this release of the KDF API.  It's 
easier to add these types of calls later if we need to than it is to 
have these extra forms for a KDF use-case that is "mostly not a good idea".
>
> Re the last version of your api - if you add the .setParameter() 
> .getParameter() calls to both KeyDerivation and KeyDerivationSpi I 
> think I'm happy with this part of the API.  I'm wondering if we should 
> talk about KeyAgreement though

See above with respect to set/getParameter.  But hopefully you'll be 
happy with the API after this next round.  I have one other change I 
will be making.  I'm removing deriveObject.  I'm uncomfortable right now 
with the idea of the API executing an arbitrary class' constructor.  
This is something I'm definitely willing to examine in the future once 
the most pressing tasks both with this API, and projects that are 
immediately depending on it are take care of.  It is easier to add calls 
to the API than it is to remove/modify/deprecate them if there's a 
problem.  I will file an RFE so that we can track this enhancement.

Modifications to the KeyAgreement API are beyond the scope of this JEP.  
We can certainly discuss ideas you have, but this KDF JEP isn't going to 
be dependent on those discussions.

--Jamil
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/security-dev/attachments/20171127/a0302ef1/attachment.htm>


More information about the security-dev mailing list