2

My app needs to work with encrypted user files on their devices. It should keep the data secret when someone gets hold of the device. For this, I'm thinking about the following schema (which may be wrong, and that's why I'm asking).

  • The app generates a random key k (of a fixed predefined length), which will be used as a master key for the file encryption.
  • It defines K = k || o, with o being a string of zeros (of a fixed predefined length).
  • It generates and stores a random salt.
  • It computes h(""), i.e., it applies a key derivation function to an empty string (which is the initial password; that's fine as the user will be prompted to change it before they store any data).
  • It stores the K ^ h("") in the key file (let's assume that the lengths match).

For validating a password, the content of the key file gets xored with h(password) ^ h(""). The result must be k || o, i.e., end with (at least) as many zeros as the length of o.

In order to change the password, the old password gets validated and when the check passes, the content of the key file gets xored with h(oldPassword) ^ h(newPassword) and the key file gets overwritten by the result.

I wonder whether the xoring is sufficient. It's quite possible the whole schema is a mess, but I couldn't find anything appropriate.

maaartinus
  • 684
  • 5
  • 12
  • 2
    Why reinvent the wheel (esp with security related things) when I am sure this already has been solved. E.g. harddisks (spinning rust or SSD) often can encrypt all their data with a key, and you need a (changeable) password to unlock that key. (Finding precisely how they implemented that though failed for me, but I am sure similar solutions are plenty). – Hennes Dec 15 '19 at 10:23
  • @Hennes I'm not trying to reinvent the wheel. I did it already :D, but I'm asking here in order to get a standard wheel. Encrypted harddisks etc., are completely unrelated to my problem as what I need is my app encrypting its files. What happens to the disk and/or other files is irrelevant. – maaartinus Dec 16 '19 at 00:06
  • Disk gets unecrypted data, stores it encrypted, using a password mechanism similars to yours. The actual disk itself in my answer was irrelevant. Maybe I should have written that as a longer answer making it more clear. – Hennes Dec 17 '19 at 05:57
  • @Hennes My whole disk is LUKS-encrypted. Would you recommend me to use a password manager not encrypting its data? – maaartinus Dec 17 '19 at 21:11
  • 3
    @maaartinus You're more likely to get a good answer on https://crypto.stackexchange.com/ since they focus on these topics specifically. That will be the best SE site when you're touching on the mathematics underlying crypto, including the "strength" of a particular algorithm or its vulnerability to analysis. – DoubleD Dec 17 '19 at 22:23

2 Answers2

2

I think your setup is actually ok, provided:

  • The h() function is a slow, iterated hash to slow brute force attacks
  • The encryption with master key is done correctly, e.g. AES-GCM
  • Key sizes are appropriate, probably 128-bit k and 256-bit o

But I would caution that you are right on the edge here, this is difficult stuff to get right and you could easily introduce weaknesses. Also, this is a quick analysis from a crypto enthusiast, not an expert.

paj28
  • 32,736
  • 8
  • 92
  • 130
1

The master key should be protected by Key Wrap - following NIST recommendations 800-38F (finalized in 2012) is the best approach.

Because the master key is not very long (128/256 bits) it is important to minimize the information exposed by the final encrypted master key ciphertext:

  • Random salt/IV. Don't expose the IV. See this answer from crypto stackexchange for a summary of the seemingly counterintuitive decision of NOT using a random salt. So much so it is better to use a hard coded IV that you do not need to include with the ciphertext.
  • Authentication. While several authenticated encryption schemes exist (e.g. AES-GCM), again we want to be careful to avoid exposing the authentication signature.

The NIST key wrap schemes incorporate these factors directly into the ciphertext with clever use of the XOR function - so all we need to supply is a strong encryption key.

HTLee
  • 1,772
  • 15
  • 30
  • Don't expose an IV because there are less moving parts in just using a constant (for single use keys) compared to reading it from a file. Do use a random salt because then you won't produce the same derived key if the password it was derived from wasn't original. (And also because it prevents precomputation based attacks or sharing work to attack multiple targets.) – Future Security Dec 19 '19 at 17:01
  • I don't see where it was suggested that you shouldn't use a random salt. The argument for nonce-misuse-resistant encryption is that it's less damaging if you inadvertently reuse a key-nonce pair. Deterministic encryption is a step below nonce-misuse-resistant encryption, equivalent to worst-case nonce-misuse-resistant encryption. Deterministic encryption is only required in special circumstances. Using nonce-misuse-resistant encryption with a random nonce is better. – Future Security Dec 19 '19 at 17:13
  • @FutureSecurity NIST key wrap specification does not rely on random salt/IV - the spec in fact uses a fixed IV. – HTLee Dec 20 '19 at 01:16
  • That's not what I meant. Use a salt when deriving the key used to wrap the data-encryption key. A consequence of that is that the wrapper key won't be used to wrap more than one key. The salt is for the KDF, not key wrapping. – Future Security Dec 20 '19 at 02:31
  • Ah I see... it would depend upon the key derivation function. If we follow what OP refers to above as k as simply a random key, we could use Java's SecureRandom function (IV/salt not required). But if you use PBKDF2 to derive the key from a password, then yes, salt is needed. – HTLee Dec 20 '19 at 07:40