1
$\begingroup$

Everyone knows Argon2id is a slow hashing algorithm, and that's on purpose, all is good.

When creating an Argon2id object a lot of parameters are needed to be taken into consideration ultimately settling on a configuration that is strong enough but within the amount of time that you're willing to wait for the operation to happen.

I'm using Argon2id in a cryptographic application where I take a user password, generate a random salt of 32 bytes and use them to create a hash of a key to use for AES-GCM encryption.

I am currently generating 1 salt for 1 key per operation. An operation can encrypt 1 or n files, my current tests are running for like 550 files. Everytime I start an encryption operation, even if I'm using the same password, a new salt is generated, but is used for all files that will be encrypted within the operation.

An alternative generating a new salt, which would result in a new key for the same password for each file, which is obviously more secure, but painfully way more slower even when I run it with a degree of parallelism of 8 concurrent file.

The single key approach uses:

768 MB RAM

7 iterations

12 threads

This clocks at 3.3 seconds hashing operation that is done once and the rest is lightning fast (finished in less than 10 seconds with 8 files in parallel)

The multiple keys approach uses:

32 MB RAM

14 iterations

12 threads

This clocks at 400 ms hashing operations that are done for each file, those parameters provide a somewhat acceptable (finishes in nearly 2 minutes with a 8 files in parallel)

My rationale for using the same key per encryption operation was that you can consider the 'n' files (in this case 550) as a single zip file that has all of them, encrypted with 1 key, the security would be equivalent. If you are able to break the zip's encryption you get access to the 550 files, unless my logic is flawed.

I need an advice if the trade off in Argon2id parameters for unique salts and slower time is more worth it, or is my initial approach acceptable in the world of cryptography. Thanks!

$\endgroup$

2 Answers 2

2
$\begingroup$

As someone who maintains a file encryption program, I would recommend the ZIP file approach without compression for directories/subdirectories. Then you derive one key to encrypt one file, eliminating such massive delay. This can have other benefits as well depending on whether you're encrypting files in chunks/hiding the exact file length with padding.

If you don't want to pack the files together (e.g. so they can be individually decrypted), you can generate one salt for an entire directory and store it in the parent directory (perhaps each subdirectory as a backup) as well as in each file. If the salt file gets deleted/renamed, you can decrypt each file individually using the salt stored in each file. This is like what Cryptomator does.

For the latter approach, I'd suggest the KEK/DEK paradigm. That involves randomly generating a data encryption key (DEK) to encrypt each file and then encrypting each DEK with the key encryption key (KEK) derived using Argon2. The encrypted DEK then becomes a header in each file.

In terms of other things, you could reduce the salt to 128 bits if you wanted to store less data because that's the standard size and enough. 3 seconds is also a bit much in my opinion for file encryption; I'd cap it at 1-1.5 seconds for usability sake. You can get away with much less if you expect passwords to be of a certain strength, which is why I presume age uses such little delay.

$\endgroup$
7
  • 1
    $\begingroup$ This gave me many ideas to search and get into, I really like the KEK/DEK idea and I will delve into it to understand it a bit more. For this to work I think I will have to use a faster algorithm than argon2id with unique salts, and then use argon2id to encrypt the encrypted files with 1 unique key? Disk space is absolutely not an issue with me. Doesn't the 256 bits salt makes it future proof if the disk space is not an issue? I also might think to for example use a zip library and get them into 1 file and encrypt it, and then on decryption unzip everything? $\endgroup$
    – Elie-M
    Commented Jul 16, 2022 at 22:13
  • $\begingroup$ I'd stick with Argon2id. The main contender is Balloon hashing, but it's not in any libraries and no information is available on parameters. You could lower the parameters or use a more efficient cryptographic library (e.g. libsodium) to get less delay. With KEK/DEK, you derive one KEK using Argon2 to encrypt all the DEKs yes. 256 bits is completely fine for future proofing/if you have the space; it's just unlikely to be necessary. Finally, that's correct; if some files won't be ZIPs, you could store whether it's a ZIP in an encrypted header next to the DEK (e.g. a bool converted to a byte). $\endgroup$ Commented Jul 17, 2022 at 7:18
  • $\begingroup$ To expand on that, you could use Argon2 to derive one key and then derive a unique key for each file using something like salted HKDF. Then you'd store extra salts instead of encrypted DEKs. The problem is always where to store the salt for Argon2. For delay, ~500ms is a decent compromise. There should be no insecure parameter choices for Argon2; it's just how much you want to slow someone down. $\endgroup$ Commented Jul 17, 2022 at 7:29
  • $\begingroup$ Since there's no harm for salts to be visible I always do: salt | nonce | tag | cipher. So if I use your suggestion I might do " salt' | nonce' | tag' | (cipher: salt | nonce | tag | cipher )' " I had used libsodium before settling on my current library and I found it a bit limited + unmaintained on top of that. I will research Balloon hashing to see if there's a proper way to implement it. I'm using C# .Net Core $\endgroup$
    – Elie-M
    Commented Jul 17, 2022 at 13:25
  • 1
    $\begingroup$ Marking this as the accepted answer, since after researching the suggestions, envelope encryption seems to be the thing that I was looking for in this scenario. $\endgroup$
    – Elie-M
    Commented Jul 18, 2022 at 10:48
0
$\begingroup$

I recommend using the same salt $S$ for the same password $P$ in multiple encryptions, and a single Argon2id password-to-key derivation of $(P,S)$ into a single AES key $K$. That's best, because it allows to use higher cost parameters in Argon2id (e.g. that of the question's single key approach). That's safe, as long as a fresh and unique AES nonce $N_i$ is used for the AES-GCM encryption each file.

Each independently decipherable ciphertext file should contain (e.g. at start) the Argon2id parameters, $S$, and $N_i$, in order to allow decryption. Decryption can cache the last $K$, and reuse it when the inputs $(P,S)$ and parameters are unchanged in the next file.

Possible improvement: if it's wanted a unique key $K_i$ per file, that can be done by a secondary fast key derivation of $(K,N_i)$. That can be Argon2id with input and the lightest parameters possible $(p=1, m=8, t=1)$. This also limits to almost nothing the consequence of a repeated $N_i$ or counter overlap.

If no trusted CSPRNG is available to generate the $N_i$, that can be done by another secondary key derivation function, e.g. as Argon2id with input $(K,i)$ and the lightest different parameters possible, e.g. $(p=1, m=8, t=1, X=\mathtt{'N'})$

$\endgroup$
3
  • $\begingroup$ I'm not sure what's the benefit here. My main complaint is that Argon2id is slow, so for this nice idea to work, I think I would have to use a much faster algorithm like PBKDF2 with unique salts, then encrypt the encrypted files with 1 argon2id strong key. Unless there's something I am missing from your proposed idea, it wouldn't fix my issue with the process being very slow. $\endgroup$
    – Elie-M
    Commented Jul 16, 2022 at 22:09
  • $\begingroup$ @Elie-M : I tried to clarify. $\endgroup$
    – fgrieu
    Commented Jul 17, 2022 at 5:52
  • $\begingroup$ Much much appreciated, this makes a lot of sense and is kind of in accordance with the other answer here. I'm already using 1 safely generated nonce per file. Encryption appends "salt | nonce | tag | cipher". I will try to implement and benchmark a double argon2id encryption one that is very light and another strong for all and it becomes " salt' | nonce' | tag' | (cipher: salt | nonce | tag | cipher )' " and see how that goes $\endgroup$
    – Elie-M
    Commented Jul 17, 2022 at 13:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.