This dataset is being compiled while we research realistic Argon2id parameter choices for the Who Technologies mTOTP protocol; we're publishing the results because the data is likely useful to the broader community working with Argon2. The full raw dataset (one row per measured hash) will be released free for anyone to use once ready.
Each cell is one (m × t) parameter point at p = 1.
Defender platforms (Android, iOS, Desktop/Server) hash 1-3 times (depending on the
speed of the hash) which are averaged together for that device.
Attacker (GPU) uses hashcat --speed-only to measure steady-state throughput.
The heatmap always shows one platform at a time so defender and attacker results are never mixed.
Android: lazysodium-android 5.1.0 (libsodium JNI bindings). Desktop/Server: PyNaCl (libsodium Python bindings; version varies by host). GPU: hashcat v7.1.2 (patched), mode 34000.
Three patches were applied to upstream hashcat v7.1.2:
len_max=5, rejecting t ≥ 1000.
Raised to 12 to cover the full parameter grid.--machine-readable output, derived from hashcat's
internal double. The existing integer H/s field truncates
sub-1 H/s rates to zero, losing all data for slow parameter
combinations where one hash takes seconds to minutes.--speed-only has a
hardcoded 4 s guard in choose_kernel() that reports
0 H/s when one hash exceeds it. Raised to 15 s
(15000 ms).
On all platforms, once a measured hash exceeds a wall-clock cutoff the
benchmark stops measuring further (more expensive) cells in that memory
column and extrapolates from prior data instead. These estimated rows
(stopped_reason = "estimated_timeout") are shown
with a diagonal-stripe overlay. Estimates are calculated as:
pass_1_cost = ns(lowest_t in column)
per_pass_cost = avg( (ns(t) - pass_1_cost) / (t - lowest_t) ) for all measured t > lowest_t
estimate(t_target) = pass_1_cost + (t_target - lowest_t) * per_pass_cost
...which takes into account the slower first pass. From testing, this estimate is more
than 99% accurate at the higher m difficulties. For lower m difficulties where timing noise
plays a larger role, we directly test each cell to get more accurate data. When both real
and estimated data exist for a cell, the real measurement is used.
Blank cells at high memory mean the device ran out of memory (OS OOM-kill on Android/iOS/Desktop; VRAM exhaustion on GPU).
OOM events are bucketed per memory column (memory cost drives peak allocation, not time cost). The marker counts devices in the current filter with any OOM in that column:
Columns with no OOM events get no marker, even if data is missing. If you're selecting Argon2id parameters for a mobile app, you probably want to stay a reasonable distance away from these yellow and red zones.
When multiple devices match the filters, each cell aggregates across all matching runs. The target value sets the color dividing line: ±50% of target renders blue, faster is green, slower is red (darker = further from target). Switching between H/s and ms converts the target and relabels the aggregation options automatically.
Early Android data is millisecond-precision only; newer Android builds and
GPU runs include a nanosecond-precision field. Integer-only cells
(e.g. 5) could be anywhere in [5, 6) ms; cells
with decimals have sub-ms precision. When both precisions exist for a cell
the ns value is used. Sub-ms cells with no ns data show as
<1.