3
$\begingroup$

I would like to use a unique encryption key and authKey for each payload that I encrypt and authenticate via AES256 CBC with HMAC-SHA256. I'm considering the following:

Salt = 32 random bytes, unique to each payload, stored out in the open.
MasterKey = 32  random bytes, chosen once ever, stored securely
EncKey||AuthKey = PBKDF2(MasterKey, Salt, iterations) 

Where EncKey will be 32 bytes used as the encryption key to AES256CBC and AuthKey will be 64 bytes used as the hashKey for HMAC-SHA256.

From my reading on crypto.stackexchange.com I understand that HKDF is probably a better function to use for deriving EncKey and AuthKey then PBKDF2 but in the development environment I'm in (.Net) I don't have access to HKDF from an authoritative source.

Is it ok to use PBKDF2 for this purpose? And if so, is it better to use a high iteration count (like 100,000) or a low iteration count like 1?

$\endgroup$
  • 1
    $\begingroup$ IIRC you'd be invoking the full PBKDF2 three times here (once per 32-byte block) which is clearly worse than using 3x the iteration count and just using some cheap other KBKDF (maybe even implementing HKDF yourself?). $\endgroup$ – SEJPM Feb 16 '17 at 14:29
  • $\begingroup$ @SEJPM I'm not sure what you mean by I'd be invoking FullPBKDF2 three times. My plan was to run it once with a fixed iteration count and get back 96 bytes. I'd use the first 32 bytes for the EncKey and the next 64 bytes for the AuthKey. There is source code for HDKF here gist.github.com/CodesInChaos/8710228 But I'm leary to use it since I don't consider a gist a trusted source. $\endgroup$ – Ron C Feb 16 '17 at 14:36
  • 3
    $\begingroup$ IIRC PBKDF2 can only output as many bytes as the underlying hash can. Thus for SHA-256 this would be 32 bytes. Now if you want 96 bytes, the standard tells you to run your hash with the fixed iteration count once for every chunk of 32 bytes (with a different counter / ...), so you'd end up running PBKDF2 internally with 100k iterations three times which is worse than using 300k iterations once and expanding the result because an attacker would only need to do 100k iterations and check with the encryption key. $\endgroup$ – SEJPM Feb 16 '17 at 14:40
  • 2
    $\begingroup$ @SEJPM Since I'm starting with a 32 byte Master key of random bytes (albeit used as the master key for generating unique keys for every payload), I figured the generation of the EncKey and AuthKey only need a KBKDF in the first place which is why I was leaning towards using 1 iteration of the PBKDF2 as my KBKDF. No? $\endgroup$ – Ron C Feb 16 '17 at 15:48
  • 2
    $\begingroup$ With one iteration, PBKDF2 should serve just about as good for key expansion as HKDF does, which is good enough for your case and no problem at all. $\endgroup$ – SEJPM Feb 16 '17 at 20:52
5
$\begingroup$

From my reading on crypto.stackexchange.com I understand that HKDF is probably a better function to use for deriving EncKey and AuthKey then PBKDF2 but in the development environment I'm in (.Net) I don't have access to HKDF from an authoritative source.

Discretion is the better part of valor, so more often than not it makes sense to try and use algorithms your environment provides over having to implement your own. In this case, however, I think we have a reasonable exception; if your environment provides PBKDF2, and thus presumably HMAC, I think HKDF is simple enough that I'd say just write your own implementation of it. The most useful source is the RFC:

In particular, if your MasterKey is already uniformly distributed, section 3.3 tells you that you can skip the HKDF-Extract function (section 2.2) and implement just the HKDF-Expand (section 2.3), which is little more than a fairly straightforward chain of HMAC calls:

HKDF-Expand(PRK, info, L) -> OKM

Options:
   Hash     a hash function; HashLen denotes the length of the
            hash function output in octets
Inputs:
   PRK      a pseudorandom key of at least HashLen octets
            (usually, the output from the extract step)
   info     optional context and application specific information
            (can be a zero-length string)
   L        length of output keying material in octets
            (<= 255*HashLen)

Output:
   OKM      output keying material (of L octets)

The output OKM is calculated as follows:

N = ceil(L/HashLen)
T = T(1) | T(2) | T(3) | ... | T(N)
OKM = first L octets of T

where:
T(0) = empty string (zero length)
T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
...

