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?

6
  • Possible duplicate of How to login and encrypt data with the same password/key Feb 5, 2018 at 22:28
  • 1
    Basically, use PBKDF2 for the encryption key, then hash the encryption key for the password hash. Feb 5, 2018 at 22:29
  • Hash the encryption key using what? A further secure number of rounds of PBKDF2 with new salt?
    – dFrancisco
    Feb 5, 2018 at 22:30
  • 1
    If the api allows you to easily add an additional PBKDF2 round to the current hash that'd probably be the best way to go, otherwise I think a single SHA512 would be enough. See also here. Feb 5, 2018 at 22:40
  • 1
    If you're not protecting a plain-text password, but rather an "impossible" to guess hash, you don't need a heavy-handed hashing, something quick will be fine.
    – dandavis
    Feb 5, 2018 at 23:11

1 Answer 1

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);
});

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .