32

I'm writing some software where I have to encrypt all files that are written to a specific folder using AES. There is one password/key that is configured somewhere, which is always used.

I don't want to store a base64 string because that is too complicated for the person who might have to change the configuration. I want to store a string containing any characters. What is the best way to convert this password to a key for use in AES?

  • Can I just get the bytes from the key?
  • Should I generate a key using PBKDF2?

As I understand, I then also need to provide a salt. However, in my situation, the salt seems unnecessary because I only have one key.

  • Do I just provide an empty salt or is that somehow not secure?
  • Or is there a completely different (hopefully simpler) way?
kelalaka
  • 5,409
  • 4
  • 24
  • 47
Matthijs Wessels
  • 443
  • 1
  • 6
  • 10
  • 3
    "If you are typing A E S you are doing it wrong": http://www.cs.berkeley.edu/~daw/teaching/cs261-f12/misc/if.html – adric Jul 15 '13 at 12:04
  • 2
    All the below solutions seem to be way above my head. Taking the above comment into consideration, I decided to just store the whole 256bit key as a hex string. The question might still be relevant for someone else though, so I'll leave it in its current form. I don't know how to choose to accept an answer though, but upvotes do their job quite well I think. – Matthijs Wessels Jul 18 '13 at 14:46

3 Answers3

41

What you do is the following:

  • Generate a long key (this is just a secure randomly generated amount data, you can use 128 bits)
  • You use this key to encrypt your file with AES (meaning you use it as a password)
  • You encrypt the key itself with AES using your password (which is a bit shorter but easier to remember. Now AES requires this again to be either 128, 192 or 256 bits of length so you need to make your password of that length. Hence you can just use PBKDF2 (or scrypt or bcrypt) to create a fixed-length key from your password. DO NOT STORE THIS.
  • You keep a hash of your hashed password using PBKDF2 (or bcrypt or scrypt). This will allow you to check if the password is correct before trying to decrypt your encrypted key.

So:

hash(hash(password)) --> can be stored
hash(password) --> cannot be stored

You always need to provide a salt even if you only have one key (this largely prevents rainbow table lookups). Fortunately for you, salt generation is done automatically by PBKDF2.

So in the end you actually have 3 things:

  • Your encrypted files
  • Your encrypted key
  • A hash of your password to verify it

You might ask why you need the key: If you would change your password (and you had encrypted all your files with your password), you would need to first decrypt all your files with your orignal password and then re-encrypt them with your new password. With this implementation all you need to do is decrypt and re-encrypt the key (as this key serves as your password).

EDIT

I kind of misinterpreted your question, I've added the key derivation function.

Also a good read (courtesy of IBM)

Anakhand
  • 137
  • 6
Lucas Kauffman
  • 54,169
  • 17
  • 112
  • 196
  • @LucasKauffman +1 for the salt, horray for salt! Isn't salt when used in the context of a reversible algorithm called an initialization vector? That's how I have understood it, same thing different name. – Four_0h_Three Jul 12 '13 at 17:41
  • Well you shouldn't store it, I've added it to the answer – Lucas Kauffman Jul 12 '13 at 17:57
  • @LucasKauffman Ah I see. I didn't realize you were hashing the hash. – Brendan Long Jul 12 '13 at 18:00
  • 1
    @PsudeoReality the IV is different from the salt actually – Lucas Kauffman Jul 12 '13 at 18:05
  • Thanks for the answer. Sounds like a good way, but in my situation I don't need the second key because the stored files are temporary so I won't have to re-encrypt anything. – Matthijs Wessels Jul 18 '13 at 20:48
  • 1
    The second hash should not be a password-hash but some fast hash like SHA-2. Otherwise you are wasting time on the verification that an attacker can mostly ignore (they can e.g. decrypt the last block and check padding). This time would be better spent on more iterations for the first hash. – otus Apr 04 '16 at 07:04
  • I'm a noob with cryptography. I thought AES has an upper bound on how large a key can be. Then why generate a long key? Assuming AES generally only accepts keys of a certain size (e.g. 128, 192, and 256 bits), how do I pass in a 128 **byte** (1024 bits) key to an AES cipher? Wouldn't the rest of the key be discarded? Again, what I'm asking is only based on the assumption that keys can only be of a specifically fixed size. – Sal Rahman Jul 22 '16 at 18:20
  • Addendum to my comment: apparently the 128 *byte* was a typo, and actually meant 128 bits. – Sal Rahman Jul 22 '16 at 18:40
  • 2
    The IBM link looks to be dead; is there a google-able title? – Brian M. Hunt Oct 30 '18 at 14:04
  • @dtech: The purpose is not to increase complexity, but to have some way of verifying that the password is correct. – BlueRaja - Danny Pflughoeft Dec 14 '18 at 08:09
  • @BlueRaja-DannyPflughoeft the point was that if you don't increase computational complexity, your security is trivial to bruteforce. Hashing the hash only doubles the time complexity, which is nowhere nearly enough, producing a password hash should have tens, even hundreds of thousands time higher cost than simple data hashing, in order to make bruteforcing impossible. – dtech Dec 14 '18 at 09:24
  • 1
    @dtech: You still don't seem to understand. bcrypt/scrypt already do that, as part of their algorithm. The purpose of `hash(hash(password))` above is not to increase complexity, it's to give the developer some way of verifying that the password is correct. `hash(password)` is the encryption key, which cannot be stored, but most encryption algorithms do not provide any way of verifying that the key you're using to decrypt is the correct one. Thus you store `hash(key)` = `hash(hash(password))` for that purpose. It does not affect brute-forcing at all. – BlueRaja - Danny Pflughoeft Dec 15 '18 at 19:59
  • @LucasKauffman When you say "You keep a hash of your hashed password using PBKDF2 (or bcrypt or script)", do you mean applying PBKDF2 again on the previous PBKDF2 result, or do you mean applying an arbitrary hash function (e.g. SHA-256) to the hashed password *obtained* through PBKDF2 (i.e. the first PBKDF2 result)? – Anakhand Feb 08 '21 at 12:57
  • Also, shouldn't we include the salt used in the list of things to store? – Anakhand Feb 08 '21 at 13:01
13

To convert a password to an AES key, PBKDF2 is the simplest way of handling it. Just make the sure the password has sufficient entropy.

You do need to use a salt, because it's there to protect against rainbow table attacks.

Depending on your platform, there may already be libraries available to help with this. If not, I'd recommend something close to Lucus Kaufman's solution.

Setup:

  1. Generate a random 128-bit key (k1), a random 128-bit IV, and a random salt (64 bits is probably sufficient).
  2. Use PBKDF2 to generate a 256-bit key from your password and the salt, then split that into two 128-bit keys (k2, k3).
    • Make sure your algorithm's native output is at least 256 bits, or this will be slow. PBKDF2-SHA256 is a good choice. Don't use two seperate algorithms for this, since it will just make it slower and more complicated for you, but won't slow an attacker down.
    • If your password already has sufficiently high entropy, then you can afford to use a fairly low number of iterations. 1000 iterations will be so fast you won't even notice it (especially since you'll only need to decrypt the key when the program starts up), so there's not much reason to go below that. If your password is weaker, you can turn up the number of iterations to compensate.
    • I don't recommend using bcrypt for this, since it's output is the wrong size and you would need to hash it again, which adds unnecessary complexity.
    • I think scrypt can generate arbitrary-sized output, so it would be a good choice if it's available (this may not be allowed if you want FIPS compliance).
  3. Use k2 to AES encrypt k1 using the random IV.
  4. Save the encrypted key, k3, the salt and the IV to a file somewhere.

