Which password-based encryption method should I use in Java 6? It should be as strong as possible, but work with the default security policy file (not the export restricted one which you have to download and install separately)?
My recommendation: You should try to avoid using password-based encryption, if you can possibly avoid it. In particular, password-based encryption tends to be insecure, in practice. The problem is that it is rare for users' passwords to have enough entropy to resist dictionary attack. Therefore, if you need encryption, you probably don't want to use password-based encryption, if there's any alternative.
A better solution is to give users a key, and use the key to encrypt or decrypt.
If you don't follow my recommendation: If you absolutely must use password-based encryption, here are some suggestions to reduce the risk somewhat:
Use PBKDF2 to derive the key from the password. Choose a suitably large number of iterations so that the key derivation process takes, e.g., 100ms on the user's machine. This will make it somewhat harder to perform dictionary search on the user's passphrase -- however, it does not eliminate the risk, so users must still take extra effort to choose a very long passphrase.
Do everything you can to increase the likelihood that your users use a very long passphrase. The best approach would be to automatically generate the password for them. I suggest choosing one with 80 bits of entropy. If you turn this into a password that uses A-Za-z0-9 (but not O or l, to avoid confusion with 0 or 1), the resulting passphrase will be 14 characters long. Users may not like having to deal with such a long passphrase, but they'll just have to suck it up and deal, if they want to be secure.
If you let users choose their own passphrase, most will probably pick a lower-quality passphrase. I suggest that you include dire warnings about the risk. You should recommend that they pick a passphrase that is at least 20 characters long, and is as random as they can make it. You may want to provide a little app to generate a sample passphrase for them, that they can cut-and-paste and use.
Even with all your efforts, if users choose their own passphrase, many users will probably pick one that is not adequately secure. The problem is that encryption requires passphrases that are considerably longer and stronger than the passwords you use for a web account password. Most users probably don't know this, and anyway the annoyances of choosing a passphrase that is long and strong enough will probably deter many users from doing so and cause them to behave insecurely.
If you're getting the sense that password-based encryption is a headache to use securely, your sense is absolutely accurate.
First read @D.W.'s answer, it is full of good advice. Then, if you still want to do password-based encryption, consider the following:
- Password-based encryption is about turning a password into a key and then using that key for symmetric encryption.
- Java can do some password-based encryption, i.e. do the two steps in a streamlined API, as is shown here. However, it allows only some combinations of password-hashing mechanisms and encryption algorithms, and, in particular, it is reported not to allow that with AES, which is a problem.
- Nothing prevents you from doing the password-to-key transform and then the encryption in two separate steps in your code.
Note that password hashing mechanisms are usually not covered by cryptographic laws and export regulations, since they do not "hide" data. I then recommend that you just include an implementation of bcrypt in your code, e.g. jBCrypt which is written in Java, OpenSource with an easy license, and reasonably compact (one source file). You would need to alter the code a bit, because that implementation wants to use strings, whereas you want raw bits; thus, do the following:
- Use the
crypt_raw()method to convert the password and salt into the 192-bit bcrypt output.
- Hash these 192 bits with SHA-512 (Java 6 includes a SHA-512 implementation). This yields 512 bits.
- Use these 512 bits for AES encryption of your data, with a MAC on the result (e.g. with HMAC/SHA-256, then again supported by Java 6). See below for details.
(I usually prefer bcrypt over PBKDF2 for the reasons explained there, but PBKDF2 is not bad either, so if you really want PBKDF2 (or need it for compliance reasons), then go for it.)
Important: doing the encryption properly, and adding a MAC, can be botched in a surprisingly high number of ways. You are warmly encouraged not to do it yourself. However, the following method should be safe:
- You have 512 bits of "password-derived data". Split it into four 128-bit blocks.
- Encrypt data with AES-128 in CBC mode. CBC requires an initialization vector, which is an array of 16 random bytes. Use the first 128-bit block (from the SHA-512 output) as key, and the second 128-bit block as IV. Also, use PKCS#5 padding (since your input data might have a length which is not a multiple of 16).
- When encryption is done, compute HMAC/SHA-256 over an input consisting of the IV, then the encryption result, in that order; the key for HMAC will be the third 128-bit block from the SHA-512 output.
- The "encrypted file" must contain the salt for bcrypt (128 bits, aka 16 bytes), the output of the AES/CBC encryption, and the HMAC output (256 bits, aka 32 bytes).
- For decryption, use the salt (from the encrypted file) and the password to recompute bcrypt, then apply SHA-512 to get the encryption key, IV and key for HMAC. Verify the HMAC value, and, if it is proper, decrypt the data.
And, remember: never, never, NEVER reuse the salt value for two encryption instances (whether they use the same password or not). Always use
java.security.SecureRandom to generate a brand new random salt every time you want to encrypt a file.