Argon2

Last updated
Argon2
General
Designers
First published2015;10 years ago (2015)
Cipher detail
Digest sizes variable
Block sizes variable
Rounds variable

Argon2 is a key derivation function that was selected as the winner of the 2015 Password Hashing Competition. [1] [2] It was designed by Alex Biryukov, Daniel Dinu, and Dmitry Khovratovich from the University of Luxembourg. [3] The reference implementation of Argon2 is released under a Creative Commons CC0 license (i.e. public domain) or the Apache License 2.0, and provides three related versions:

Contents

All three modes allow specification by three parameters that control:

Cryptanalysis

While there is no public cryptanalysis applicable to Argon2d, there are two published attacks on the Argon2i function. The first attack is applicable only to the old version of Argon2i, while the second has been extended to the latest version (1.3). [5]

The first attack shows that it is possible to compute a single-pass Argon2i function using between a quarter and a fifth of the desired space with no time penalty, and compute a multiple-pass Argon2i using only N/e (≈ N/2.72) space with no time penalty. [6] According to the Argon2 authors, this attack vector was fixed in version 1.3. [7]

The second attack shows that Argon2i can be computed by an algorithm which has complexity O(n7/4 log(n)) for all choices of parameters σ (space cost), τ (time cost), and thread-count such that n=στ. [8] The Argon2 authors claim that this attack is not efficient if Argon2i is used with three or more passes. [7] However, Joël Alwen and Jeremiah Blocki improved the attack and showed that in order for the attack to fail, Argon2i v1.3 needs more than 10 passes over memory. [5]

To address these concerns, RFC9106 recommends using Argon2id to largely mitigate such attacks. [9]

Algorithm

Source: [4]

Function Argon2    Inputs:       password (P):       Bytes (0..232-1)    Password (or message) to be hashed       salt (S):           Bytes (8..232-1)    Salt (16 bytes recommended for password hashing)       parallelism (p):    Number (1..224-1)   Degree of parallelism (i.e. number of threads)       tagLength (T):      Number (4..232-1)   Desired number of returned bytes       memorySizeKB (m):   Number (8p..232-1)  Amount of memory (in kibibytes) to use       iterations (t):     Number (1..232-1)   Number of iterations to perform       version (v):        Number (0x13)The current version is 0x13 (19 decimal)       key (K):            Bytes (0..232-1)    Optional key (Errata: PDF says 0..32 bytes, RFC says 0..232 bytes)       associatedData (X): Bytes (0..232-1)    Optional arbitrary extra data       hashType (y):       Number (0=Argon2d, 1=Argon2i, 2=Argon2id)    Output:       tag:                Bytes (tagLength)The resulting generated bytes, tagLength bytes longGenerate initial 64-byte block H0.     All the input parameters are concatenated and input as a source of additional entropy.     Errata: RFC says H0 is 64-bits; PDF says H0 is 64-bytes.     Errata: RFC says the Hash is H^, the PDF says it's ℋ (but doesn't document what ℋ is). It's actually Blake2b.     Variable length items are prepended with their length as 32-bit little-endian integers.    buffer ← parallelism ∥ tagLength ∥ memorySizeKB ∥ iterations ∥ version ∥ hashType          ∥ Length(password)       ∥ Password          ∥ Length(salt)           ∥ salt          ∥ Length(key)            ∥ key          ∥ Length(associatedData) ∥ associatedData    H0 ← Blake2b(buffer, 64) //default hash size of Blake2b is 64-bytesCalculate number of 1 KB blocks by rounding down memorySizeKB to the nearest multiple of 4*parallelism kibibytes     blockCount ← Floor(memorySizeKB, 4*parallelism)     Allocate two-dimensional array of 1 KiB blocks (parallelism rows x columnCount columns)    columnCount ← blockCount / parallelism;   //In the RFC, columnCount is referred to as qCompute the first and second block (i.e. column zero and one ) of each lane (i.e. row)for i ← 0 to parallelism-1 dofor each row       Bi[0] ← Hash(H0 ∥ 0 ∥ i, 1024) //Generate a 1024-byte digest       Bi[1] ← Hash(H0 ∥ 1 ∥ i, 1024) //Generate a 1024-byte digestCompute remaining columns of each lanefor i ← 0 to parallelism-1 do//for each rowfor j ← 2 to columnCount-1 do//for each subsequent column//i' and j' indexes depend if it's Argon2i, Argon2d, or Argon2id (See section 3.4)          i′, j′ ← GetBlockIndexes(i, j)  //the GetBlockIndexes function is not defined          Bi[j] = G(Bi[j-1], Bi′[j′]) //the G hash function is not definedFurther passes when iterations > 1for nIteration ← 2 to iterations dofor i ← 0 to parallelism-1 dofor each rowfor j ← 0 to columnCount-1 do//for each subsequent column//i' and j' indexes depend if it's Argon2i, Argon2d, or Argon2id (See section 3.4)            i′, j′ ← GetBlockIndexes(i, j)            if j == 0 then               Bi[0] = Bi[0] xor G(Bi[columnCount-1], Bi′[j′])            else              Bi[j] = Bi[j] xor G(Bi[j-1], Bi′[j′])     Compute final block C as the XOR of the last column of each row    C ← B0[columnCount-1]    for i ← 1 to parallelism-1 do       C ← C xor Bi[columnCount-1]     Compute output tagreturn Hash(C, tagLength)

