0
$\begingroup$

I want to encrypt several files correctly with a PBKDF2 generated key (using a password and salt), I am currently doing so but if I enter the wrong password, as expected an exception occurs.

Note: there is no server involved

1) I was thinking of how I could do this. I thought the way that makes most sense is to encrypt a value like "Hello World!" and store the encrypted text, then next time encrypt "Hello World!" again and check if the stored value is equal to the output and no exception occurs.

2) But then I thought maybe I could replace "Hello World!" with the salt I used when creating the password, which I don't know if this actually matters but I am not an expert with passwords.

My question is, should I validate the password with the first option, second option or is there a better way option?

$\endgroup$
  • $\begingroup$ what is wrong with an exception? $\endgroup$ – Richie Frame May 3 at 0:07
  • $\begingroup$ What do you mean? an exception causes the decryption to fail $\endgroup$ – OughtToPrevail May 3 at 0:21
0
$\begingroup$

PBKDF2 is not an encryption function.

Given an arbitrary-long input, it outputs a fixed-size value, which can eventually be used as a key for an encryption function.

So, your question could be reformulated as: how do I check that the secret key obtained by deriving the password is correct?

If you are using authenticated encryption, no need to invent anything: if the key is wrong, the authentication tag will not match and decryption will fail at the first chunk.

If, for some reason, you absolutely need to verify the key before processing the first chunk of encrypted data, you can prepend an empty (but still authenticated) message.

Once again, if the tag doesn't match what would be correct for the given key and an empty message, the authenticated encryption function will naturally return an error.

$\endgroup$
  • $\begingroup$ I am well aware PBKDF2 is not an encryption function, sorry if I made it seem like I do, I will edit my question. Can you explain what you mean by an empty authenticated message? $\endgroup$ – OughtToPrevail May 3 at 1:02
  • 1
    $\begingroup$ You didn't provide any details about the encryption function itself. If you want to avoid tampered data from being undetected, you need to use authenticated encryption. The modern way to do it is to use an AEAD such as ChaCha20-Poly1305 or AES-GCM. They can authenticate an empty message like any other message. $\endgroup$ – Frank Denis May 3 at 8:22
  • $\begingroup$ Thank you but that does not answer my question $\endgroup$ – OughtToPrevail May 3 at 8:50
  • 1
    $\begingroup$ It does :) If the password is incorrect, the key will not match the original one, and decryption will immediately fail. $\endgroup$ – Frank Denis May 3 at 9:42
  • $\begingroup$ No it doesn't read my question again, I have several files, there is no point in checking the password for each file. Also I am not using authenticated encryption since I am using Java and since I want it to be cross-platform according to Android AES-GCM isn't supported until API 22 when there are still people who use API 15. (ChaCha is API 28+) $\endgroup$ – OughtToPrevail May 4 at 4:15
0
$\begingroup$

Essentially you want to know if the password is correct after the password is entered, but before files are accessed. Either option you came up with will detect incorrect passwords, and neither is worse than the other.

A method I used in the past was similar to your ideas, to encrypt a known string or block, then check it after. However I processed the block so that there was no longer a direct relationship with the key.

The 16 byte string is encrypted, hashed, then truncated down to 4 bytes and text encoded with Base64URL, leaving a 6 byte alphanumeric string. The same process is repeated on password entry, and the string is matched to determine if the password is correct. The string is stored with the salt and hashing parameters. I would suggest a warning be shown when the password is detected as incorrect, and you be given the option to use the generated key anyway, there may be a reason to use a different password than your usual.

The other thing I did differently was during file encryption. Instead of using the password generated key, a random key was generated to encrypt the data, then that was encrypted with the password generated key and stored with the file. That way the password could be changed without reencrypting the entire file, only the encrypted key needs to be changed.

Edit for additional explanation

can you explain your third paragraph better? (Please include in the explanation: Why use this long way, what are the advantages, why is Base64URL needed (instead of storing the bytes), why truncate to 4 bytes?)

Starting at the end, why 4 bytes? Because more are just not necessary. 4 bytes is 32 bits, which gives you a very good chance of a wrong password NOT generating the same value. The more bytes you store, an attacker would be more likely to determine the actual password, instead of one of the millions that generate the same check value. The less you store, the more likely an incorrect password will pass the test.

Why Base64URL instead of storing the bytes? Encoding allows it to be stored in a text readable format, as well as be written down or printed without conversion. Standard password hash formats use text readable encodings, PHC uses a variant of the modular crypt format with Base64 encoding. If you did not encode the values, they could potentially contain control characters like carriage return and line feed, and reading the values could no longer be done with a text editor. I like unpadded Base64URL because it gives more flexibility (you can put it in a URL, etc), the padding is not needed because there is no reason to decode it, it is either used as a direct input (encoded salts) or a comparison string.

Why use this long way, what are the advantages? The main advantage is that there is no longer a direct relationship between the key generated by the password and the check value. If there was ever a known plaintext shortcut attack on the cipher or a preimage attack on the hash function, the long way should should completely prevent either or even both of those.

Incidentally the long way is not long at all. Since you already have implemented a hash for PBKDF2 and a cipher for encryption, it should take only a line or 2 of code to generate the check value, Base64URL can usually be implemented in 1 line of code if you already have Base64 available. Pseudocode like this after you generate the key:

CheckValue = Base64URL(Truncate(Hash(Encrypt_ECB(0,key)),4))

$\endgroup$
  • $\begingroup$ The idea of the encryption key per file is very good, thank you, but can you explain your third paragraph better? (Please include in the explanation: Why use this long way, what are the advantages, why is Base64URL needed (instead of storing the bytes), why truncate to 4 bytes?) $\endgroup$ – OughtToPrevail May 3 at 5:44
  • $\begingroup$ Wow! thank you for detailed explanation. But I am using AES-256 which is resistant against plaintext attacks. But even if it wasn't, if an exception is thrown with the file key it means the file was corrupted so the password may actually be correct. I also don't think truncate should be used since it is after all making a higher chance of miss detection and also, I don't have Base64, might implement it later in the project. $\endgroup$ – OughtToPrevail May 4 at 4:03

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.