How to securely encrypt a file with an insecure password in Rust
kerkour.com
kerkour.com
The output of a KDF does not contain more entropy than the input. You cannot "create" entropy with a KDF. Sure it _looks_ random, but no actual randomness was added during the process.
A KDF adds some security against brute force attacks by making them more expensive, but it does not add entropy, it does not increase the search space of a brute force attack.
Having said this, I want to put a word in for a design change I think all of these tools should consider: don't accept user-provided passphrases by default. Instead, generate passphrases for the user, with a wordlist and entropy target.
Encrypting programs can still accept a (bad) passphrase with an option! But it shouldn't be the default behavior.
I like this idea, but proper passphrase generation is hard:
1. My personal estimation is there maybe 2^(11-12) words that nearly-all English speakers know and spell the same way; note that this is much smaller than the vocabularies of the lowest percentile, but two people with 5k word vocabularies will have an intersection smaller than 5k words, and there are words spelled differently in different dialects. Also rarely used words are harder to recall.
2. At least for me, I confuse similarly sounding words on recall
3. There are lots of non-English speakers
4. You probably don't want your passphrase generator spitting out double-entendres
For my own personal use, I took the 2k most common words in the English language, used metaphone to remove words that were likely to sound the same, and I don't care about #3 and #4. This left me with over 512 but less than 1024 words remaining. 9 bits of entropy per key, a 5 word passphrase is memorable for most people giving you 45 bits of entropy total. I'm not a cryptographer, but I suspect 45 bits is "good enough" with something non-fancy like PBKDF2-HMAC-SHA1 and an improvement over a prompt, but I haven't solved all of the problems on the list. I think #4 can be safely ignored for a free product, but that concern specifically caused us to swap out passphrases for an alphanumeric password for a tool used at work.
[1]: https://theworld.com/~reinhold/diceware.html [2]: https://www.eff.org/deeplinks/2016/07/new-wordlists-random-p... [3]: https://xkcd.com/936/
Additionally, there's support for stack-allocated secrets which lack some of the extra protections that heap-allocated secrets have, but is often a much more appropriate approach for short-lived secrets.
A downside of this is that it relies on unsafe code in order to call into libsodium and convert back and forth between pointers. And of course it has a libsodium dependency.
Full post with details here: https://ayende.com/blog/196289-C/dont-assume-the-result-of-r...
Further, argon2id incorporates strategies to make GPU parallelization less effective.
Obviously this won't protect you against something like a dictionary attack, there's nothing that can magically protect you if you choose a low-entropy password, just something that can make the process more difficult.
Ignoring the extra time for decryption there's no difference between a unique low-entropy password that takes 2 years to bruteforce and a high-entropy password that takes 2 years to bruteforce.
If we're talking a more random but still short password (for example, just 8 random alphanumeric characters is log2(262+10)8 = ~48 bits), then the KDF becomes very attractive to help skyrocket the brute forcing cost to something more similar to trying to brute force the 256 bit key instead.
Edit: The defaults for the argon2 crate are here[1]. They seem to prioritize time cost over memory cost. Some random searching online suggests that a time cost of 3 corresponds to roughly ~2 seconds on modern hardware, so running a 100k dictionary with a time cost of 3 would require ~27.7 hours for the amortized find (50k) or ~55.5 for the worst case find. So, this isn't a very good scheme for a motivated (or parallel) attacker and an exceptionally weak password.
Let's imagine that you increased the time by 10x. That's 277 hours for a password. That's not very long at all - 12 days. Even if you increased by 100x, 120 days is not crazy, and presumably attackers can go way faster than your assumption.
A KDF isn't going to be enough to save you if you're using a top 100k password and the attacker can bruteforce offline.
Of course, users won't do this for themselves, which is why tools that do passphrase encryption should generate passwords by default, and accept user-provided passwords only as a non-default option.
Passphrases still have value, even when they're long strings of words: they're easy to write down, easy to repeat aloud, and easier than a random string to "recognize" visually.
I will add a conclusion with an explanation of the security of the system.
TL;DR: You can play with Argon2's parameters to use the resources that fit your requirements.
People get hung up on this, because the nonce looks like it could serve as a key; if you keep the key hidden, an attacker can't brute-force your hash at all. The obvious response to that is: if you can do keep your nonce secret like that, just get rid of the passwords, and key your system with actual keys. Or, store your passwords in the super-secure place you store the nonces.
These discussions quickly rabbithole into analyses of the varying levels of security between filesystems, HSMs, program memory, networked filesystems, the kernel, VM boundaries, and the difficulty for an attacker of assembling all these components at once. It's all pretty silly. But the answer to your question is simple: a salt (or nonce) isn't a key; that's not the purpose it serves in the design. If you really want to key your password hash, you don't need to muck with the salt to do that.
I would expect the entropy to remain the same.
Since the salt is included in the output of the KDF, is it really adding entropy to the key?
Using all utf-8 characters seems like it combines the downsides of both of these (not really human readable/writeable but also not using the full key space).
Restricting yourself to ascii means you don't need to worry about text encoding. Who knows when you end up needing to paste it, or when something decides to be helpful and messes up the encodings.
The tradeoff for using a smaller character set is longer passphrases (for shorter character sets) versus "less humane" passphrases, for a given level of target entropy.