(where the constant concatenated to the end of each T(n) is a
single octet.)
$\endgroup$
  • 1
    $\begingroup$ That's tempting. I'll give the spec a read. One thing that's odd though, if it's such an easy algorithm to implement why hasn't Microsoft included it in crypto classes? Or why isn't a c# callable version already readily available from a trusted source. Even Bouncy Castle doesn't include it. ??? $\endgroup$ – Ron C Feb 16 '17 at 20:19
  • 1
    $\begingroup$ Maybe the (rather less popular) C# version, but I'm pretty sure I wrote the initial implementation for the Java version for Bouncy Castle. $\endgroup$ – Maarten Bodewes Feb 16 '17 at 20:26
  • $\begingroup$ It's true that it's a simple algorithm, but still, I doubt the benefit of using HKDF over PBKDF2 in this scenario outweighs the risk of making a mistake in implementing the algorithm, especially if the programmer has limited experience with crypto. $\endgroup$ – hunter Feb 16 '17 at 20:33
  • $\begingroup$ @hunter: Looking at it in context, though, where RonC is already dealing with secret key material and apparently manually implementing an encrypt-then-MAC composition, the additional risk looks small. This is not a functionality that has big "gotchas" like nonce reuse or timing attacks; it just requires the same general sort of care that the context already does. $\endgroup$ – Luis Casillas Feb 17 '17 at 1:52
  • $\begingroup$ I did chose to implement HKDF based on rfc5869 and it was fairly easy. You can review and comment on the code here: codereview.stackexchange.com/questions/155613/… $\endgroup$ – Ron C Feb 17 '17 at 18:46
3
$\begingroup$

Yes, you could use PBKDF2 for this.

You can use an iteration count of 1 as the master key will already contain 256 bits of entropy: that's plenty, you don't need any strengthening for that.

So you should try and configure PBKDF2 using HMAC-SHA256 or HMAC-SHA512 as PRF. Note that the standard indicates SHA-1 as default, so that's probably what is used if you don't configure it with a different secure hash or HMAC. It seems however impossible to configure Rfc2898DeriveBytes with SHA-256. That means that the key strength may be diminished somewhat as SHA-1 is configured to provide no more than 160 bits of security.

Note that normally you should not use more output from PBKDF2 than the hash size as it provides an advantage to an attacker. This is however inconsequential because the high amount of entropy in the master key puts the attacker at a pretty strong disadvantage in the first place.

Input:

  • P: the masterkey in bytes (instead of the password)
  • S: the derivation data, possibly prefixed by a salt (included with the ciphertext, one salt should suffice per set of keys)
  • c: the iteration count, set to 1
  • dkLen: 32 (the size of the derived key or secret in octets)

You could for instance use an 8 byte salt, followed by the encoding of the ASCII encoded strings "EncKey" and "AuthKey". You may also want to include some ID, especially if you want to establish keys between two parties (for two way communication).

The salt isn't really necessary, it's optional for HKDF as well.


Implementing HKDF is indeed not that hard, but be sure to test it plenty if you decide to go that route.

One advantage of choosing HKDF is that HMAC expects a key instead of a password. So you probably could use a key container with HMAC while that would not be possible for PBKDF2.

Your choice.

$\endgroup$
  • 1
    $\begingroup$ Changed answer with regards to the choice of hash (the result is kind-of the same, but using SHA-1 or a larger output size would not be as catastrophic as mentioned before. $\endgroup$ – Maarten Bodewes Feb 17 '17 at 0:18
2
$\begingroup$

If you are going to generate a MasterKey with a CSPRNG, and securely store/retrieve it when encrypting/decrypting, I see no need for a key derivation function of any kind. I suggest you just use the key directly with AES-GCM which takes care of the authentication for you. If you need to use AES-CBC-HMAC, then just generate a longer MasterKey and split it into two keys as required.

$\endgroup$
  • $\begingroup$ I am generating the MasterKey with a CSPRNG however I like the idea of generating a unique EncKey and AuthKey for each encryption with AES-CBC-HMAC since doing so puts more entropy into the encryption process and if the encKey for a given message were ever discovered it couldn't be used to decrypt any other messages. $\endgroup$ – Ron C Feb 16 '17 at 17:51
  • $\begingroup$ I see. Then I would simply go with EncKey = PBKDF2(MasterKey, Salt, 1). You don't need any more entropy if your MasterKey was generated by a CSPRNG. AES-GCM is easier to use (and thus, arguably safer) than AES-CBC-HMAC. If you must go with the latter, then just repeat: AuthKey = PBKDF2(MasterKey, Salt, 1). $\endgroup$ – hunter Feb 16 '17 at 18:09
  • $\begingroup$ I assume you mean with a different salt for the EncKey than the for generating the AuthKey. But if I only want one salt to append to the output why not just run PBKDF2(MasterKey, Salt, HMAC256, 1) once and ask for EncKey||AuthKey bytes all at once? $\endgroup$ – Ron C Feb 16 '17 at 19:37
  • $\begingroup$ Sure - each bit of PBKDF2 output is considered to be independent of every other bit, so you can just call it once and request however many bits you need for your various keys. $\endgroup$ – hunter Feb 16 '17 at 20:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.