fix(2fa): widen recovery code entropy to 80 bits (BUNYIP-202) #244

Merged
David merged 2 commits from fix/bunyip-202-recovery-code-entropy into main 2026-06-26 12:37:04 +02:00
Member

Recovery codes were drawn from only 4 random bytes (32 bits) and rendered
as an 8-hex-char XXXX-XXXX string. With 8 live codes per account and a 2FA
verify rate limit an attacker can sidestep by rotating source IPs, the
~2^32/8 search to hit any valid code was brute-forceable against the 2FA
challenge endpoint, bypassing the second factor.

Generate recovery codes from 10 CSPRNG bytes (80 bits) base32-encoded into
16 characters, grouped as XXXX-XXXX-XXXX-XXXX for readability. 80 bits
clears the OWASP >=64-bit bar for backup codes. Hyphens stay display-only:
generation and verify_recovery_code both normalise by stripping hyphens
and upper-casing before Argon2id hashing, so existing stored codes and the
verify path are unaffected by the format change.

The per-account failed-attempt lockout the audit also called for (key the
rate limit on user_id, not just source IP) already landed in BUNYIP-201, so
this change covers the remaining weak-entropy acceptance criterion.

#BUNYIP-202

Recovery codes were drawn from only 4 random bytes (32 bits) and rendered as an 8-hex-char XXXX-XXXX string. With 8 live codes per account and a 2FA verify rate limit an attacker can sidestep by rotating source IPs, the ~2^32/8 search to hit any valid code was brute-forceable against the 2FA challenge endpoint, bypassing the second factor. Generate recovery codes from 10 CSPRNG bytes (80 bits) base32-encoded into 16 characters, grouped as XXXX-XXXX-XXXX-XXXX for readability. 80 bits clears the OWASP >=64-bit bar for backup codes. Hyphens stay display-only: generation and verify_recovery_code both normalise by stripping hyphens and upper-casing before Argon2id hashing, so existing stored codes and the verify path are unaffected by the format change. The per-account failed-attempt lockout the audit also called for (key the rate limit on user_id, not just source IP) already landed in BUNYIP-201, so this change covers the remaining weak-entropy acceptance criterion. #BUNYIP-202
fix(2fa): widen recovery code entropy to 80 bits (BUNYIP-202)
Some checks failed
E2E / Playwright against deployment (pull_request) Successful in 1m10s
Check / fmt + clippy + build + tests (pull_request) Failing after 38m42s
9a82d78eb2
Recovery codes were drawn from only 4 random bytes (32 bits) and rendered
as an 8-hex-char XXXX-XXXX string. With 8 live codes per account and a 2FA
verify rate limit an attacker can sidestep by rotating source IPs, the
~2^32/8 search to hit any valid code was brute-forceable against the 2FA
challenge endpoint, bypassing the second factor.

Generate recovery codes from 10 CSPRNG bytes (80 bits) base32-encoded into
16 characters, grouped as XXXX-XXXX-XXXX-XXXX for readability. 80 bits
clears the OWASP >=64-bit bar for backup codes. Hyphens stay display-only:
generation and verify_recovery_code both normalise by stripping hyphens
and upper-casing before Argon2id hashing, so existing stored codes and the
verify path are unaffected by the format change.

The per-account failed-attempt lockout the audit also called for (key the
rate limit on user_id, not just source IP) already landed in BUNYIP-201, so
this change covers the remaining weak-entropy acceptance criterion.

#BUNYIP-202
Merge branch 'main' into fix/bunyip-202-recovery-code-entropy
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 21s
Check / fmt + clippy + build + tests (pull_request) Successful in 21m30s
Create release / Create release from merged PR (pull_request) Has been skipped
8e001eadc9
David merged commit 9c0ac500bf into main 2026-06-26 12:37:04 +02:00
David deleted branch fix/bunyip-202-recovery-code-entropy 2026-06-26 12:37:04 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/bunyip!244
No description provided.