Encryption / Decryption:

  1. Use PBKDF2 + your password and the salt from the file to regenerate k2 and k3.
  2. Verify k3. If it doesn't match, either your password is wrong, or someone tampered with your file. Stop here.
  3. Use k2 and the IV from the file to decrypt k1.
  4. Use k1 to encrypt or decrypt files.

Password Change

  1. Decrypt k1 as in the Encryption / Decryption section.
  2. Follow the steps in Setup, using the same k1, but regenerate everything else (new random salt and IV, generate k2 and k3 from the new password).

Do not store k2 anywhere. Do not store k1 unencrypted. Doing either of those things will break the security of your system.

If you don't care about being able to change your password (I would consider this a very important feature, but maybe you don't), then you could skip the steps involving k1, and use k2 as your AES key and k3 to verify it.

You may also find it useful to generate another key for HMAC, so you can verify that the encrypted files haven't been tampered with. If you want to do that, you can generate a 128-bit HMAC key, and encrypt and store that with the main AES key.

Brendan Long
  • 2,878
  • 1
  • 19
  • 27
  • The password is not entered from outside of the webapp but just stored in the configuration. So I don't think I need to check if the password is correct. – Matthijs Wessels Jul 18 '13 at 20:48
6

If you generate a key you should somehow hash it, considering that a hash function is "sort-of" like a pseudo-rng. So using PBKDF2 is always good, since it is a key derivation method and is used specifically for this purpose. You pass in a password, and it generates a key you can use based on your password and a salt. As far as Salt, it is automagically part of PBKDF2 so you shouldn't have to worry about it.

If you use something like .NET and store the key in your web.config file you can encrypt parts of your web.config with either the machine key or an RSA key http://msdn.microsoft.com/en-us/library/dtkwfdky%28v=vs.100%29.aspx , the nice thing about this is that windows will take care of storing the RSA key and or machine key. You can even automate this process with your build server.

nerdybeardo
  • 273
  • 2
  • 7
  • 2
    You would need to re-hash the key you derived with PBKFD2. Otherwise you store the decryption key directly with your encrypted files. – Lucas Kauffman Jul 12 '13 at 12:09