1

As an exercise, I am writing some code that accepts a list of possibly secret properties (e.g. user ID, password, IP address), adds a timestamp, encrypts it, and signs it.

Here's a high-level overview of what the code does - does this look correct from the point of view that it should ensure secrecy and integrity?

The main thing I am unsure about is deriving the encryption key and the signing key from the same user-provided secret key, but this seems a little paranoid given that there should be no relationship between the two assuming the strength of the underlying hash function of the key derivation algorithm.

If anyone is interested the source code together with a small test suite, packaged up with setuptools, can be found here.

Implementation

The user supplies a private key and two optional salts which we use to derive encryption and signing keys using PBKDF2. If no salt is provided two are generated using urandom.

Next, the user supplies a list of property strings, ['a', 'b', 'c',...] which is converted to a |-delimited string and to which str(time.time()) is prepended. This plaintext is padded using PKCS#7 method. An Initialization Vector (IV) is generated using urandom and the plaintext is then encrypted with AES-256 (CBC mode) using the key described above and the IV.

The IV + ciphertext are then signed using Python's native HMAC-MD5 implementation with the signing key derived as described above, and this signature is appended to the message.

The decryption and verification process follow roughly the opposite process as above, with extra care taken in the verification step to use the built-in compare_hash method to prevent vulnerability due to timing attack.

0

How many iterations of PBKDF2 are you using?

I don't think there is an issue with deriving the encryption and signing key from the same user-provided secret key. As you said, if the hash function is solid and you use a separate salts and enough iterations of PBKDF2 it will be secure.

Using PKCS#7 padding should be fine and if you check the HMAC prior to decryption you're not vulnerable to padding oracle attacks. I must ask though, why HMAC-MD5? While HMAC-MD5 is currently not broken because there are no practical preimage attacks against MD5, but nevertheless MD5 already failed part of it's security requirements due to it's weak Collision Resistence. So why not use a more secure hash function? Attacks on crypto only get better, not worse. I'd strongly recommend you use SHA-2 or SHA-3.

  • I'm using 1000 iterations of PBKDF2. The documentation for the Python HMAC library used MD5 in all the examples so I did not think to use a different hash but you're right, I'll change it to SHA-2. – MikeBrno Jan 10 '15 at 9:44
  • I would use more iterations. As to why, I'll refer to this stackexchange answer. – sukosevato Jan 10 '15 at 14:35
  • Based on that thread would you say 10k iterations would be sufficient? – MikeBrno Jan 10 '15 at 17:43
  • It depends on your use case, i.e. how frequently you will be doing the operation. For example something like Lastpass that uses it to protect the master password, you only do the operation once upon startup and then cache it. So you can afford a 500 ms delay. If you're doing the operation very frequently then a 25 ms delay may be better. How many iterations equals that delay depends on the hashing algorithm. On a R9-290 videocard you can do ~12 billion SHA256 hashes/second. An Intel i7 4960X can do 0.5 Billion / core / second. Realise that 10k is peanuts and incurs a 0.02 ms delay on a 4960x. – sukosevato Jan 10 '15 at 21:17
  • Makes sense. I'll expose it as a setting and set the default to 10k, there's no real use case here, just an exercise :). Thanks – MikeBrno Jan 10 '15 at 21:20

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.