Argon2id Tuning in Practice: Why 64 MB × 3 Iterations Was the Right Call
How we picked Argon2id parameters for ByteGuard — benchmarks on real iPhones, the OWASP recommendation, the UX vs security trade-off, and what 1Password and Bitwarden chose.
Published: 2026-05-05 · 9 min read
Why KDF Tuning Matters at All
Most password managers reduce a user's secret to a fixed-length encryption key using a key derivation function (KDF). The KDF is what stands between a stolen password vault file and the attacker turning it into your plaintext logins.
If the KDF is weak, the entire crypto stack — AES-256-GCM included — collapses. Doesn't matter how strong AES is if Argon2id only takes 0.1 ms per attempt: an offline attacker rents an A100 GPU for an hour and tries 10 billion candidate passwords against your vault.
If the KDF is too strong, your phone takes 5 seconds to unlock the app every time you tap Face ID. You stop using the app.
The whole game is finding the largest cost an attacker has to pay per guess while keeping legitimate unlocks under a UX threshold.
"Cost per guess" is asymmetric. Your phone runs the KDF once per unlock — annoyance is bounded by single-shot latency. An offline attacker runs it billions of times — annoyance is bounded by total compute cost. Inflating cost per guess by 10× hurts the attacker 10× more than the user.
Why Argon2id (Not PBKDF2 or scrypt)
Argon2id won the Password Hashing Competition in 2015 and has been the recommended KDF for new systems ever since. It has three properties that PBKDF2 lacks:
- Memory-hardness. Argon2id requires N megabytes of RAM per concurrent guess. PBKDF2 needs almost no memory, so an attacker can run thousands of parallel guesses on a single GPU. Memory-hardness wipes that out — each guess needs its own dedicated RAM block.
- Side-channel resistance (the "id" mode is a hybrid: side-channel resistant on first pass, time-memory trade-off resistant on subsequent passes).
- Parameterized work factor. Memory cost (m), iteration count (t), parallelism (p) — all tunable independently. PBKDF2 only has iteration count.
The cost difference at attack scale is roughly 1000×. A modern GPU rig can try ~10⁹ PBKDF2-SHA256 / sec but only ~10⁶ Argon2id (64 MB) / sec. That's the difference between "8-character password cracked overnight" and "8-character password takes a thousand nights".
What "Memory-Hard" Actually Means
Concretely: Argon2id allocates a 64 MB block of memory and fills it with derived bytes that depend on every previous byte. To compute the final hash, the algorithm walks the block in a pattern that defeats any "compute on the fly without storing" optimization. The attacker can't trade space for time.
Compare GPU economics:
- PBKDF2-SHA256: needs ~32 bytes of state per guess. An A100 has 40 GB of HBM2 → can run ~1.25 billion parallel guesses.
- Argon2id (64 MB): needs 64 MB of state per guess. Same A100 → can run 625 parallel guesses.
Same hardware, 2 million× fewer parallel slots. That's the structural moat memory-hard KDFs build.
The OWASP Recommendation (and Why We Followed It)
The OWASP Password Storage Cheat Sheet recommends, for mobile / interactive contexts:
This is what ByteGuard ships. Not because we wanted to be conservative — because we ran the numbers and OWASP's choice landed in the sweet spot for iOS.
Why Not 128 MB × 2 Iter, or 32 MB × 4?
The total work is roughly m × t. Both 128×2 (256 MB·iter) and 64×3 (192 MB·iter) are stronger than 32×4 (128 MB·iter). On paper, you might pick 128×2 for maximum cost. But three things push the balance back to 64×3:
- Memory pressure on iOS. iOS aggressively kills background apps that hog memory. A 128 MB transient allocation during unlock can trigger memory warnings on iPhone SE / iPhone 12 mini (3-4 GB total RAM, much less available). 64 MB is a clean fit.
- UX latency. Argon2id memory access dominates wall-clock time more than iteration count. 128 MB × 2 takes longer than 64 MB × 3 on the same device, even though the work product is similar.
- Future-proofing direction. Memory-hardness scales with attacker GPU memory. As GPUs add more RAM, you raise m, not t. Starting at 64 MB leaves headroom for 128 MB / 256 MB upgrades later without UX surgery.
Real iPhone Benchmarks
We measured Argon2id 64 MB × 3 iter (libsodium crypto_pwhash_str, OPSLIMIT_INTERACTIVE) on the devices we test against:
| Device | Year | SoC | Unlock latency (avg over 100 runs) |
|---|---|---|---|
| iPhone 15 Pro | 2023 | A17 Pro | 180 ms |
| iPhone 14 | 2022 | A15 | 240 ms |
| iPhone 13 mini | 2021 | A15 | 250 ms |
| iPhone 12 | 2020 | A14 | 290 ms |
| iPhone SE (3rd gen) | 2022 | A15 | 250 ms |
| iPhone 11 | 2019 | A13 | 340 ms |
| iPhone XS | 2018 | A12 | 410 ms |
The UX research literature places "fast feedback" under 100 ms and "no perceptible delay" under 200 ms. We sit at 180–290 ms on currently supported devices — a small but acceptable beat after Face ID. Above 300 ms users start to notice. iPhone XS sits at 410 ms but Face ID + Argon2id together still complete under 700 ms wall-clock, which is well within Apple's own Touch ID / Face ID + Keychain unlock budget.
"Avg over 100 runs" matters because the first invocation after a cold start is 50–100 ms slower (page-fault on the 64 MB allocation). We pre-warm the allocator on app launch so the first user-perceptible unlock is the warm path.
What 1Password and Bitwarden Chose
| Product | KDF | Parameters | Notes |
|---|---|---|---|
| ByteGuard | Argon2id | 64 MB × 3 iter × 1 thread | Per OWASP mobile recommendation. Fixed. |
| 1Password | PBKDF2-SHA256 | 650,000 iterations | Compensated by their 128-bit Secret Key serving as a second factor. |
| Bitwarden | PBKDF2-SHA256 or Argon2id | PBKDF2: 600,000 / Argon2id: 64 MB × 3 | User-selectable since 2023. Argon2id is opt-in, default is still PBKDF2. |
| Apple Keychain | (internal) | (unpublished) | Apple Platform Security Guide does not document the iCloud Keychain KDF parameters. |
1Password's choice is defensible because their Secret Key adds 128 bits of entropy that never leaves the device. An attacker who steals the vault but not the Secret Key faces 2¹²⁸ candidate keys regardless of password strength. ByteGuard adopts the same architectural pattern — our Secret Key is a 12-word BIP39 mnemonic — so we get the Secret Key benefit and Argon2id memory-hardness on top.
When (and How) We Would Re-Tune
Two triggers force re-tuning:
- Hardware moves. When the median supported iPhone is 2 generations newer (i.e. 2027 baseline = iPhone 13 / A15 minimum), we'll consider raising memory to 96 MB or 128 MB. The CPU cost of Argon2id has been roughly constant relative to per-core performance — memory bandwidth is what changes.
- GPU memory grows. If consumer GPUs jump to 128+ GB HBM (currently A100 is 40 GB / 80 GB, H100 is 80 GB), parallel attack throughput on 64 MB Argon2id improves. We'd raise m proportionally.
The protocol has versioning baked in: every Argon2id hash carries its parameters in the encoded string ($argon2id$v=19$m=65536,t=3,p=1$...). Old vaults rehash on next unlock when params change, so migration is incremental and silent.
Key Takeaways
- KDF strength is the foundation of every other crypto guarantee. Picking it casually is a bug.
- Argon2id beats PBKDF2 by ~1000× at attack scale because of memory-hardness, not iteration count.
- OWASP's 64 MB × 3 iter × 1 thread is a pragmatic sweet spot for mobile — not the strongest possible, but the strongest that runs in <300 ms on iPhone 13 / A15.
- iOS memory pressure is real. 128 MB allocations during unlock risk OS termination on lower-end devices.
- Don't trust "we use Argon2id" as a marketing claim. Ask for parameters: m, t, p. Without them the statement is meaningless.
ByteGuard's full key hierarchy and iOS Keychain integration are documented in the Security Whitepaper.