Recovery codes… Or just one recovery code?
Source: Dev.to
Overview
Authentication discussions usually focus on:
- Passkeys
- TOTP
- Hardware tokens
- Passwordless
Recovery mechanisms are rarely analysed with the same rigour, yet recovery often defines the actual security boundary. If login is strong but recovery is weak, the system is weak.
Backup Code Lists (5–10 one‑time codes)
Properties
- Multiple static tokens
- One‑time use
- Regenerable list
Typical Issues
- Users do not store them properly
- Screenshots are taken
- Codes are forgotten
- No entropy analysis is done
- Security is assumed because there are “many codes”
Security Considerations
- Entropy is what actually matters.
- Recovery becomes delegated authentication.
- Security depends on:
- Email account protection
- SIM security
- External attack surface
This shifts the threat model rather than strengthening it.
Seed Phrases (12–24 words)
Characteristics
- Recovery of private key
- High entropy
- Offline storage expectation
Usability
- Cryptographically rigorous, but has high usability cost.
Assumptions
- Attacker has recovery endpoint access
- No data leak
- No internal compromise
- Attack is purely online
Security Depends On
- Entropy
- Generation quality
- Rate limiting
Not on the number of codes issued.
Tested Configuration
- Length: 15 characters
- Alphabet size: 31 symbols (ambiguous characters such as
0/Oand1/I/Lexcluded) - Generation: CSPRNG (
crypto.randomBytes) with rejection sampling (no modulo bias) - TTL: No fixed TTL
- Rate limiting: Strict; a few attempts within several minutes
- Rotation: Automatic after successful use
Entropy Calculation
- Alphabet size: 31
- Total search space: (31^{15})
- Entropy: ≈ 78 bits
Aggressive Attack Scenario
- Attempts: ~1.5 million per year
- Probability of success: Negligible (effectively infeasible for an online attack)
Issuing 10 codes does not multiply the entropy of a single attempt; an attacker still guesses one valid code at a time.
Security Depends On
- Per‑account rate limiting
- Per‑identifier throttling
- Entropy per code
- Correct random generation
- Safe storage model
Not on how many printable tokens are handed to the user.
Randomness
// Use a cryptographically secure PRNG
const crypto = require('crypto');
function generateCode(length, alphabet) {
const bytes = crypto.randomBytes(length * 2); // oversample
let result = '';
let i = 0;
while (result.length < length && i < bytes.length) {
const idx = bytes[i] % alphabet.length;
// Rejection sampling to avoid modulo bias
if (bytes[i] < 256 - (256 % alphabet.length)) {
result += alphabet[idx];
}
i++;
}
return result;
}
- Avoid
Math.random(). - Use rejection sampling when mapping bytes to the alphabet to eliminate modulo bias.
Rate Limiting
Critical Points
- Limit per recovery identifier (not only per IP).
- Combine per‑account and per‑IP limits.
- Add exponential backoff if necessary.
Rotation
After a successful recovery:
- Invalidate the previous code.
- Generate a new one.
- Avoid long‑term static tokens.
Conclusion
Recovery is not a UX afterthought; it is an authentication primitive. If recovery entropy and attempt control are properly engineered, a single recovery code can be strictly sufficient under an online threat model.
The real question is not “How many codes?” but “What is the entropy and how is the attack surface controlled?”