Argon2
2015 password-based key derivation function
- 6 min read
- 3 chapters
Explore on Lably
ToursExplore Lably’s 3D toursThe Argon2 function uses a large, fixed-size memory region (often called the 'memory array' in documentation) to make brute-force attacks computationally expensive. The three variants differ in how they access this memory:
- Argon2d maximizes resistance to GPU cracking attacks. It accesses the memory array in a password-dependent order, which reduces the possibility of time–memory trade-off (TMTO) attacks, but introduces possible side-channel attacks.
- Argon2i is optimized to resist side-channel attacks. It accesses the memory array in a password-independent order.
- Argon2id is a hybrid version. It follows the Argon2i approach for the first half pass over memory and the Argon2d approach for subsequent passes. RFC 9106 recommends using Argon2id if one does not know the difference between the types or if side-channel attacks are considered to be a viable threat.
All three modes allow specification by three parameters that control:
- execution time
- memory required
- degree of parallelism
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).
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. According to the Argon2 authors, this attack vector was fixed in version 1.3.
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=σ∗τ. The Argon2 authors claim that this attack is not efficient if Argon2i is used with three or more passes. 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.
To address these concerns, RFC9106 recommends using Argon2id to largely mitigate such attacks.
Sign in to keep reading
2 more chapters are free with a Lably account.
Algorithm
Source:
<span style="color:blue;" id="mwYg"><b id="mwYw">Function</b></span> Argon2 <span style="color:blue;" id="mwZA"><b id="mwZQ">Inputs:</b></span> password (<b id="mwZg">P</b>): Bytes (0..2<sup id="mwZw">32</sup>-1) <span style="color:green;" id="mwaA">Password (or message) to be hashed</span> salt (<b id="mwaQ">S</b>): Bytes (8..2<sup id="mwag">32</sup>-1) <span style="color:green;" id="mwaw">Salt (16 bytes recommended for password hashing)</span> parallelism (<b id="mwbA">p</b>): Number (1..2<sup id="mwbQ">24</sup>-1) <span style="color:green;" id="mwbg">Degree of parallelism (i.e. number of threads)</span> tagLength (<b id="mwbw">T</b>): Number (4..2<sup id="mwcA">32</sup>-1) <span style="color:green;" id="mwcQ">Desired number of returned bytes</span> memorySizeKB (<b id="mwcg">m</b>): Number (8p..2<sup id="mwcw">32</sup>-1) <span style="color:green;" id="mwdA">Amount of memory (in <a rel="mw:WikiLink" href="./Kibibyte" title="Kibibyte" class="mw-redirect" id="mwdQ">kibibytes</a>) to use</span> iterations (<b id="mwdg">t</b>): Number (1..2<sup id="mwdw">32</sup>-1) <span style="color:green;" id="mweA">Number of iterations to perform</span> version (<b id="mweQ">v</b>): Number (0x13) <span style="color:green;" id="mweg">The current version is 0x13 (19 decimal)</span> key (<b id="mwew">K</b>): Bytes (0..2<sup id="mwfA">32</sup>-1) <span style="color:green;" id="mwfQ">Optional key (Errata: PDF says 0..32 bytes, RFC says 0..2<sup id="mwfg">32</sup> bytes)</span> associatedData (<b id="mwfw">X</b>): Bytes (0..2<sup id="mwgA">32</sup>-1) <span style="color:green;" id="mwgQ">Optional arbitrary extra data</span> hashType (<b id="mwgg">y</b>): Number (0=Argon2d, 1=Argon2i, 2=Argon2id) <span style="color:blue;" id="mwgw"><b id="mwhA">Output:</b></span> tag: Bytes (tagLength) <span style="color:green;" id="mwhQ">The resulting generated bytes, tagLength bytes long</span> <span style="color:green;" id="mwhg"><i id="mwhw">Generate initial 64-byte block H<sub id="mwiA">0</sub>.</i> All the input parameters are concatenated and input as a source of additional entropy. Errata: RFC says H<sub id="mwiQ">0</sub> is 64-bits; PDF says H<sub id="mwig">0</sub> 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.</span> buffer ← parallelism ∥ tagLength ∥ memorySizeKB ∥ iterations ∥ version ∥ hashType ∥ Length(password) ∥ Password ∥ Length(salt) ∥ salt ∥ Length(key) ∥ key ∥ Length(associatedData) ∥ associatedData H<sub id="mwiw">0</sub> ← Blake2b(buffer, 64) <span style="color:green;" id="mwjA"><i id="mwjQ">//default hash size of Blake2b is 64-bytes</i></span> <span style="color:green;" id="mwjg">Calculate number of 1<span typeof="mw:Entity" id="mwjw"> </span>KB blocks by rounding down memorySizeKB to the nearest multiple of 4*parallelism <a rel="mw:WikiLink" href="./Kibibyte" title="Kibibyte" class="mw-redirect" id="mwkA">kibibytes</a></span> blockCount ← Floor(memorySizeKB, 4*parallelism) <span style="color:green;" id="mwkQ">Allocate two-dimensional array of 1<span typeof="mw:Entity" id="mwkg"> </span>KiB blocks (parallelism rows x columnCount columns)</span> columnCount ← blockCount / parallelism; <span style="color:green;" id="mwkw">//In the RFC, columnCount is referred to as <b id="mwlA">q</b></span> <span style="color:green;" id="mwlQ">Compute the first and second block (i.e. column zero and one) of each lane (i.e. row)</span> <b id="mwlg">for</b> i ← 0 <b id="mwlw">to</b> parallelism-1 <b id="mwmA">do</b> <span style="color:green;" id="mwmQ">for each row</span> B<sub id="mwmg">i</sub>[0] ← Hash(H<sub id="mwmw">0</sub> ∥ 0 ∥ i, 1024) <span style="color:green;" id="mwnA"><i id="mwnQ">//Generate a 1024-byte digest</i></span> B<sub id="mwng">i</sub>[1] ← Hash(H<sub id="mwnw">0</sub> ∥ 1 ∥ i, 1024) <span style="color:green;" id="mwoA"><i id="mwoQ">//Generate a 1024-byte digest</i></span> <span style="color:green;" id="mwog">Compute remaining columns of each lane</span> <b id="mwow">for</b> i ← 0 <b id="mwpA">to</b> parallelism-1 <b id="mwpQ">do</b> <span style="color:green;" id="mwpg">//for each row</span> <b id="mwpw">for</b> j ← 2 <b id="mwqA">to</b> columnCount-1 <b id="mwqQ">do</b> <span style="color:green;" id="mwqg">//for each subsequent column</span> <span style="color:green;" id="mwqw">//i' and j' indexes depend if it's Argon2i, Argon2d, or Argon2id (See section 3.4)</span> i′, j′ ← GetBlockIndexes(i, j) <span style="color:green;" id="mwrA">//the GetBlockIndexes function is not defined</span> B<sub id="mwrQ">i</sub>[j] = G(B<sub id="mwrg">i</sub>[j-1], B<sub id="mwrw">i′</sub>[j′]) <span style="color:green;" id="mwsA">//the G hash function is not defined</span> <span style="color:green;" id="mwsQ">Further passes when iterations > 1</span> <b id="mwsg">for</b> nIteration ← 2 <b id="mwsw">to</b> iterations <b id="mwtA">do</b> <b id="mwtQ">for</b> i ← 0 <b id="mwtg">to</b> parallelism-1 <b id="mwtw">do</b> <span style="color:green;" id="mwuA">for each row</span> <b id="mwuQ">for</b> j ← 0 <b id="mwug">to</b> columnCount-1 <b id="mwuw">do</b> <span style="color:green;" id="mwvA">//for each subsequent column</span> <span style="color:green;" id="mwvQ">//i' and j' indexes depend if it's Argon2i, Argon2d, or Argon2id (See section 3.4)</span> i′, j′ ← GetBlockIndexes(i, j) <b id="mwvg">if</b> j == 0 <b id="mwvw">then</b> B<sub id="mwwA">i</sub>[0] = B<sub id="mwwQ">i</sub>[0] xor G(B<sub id="mwwg">i</sub>[columnCount-1], B<sub id="mwww">i′</sub>[j′]) <b id="mwxA">else</b> B<sub id="mwxQ">i</sub>[j] = B<sub id="mwxg">i</sub>[j] xor G(B<sub id="mwxw">i</sub>[j-1], B<sub id="mwyA">i′</sub>[j′]) <span style="color:green;" id="mwyQ">Compute final block <b id="mwyg">C</b> as the XOR of the last column of each row</span> C ← B<sub id="mwyw">0</sub>[columnCount-1] <b id="mwzA">for</b> i ← 1 <b id="mwzQ">to</b> parallelism-1 <b id="mwzg">do</b> C ← C <b id="mwzw">xor</b> B<sub id="mw0A">i</sub>[columnCount-1] <span style="color:green;" id="mw0Q">Compute output tag</span> <b id="mw0g">return</b> 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.
<span style="color:blue;" id="mw2Q"><b id="mw2g">Function</b></span> Hash(message, digestSize) <span style="color:blue;" id="mw2w"><b id="mw3A">Inputs:</b></span> message: Bytes (0..2<sup id="mw3Q">32</sup>-1) <span style="color:green;" id="mw3g">Message to be hashed</span> digestSize: Integer (1..2<sup id="mw3w">32</sup>) <span style="color:green;" id="mw4A">Desired number of bytes to be returned</span> <span style="color:blue;" id="mw4Q">Output:</span> digest: Bytes (digestSize) <span style="color:green;" id="mw4g">The resulting generated bytes, digestSize bytes long</span> <span style="color:green;" id="mw4w"><b id="mw5A">Hash</b> is a variable-length hash function, built using Blake2b, capable of generating digests up to 2<sup id="mw5Q">32</sup> bytes.</span> <span style="color:green;" id="mw5g">If the requested digestSize is 64-bytes or lower, then we use Blake2b directly</span> <b id="mw5w">if</b> (digestSize <= 64) <b id="mw6A">then</b> <b id="mw6Q">return</b> Blake2b(digestSize ∥ message, digestSize) <span style="color:green;" id="mw6g">// concatenate 32-bit little endian digestSize with the message bytes</span> <span style="color:green;" id="mw6w">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;" id="mw7A">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;" id="mw7Q">Generate r whole blocks.</span> <span style="color:green;" id="mw7g">Initial block is generated from message</span> V<sub id="mw7w">1</sub> ← Blake2b(digestSize ∥ message, 64); <span style="color:green;" id="mw8A">Subsequent blocks are generated from previous blocks</span> <b id="mw8Q">for</b> i ← 2 <b id="mw8g">to</b> r <b id="mw8w">do</b> V<sub id="mw9A">i</sub> ← Blake2b(V<sub id="mw9Q">i-1</sub>, 64) <span style="color:green;" id="mw9g">Generate the final (possibly partial) block</span> partialBytesNeeded ← digestSize – 32*r; V<sub id="mw9w">r+1</sub> ← Blake2b(V<sub id="mw-A">r</sub>, partialBytesNeeded) <span style="color:green;" id="mw-Q">Concatenate the first 32-bytes of each block V<sub id="mw-g">i</sub> (except the possibly partial last block, which we take the whole thing)</span> <span style="color:green;" id="mw-w">Let A<sub id="mw_A">i</sub> represent the lower 32-bytes of block V<sub id="mw_Q">i</sub></span> <b id="mw_g">return</b> A<sub id="mw_w">1</sub> ∥ A<sub id="mwAQA">2</sub> ∥ ... ∥ A<sub id="mwAQE">r</sub> ∥ V<sub id="mwAQI">r+1</sub>
Recommended minimum parameters
The Request for Comments document standardizing Argon2, which was published in September 2021, recommends:
- Memory: 2 GiB, Iterations: 1, Parallelism: 4; for "a default setting for all environments"
- Memory: 64 MiB, Iterations: 3, Parallelism: 4; for "memory-constrained environments"