Variable-length hash function

Argon2 makes use of a hash function capable of producing digests up to 232 bytes long. This hash function is internally built upon Blake2.

{{#parsoid\0fragment:1}}  <span style="color:blue;">'''Function'''</span> Hash(message, digestSize)     <span style="color:blue;">'''Inputs:'''</span>        message:         Bytes (0..2<sup>32</sup>-1)     <span style="color:green;">Message to be hashed</span>        digestSize:      Integer (1..2<sup>32</sup>)     <span style="color:green;">Desired number of bytes to be returned</span>     <span style="color:blue;">Output:</span>        digest:          Bytes (digestSize)<sup>  </sup> <span style="color:green;">The resulting generated bytes, digestSize bytes long</span>       <span style="color:green;">'''Hash''' is a variable-length hash function, built using Blake2b, capable of generating     digests up to 2<sup>32</sup> bytes.</span>       <span style="color:green;">If the requested digestSize is 64-bytes or lower, then we use Blake2b directly</span>     '''if''' (digestSize <= 64) '''then'''        '''return''' Blake2b(digestSize ∥ message, digestSize) <span style="color:green;">// concatenate 32-bit little endian digestSize with the message bytes</span>       <span style="color:green;">For desired hashes over 64-bytes (e.g. 1024 bytes for Argon2 blocks),     we use Blake2b to generate twice the number of needed 64-byte blocks,     and then only use 32-bytes from each block</span>       <span style="color:green;">Calculate the number of whole blocks (knowing we're only going to use 32-bytes from each)</span>     r ← Ceil(digestSize/32)-2;       <span style="color:green;">Generate r whole blocks.</span>     <span style="color:green;">Initial block is generated from message</span>     V<sub>1</sub> ← Blake2b(digestSize ∥ message, 64);     <span style="color:green;">Subsequent blocks are generated from previous blocks</span>     '''for''' i ← 2 '''to''' r '''do'''        V<sub>i</sub> ← Blake2b(V<sub>i-1</sub>, 64)     <span style="color:green;">Generate the final (possibly partial) block</span>     partialBytesNeeded ← digestSize – 32*r;     V<sub>r+1</sub> ← Blake2b(V<sub>r</sub>, partialBytesNeeded)       <span style="color:green;">Concatenate the first 32-bytes of each block V<sub>i</sub>     (except the possibly partial last block, which we take the whole thing)</span>     <span style="color:green;">Let A<sub>i</sub> represent the lower 32-bytes of block V<sub>i</sub></span>     '''return''' A<sub>1</sub> ∥ A<sub>2</sub> ∥ ... ∥ A<sub>r</sub> ∥ V<sub>r+1</sub>

As of May 2023, OWASP's Password Storage Cheat Sheet recommends that people "use Argon2id with a minimum configuration of 19 MiB of memory, an iteration count of 2, and 1 degree of parallelism." [10]

OWASP recommends that Argon2id should be preferred over Argon2d and Argon2i because it provides a balanced resistance to both GPU-based attacks and side-channel attacks. [10]

OWASP further notes that the following Argon2id options provide equivalent cryptographic strength and simply trade off memory usage for compute workload: [10]

References

  1. "Password Hashing Competition"
  2. Jos Wetzels (2016-02-08). "Open Sesame: The Password Hashing Competition and Argon2". arXiv: 1602.03097 [cs.CR].
  3. Argon2: the memory-hard function for password hashing and other applications, Alex Biryukov, et al, October 1, 2015
  4. 1 2 Biryukov, Alex; Dinu, Daniel; Khovratovich, Dmitry; Josefsson, Simon (September 2021). "Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications" . Retrieved September 9, 2021.
  5. 1 2 Joël Alwen; Jeremiah Blocki (2016-08-05). Towards Practical Attacks on Argon2i and Balloon Hashing (PDF) (Report).
  6. Henry; Corrigan-Gibbs; Dan Boneh; Stuart Schechter (2016-01-14). Balloon Hashing: Provably Space-Hard Hash Functions with Data-Independent Access Patterns (PDF) (Report).
  7. 1 2 "[Cfrg] Argon2 v.1.3". www.ietf.org. Retrieved 2016-10-30.
  8. Joël Alwen; Jeremiah Blocki (2016-02-19). Efficiently Computing Data-Independent Memory-Hard Functions (PDF) (Report).
  9. "Recommendations". Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications. IETF. September 2021. sec. 7.4. doi: 10.17487/RFC9106 . RFC 9106 . Retrieved 12 July 2023.
  10. 1 2 3 "Password Storage Cheat Sheet". OWASP Cheat Sheet Series. OWASP. Retrieved 2023-05-17.