[HN Gopher] How to securely encrypt a file with an insecure pass...
       ___________________________________________________________________
        
       How to securely encrypt a file with an insecure password in Rust
        
       Author : sylvain_kerkour
       Score  : 46 points
       Date   : 2022-01-19 16:38 UTC (6 hours ago)
        
 (HTM) web link (kerkour.com)
 (TXT) w3m dump (kerkour.com)
        
       | boomer918 wrote:
       | If the salt is stored with the file, how is this safe against a
       | brute force attack against the low entropy password?
        
         | tptacek wrote:
         | The purpose of a "salt" is just to randomize the hash; an
         | attacker can precalculate a dictionary for a hash function H,
         | but they can't plausibly precalculate 2^128 dictionaries for
         | the family of hash functions H_nonce.
         | 
         | 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.
        
         | woodruffw wrote:
         | In this case, the security is in the memory and/or time
         | hardness of the KDF: a motivated attacker could use the salt
         | with their dictionary, but would have to be willing to wait (on
         | average) a decent, perhaps deterring, amount of time (or
         | similar for memory).
         | 
         | 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.
        
           | staticassertion wrote:
           | I think the short answer is "don't be in a dictionary". Using
           | a unique password is critically important.
           | 
           | 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.
        
         | jffry wrote:
         | My understanding is that the key derivation function chosen,
         | argon2, is much more expensive to compute than something like
         | the SHA family of hashes. This is a desirable property in a KDF
         | precisely because it makes brute forcing much more difficult.
         | 
         | 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.
        
           | Grimburger wrote:
           | > there's nothing that can magically protect you if you
           | choose a low-entropy password
           | 
           | 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.
        
           | tyingq wrote:
           | It does seem like talking about dictionary attacks in the
           | article would be helpful. Without some reference as to how
           | many argon2 hashes per minute is reasonably possible with the
           | shown settings, we're flying a little blind.
        
             | woodruffw wrote:
             | I performed a rough estimate in my comment up the thread,
             | using ~2s per Argon2id with a time cost of 3. TL;DR is that
             | you probably wouldn't want to have an extremely common
             | password with this scheme.
        
               | stouset wrote:
               | There's no KDF in the world that can protect you if your
               | password is in a top-10,000 list or exposed elsewhere
               | alongside your username.
        
               | tyingq wrote:
               | Right, but that's sort of what I was getting at. The
               | article doesn't talk much about the password other than
               | it's "insecure". It's probably worth mentioning that a
               | dictionary attack at some multiple of ~2/per-second/per-
               | core is possible. So it's not just top-10,000 list, but
               | maybe "top million" or more that's a bad idea.
        
               | woodruffw wrote:
               | Yes, I think that's what the GP was trying to say. The
               | post doesn't qualify "insecure" meaning "not best
               | practices" vs. "insecure" meaning "your password is an
               | extremely common one."
        
         | kevincox wrote:
         | It should probably be regarded as "relatively secure"
         | encryption. You can't have truly secure encryption if your
         | passphrase is garbage.
        
         | sylvain_kerkour wrote:
         | Good question!
         | 
         | 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.
        
       | one_off_comment wrote:
       | Tangential thought: if you're using a passphrase you're not going
       | to ever type manually, for example something you're going to
       | generate once and stick in a secret management system, why not
       | build the passphrase using all possible UTF-8 characters as your
       | corpus? Seems like restricting yourself to ASCII characters is
       | just giving an advantage to those attempting to brute force the
       | passphrase.
        
         | [deleted]
        
         | resoluteteeth wrote:
         | This doesn't make much sense to me. The point of a passphrase
         | is to be readable/writeable by a human. If you don't need that,
         | you just want a binary key (which can be base64 encoded/decoded
         | to be read/written by a human).
         | 
         | 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).
        
         | kzrdude wrote:
         | I'd be happy with just 32 bytes of random alphanumeric ascii,
         | it doesn't really need improvement. If that gives a too big
         | advantage, then use more.
        
         | toast0 wrote:
         | > why not build the passphrase using all possible UTF-8
         | characters as your corpus? Seems like restricting yourself to
         | ASCII characters is just giving an advantage to those
         | attempting to brute force the passphrase.
         | 
         | 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.
        
       | woodruffw wrote:
       | Not my crate, but I'll plug secrecy[1] as a better alternative to
       | calling `zeroize()` manually on each variable containing secret
       | material. That provides `Secret<S: Zeroize>`, which provides
       | reliable zero-on-drop semantics.
       | 
       | [1]: https://docs.rs/secrecy/latest/secrecy/
        
         | stouset wrote:
         | Alternatively, you could use secrets[1] (full disclosure, I'm
         | the author). The secrecy crate does very little to prevent you
         | from accidentally creating copies of secret data. Additionally,
         | my crate punts to libsodium's memory management[2] API so
         | provides all the features it does: memory is prevented from
         | being paged to disk, memory is protected by `mprotect` when not
         | being accessed, memory is protected with guard pages before and
         | after, and memory is preceded by a canary to protect against
         | underflow. secrets' functionality and protections are
         | essentially a superset of secrecy's.
         | 
         | 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.
         | 
         | 1: https://docs.rs/secrets/latest/secrets/
         | 
         | 2: https://doc.libsodium.org/memory_management
        
       | admax88qqq wrote:
       | This is inaccurate and borderline dangerous advice.
       | 
       | 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.
        
         | omoikane wrote:
         | See also: https://en.wikipedia.org/wiki/Key_stretching
        
       | charcircuit wrote:
       | How does a KDF _add_ entropy to a passphrase?
       | 
       | I would expect the entropy to remain the same.
        
         | staticassertion wrote:
         | It adds a salt. Otherwise entropy shouldn't really be a factor
         | - your input password could be more entropic than your output
         | hash, in terms of shannon entropy/ number of bits needed to
         | represent the values.
        
           | charcircuit wrote:
           | I am referring to the diagram saying low entropy passphase ->
           | high entropy key
           | 
           | Since the salt is included in the output of the KDF, is it
           | really adding entropy to the key?
        
             | staticassertion wrote:
             | Yeah, I found the diagram a bit confusing as well. I don't
             | think that a KDF is adding any entropy.
        
         | admax88qqq wrote:
         | It doesn't, this article is inaccurate
        
       | tptacek wrote:
       | I'd generally look at anything like this as a code smell. If
       | you're looking for simple file encryption in Rust, and you'd
       | consider doing something as bespoke as this, just use `rage` (and
       | its `age` crate). As a bonus, you get interop with Go (the
       | reference implementation of age is in Go).
       | 
       | https://github.com/str4d/rage
       | 
       | 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.
        
         | aidenn0 wrote:
         | > 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.
         | 
         | 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.
        
           | tptacek wrote:
           | This is all true, but my take would be that you can't do
           | worse than users will do with a password prompt. By all
           | means, leave an option for fussy users to provide their own.
        
         | staticassertion wrote:
         | I guess some context is that this is intended to be educational
         | and the author also has a book on the topic.
        
       ___________________________________________________________________
       (page generated 2022-01-19 23:01 UTC)