4

I am writing a Java application that is required to locally authenticate a user with a password and then use the password to generate an AES-256 key for local file encryption/decryption.

I understand the principles behind everything and how important proper algorithm choice, rounds of hashing and crypto-random salt generation is. With this in mind, I use the PBKDF2WithHmacSHA256 algorithm supported in Java 8, a 16 byte salt value generated with Java's SecureRandom and 250 000 rounds of hashing. My question lies in the implementation, the following is a (simplified) version of how I generate the hash and users key. The code was shortened for the sake of this post and values were hard-coded for again, simplification of the post.

int iterations = 250000;
String password = "password";
String salt = "salt";

SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
char[] passwordChars = password.toCharArray();
KeySpec spec = new PBEKeySpec(passwordChars, salt.getBytes(), iterations, 256);
SecretKey key = factory.generateSecret(spec);

byte[] passwordHash = key.getEncoded();

SecretKey secret = new SecretKeySpec(key.getEncoded(), "AES");

This code is based on the concatenation of a few different open source Java projects I have gone through that also leverage the PBKDF2 algorithm for either password hashing, AES key generation, or both.

My question here is, is this actually secure? I have a feeling that the use of the same SecretKey value "key" to generate the SecretKey "secret" and generate the hash is incorrect.If this is true, can anyone advise the correct method to leverage the PBKDF2WithHmacSHA512 algorithm to generate a password hash and derive a AES key?

dFrancisco
  • 2,721
  • 2
  • 14
  • 27

1 Answers1

0

To secure this the salt should be randomized and attached to the hashed password. I generally do this by encoding the value I store in the BLOB as a fixed set of bytes that start the salt (IV) followed by the hashed password and parse it as such. Variants can be done but the technique is generally the same where salt is random for each entry on the table. This prevents the use of rainbow tables as they'd have to do the salt + hashing (this is what caused the data breach a few years back with LinkedIn and the problem with Windows NT passwords).

As far as the AES data goes, I just add another random salt (doable in CryptoJS) or extra SHA512 hash or both just to add a little more computation to the final decryption. But it doesn't really add much because the secret key is symmetric if the data needs to be viewable by the same device and once the device is compromised its a matter of executing the key that's stored with the encrypted data. You'd only be doing it to ensure there's encryption-at-rest.

This is my equivalent test using randomized data everywhere in Crypto JS

test("random salt", () => {
  const salt = lib.WordArray.random(16);
  const password = "somethingSecret";
  const pb = PBKDF2(password, salt, {});
  const theKey = pb.toString();
  const secretData = {
    theMessage: lib.WordArray.random(16).toString(),
  };
  const secretMessage = JSON.stringify(secretData);
  const iv = lib.WordArray.random(16);
  const encrypted = AES.encrypt(secretMessage, theKey, {
    iv,
  }).toString();

const pb2 = PBKDF2(password, salt, {}); const theKey2 = pb2.toString(); const decrypted = AES.decrypt(encrypted, theKey2, { iv }).toString(enc.Utf8); expect(decrypted).toBe(secretMessage); expect(JSON.parse(decrypted)).toStrictEqual(secretData); });