Passwords, Keys, and Identity
Not-so-random numbers.
Conditioning high-entropy secrets into usable keys.
info string is a domain separator: different labels derive independent keys from the same \(t\)\[ \begin{aligned} &\textbf{Extract: } t \leftarrow \text{HMAC}(\mathit{salt},\, s) \\[4pt] &\textbf{Expand: } q \leftarrow \lceil L / \text{HashLen} \rceil \\ &\quad z_0 \leftarrow \varepsilon \\ &\quad \textbf{for } i = 1 \textbf{ to } q \textbf{ do:} \\ &\qquad z_i \leftarrow \text{HMAC}(t,\; z_{i-1} \,\|\, \text{info} \,\|\, \text{octet}(i)) \\ &\quad \textbf{return first } L \text{ octets of } z_1 \,\|\, \cdots \,\|\, z_q \end{aligned} \]
info labels derive independent keys: client traffic key, server traffic key, handshake key, resumption secretinfo labels produce keys that are independent under HMAC’s PRF securitySlowing down the attacker, not the user.
\[ \begin{aligned} &F(pw,\, salt,\, c,\, i): \\ &\quad U_1 \leftarrow \text{PRF}(pw,\, salt \,\|\, \text{INT}(i)) \\ &\quad \textbf{for } j = 2 \textbf{ to } c\textbf{:} \\ &\qquad U_j \leftarrow \text{PRF}(pw,\, U_{j-1}) \\ &\quad \textbf{return } U_1 \oplus U_2 \oplus \cdots \oplus U_c \\[6pt] &\text{PBKDF2}(pw,\, salt,\, c,\, L): \\ &\quad q \leftarrow \lceil L \,/\, \text{HashLen} \rceil \\ &\quad \textbf{return first } L \text{ bytes of } F(pw,\, salt,\, c,\, 1) \,\|\, \cdots \,\|\, F(pw,\, salt,\, c,\, q) \end{aligned} \]
\[ \begin{aligned} &H_0 \leftarrow \text{BLAKE2b}(p \;\|\; \tau \;\|\; m \;\|\; t \;\|\; v \;\|\; y \;\|\; \text{len}(P) \;\|\; P \;\|\; \text{len}(S) \;\|\; S \;\|\; \cdots) \\[4pt] &B[i][0] \leftarrow H'(H_0 \;\|\; 0 \;\|\; i), \quad B[i][1] \leftarrow H'(H_0 \;\|\; 1 \;\|\; i) \end{aligned} \]
Picture memory as a \(p \times q\) grid: \(p\) lanes (rows), \(q = m / p\) columns, each cell a 1024-byte block; lanes can be filled concurrently
Bootstrap from a fingerprint of every input, then seed the first two columns of each lane
\(H_0\) commits to parameters \((p, \tau, m, t)\), version \(v\), and variant tag \(y \in \{0, 1, 2\}\) for Argon2d/i/id, followed by the length-prefixed password \(P\) and salt \(S\); all integer fields are 32-bit little-endian
Placing \(y\) before \(P\) is what domain-separates the three variants
\(H'\) is BLAKE2b extended to arbitrary output length; it stretches \(H_0\) into the \(2p\) seed blocks
After setup, \(2p\) blocks are filled
\[ \begin{aligned} &\textbf{for } r = 0 \textbf{ to } t - 1 \textbf{ do:} \\ &\quad \textbf{for slice } s = 0 \textbf{ to } 3 \textbf{ do:} \\ &\qquad \textbf{for each lane } i \textbf{ and column } j \textbf{ in slice } s \;(j \geq 2) \textbf{ do:} \\ &\qquad\quad \textbf{if } r = 0 \textbf{ and } s < 2: \; (i', j') \leftarrow \phi_{\text{i}}(r, s, i, j) \\ &\qquad\quad \textbf{else:} \; (i', j') \leftarrow \phi_{\text{d}}(B[i][j-1]) \\ &\qquad\quad B[i][j] \leftarrow G(B[i][j-1],\; B[i'][j']) \oplus B[i][j] \quad (r > 0) \end{aligned} \]
Walk each lane left to right for \(t\) passes; every new block compresses its left neighbour with a reference block fetched from elsewhere in memory
Each pass is split into 4 slices of \(q/4\) columns; lanes are filled in parallel within a slice and synchronise at slice boundaries, so the reference set grows in lock-step
\(\phi_{\text{i}}\) picks \((i', j')\) from a counter (fixed access pattern), used for slices 0–1 of pass 0; defeats cache-timing side channels while the password is still recoverable from access traces
\(\phi_{\text{d}}\) picks \((i', j')\) from the content of \(B[i][j-1]\) (password-dependent pattern), used in every other slice; forces an attacker to actually keep the grid in RAM
\(G\) is a 1024-byte compression built from BLAKE2b internals
The trailing XOR on later passes binds each block to its prior-pass value at the same coordinates, so an attacker can’t reuse memory across passes or skip intermediate passes
\[ \begin{aligned} &C \leftarrow \bigoplus_{i=0}^{p-1} B[i][q-1] \\[4pt] &\textbf{return } H'(C,\; \tau) \end{aligned} \]
Better ways to use a password.
\[ \begin{array}{lcl} \textbf{Client} & & \textbf{Server} \\[6pt] & \textit{Registration} & \\[3pt] v \leftarrow g^{H(\mathit{salt},\, \mathit{pw})} \bmod N & \xrightarrow{\quad(\mathit{salt},\; v)\quad} & \text{stores } (\mathit{salt},\; v) \\[10pt] & \textit{Authentication} & \\[3pt] A \leftarrow g^a \bmod N & \xrightarrow{\quad A \quad} & B \leftarrow kv + g^b \bmod N \\ K \leftarrow H(\ldots,\, \mathit{pw}) & \xleftarrow{\quad(\mathit{salt},\; B)\quad} & \\ & & K \leftarrow H(\ldots,\, v) \\[6pt] & \textit{both verify the other derived the same } K & \end{array} \]
draft-irtf-cfrg-opaque) is the modern replacement for SRP| Scheme | Server sees plaintext? | Credential reuse? | Offline cracking after breach? |
|---|---|---|---|
| Plaintext storage | Yes, always | Yes | Immediately |
| Salted hash | Yes, at login | Yes | Yes (GPU) |
| PBKDF2 / Argon2 | Yes, at login | Yes | Yes (slower) |
| Challenge-response | No | No | Yes (verifier) |
| SRP | No | No | Weak passwords only |
| OPAQUE | No | No | No |
All PAKE schemes (challenge-response onward) provide mutual authentication and fresh session key derivation. The columns above capture breach and live-exposure properties only.
Closing the gap between human memory and cryptographic uniformity.
Ask now, catch me after class, or email eoin@eoin.ai