Safeguard
Application Security•

ASVS 5.0 V6.5.3: Generate MFA Secrets With a CSPRNG

Recovery codes, OOB codes and TOTP seeds must come from a cryptographically secure random generator. Examples in four languages and how to test. Level 2.

Safeguard Security Team
3 min read

ASVS 5.0 V6.5.3 requires that lookup secrets, out of band authentication codes and TOTP seeds are generated with a cryptographically secure pseudorandom number generator, so the values cannot be predicted. This is a Level 2 requirement.

Why it matters

General purpose random functions are built for speed and spread, not secrecy. Their output can be reconstructed from a few observed values. If an attacker sees two or three of your six digit codes, a predictable generator lets them compute the next one for another user. A TOTP seed made from a weak source can be guessed outright, which defeats the factor before it is ever used.

How to implement it

Use the operating system backed generator provided by your language, never the default one.

# Python
import secrets
code = f"{secrets.randbelow(10**6):06d}"
seed = secrets.token_bytes(20)        # 160 bit TOTP seed
// Node.js
import { randomInt, randomBytes } from "node:crypto";
const code = String(randomInt(0, 1_000_000)).padStart(6, "0");
const seed = randomBytes(20);
// Java
SecureRandom rng = new SecureRandom();
String code = String.format("%06d", rng.nextInt(1_000_000));
byte[] seed = new byte[20];
rng.nextBytes(seed);
// Go
n, _ := rand.Int(rand.Reader, big.NewInt(1000000)) // crypto/rand
code := fmt.Sprintf("%06d", n.Int64())

Avoid modulo on random bytes, which biases results. Use the range helpers shown above. Seed a TOTP with at least 128 bits, and 160 bits matches the common authenticator apps.

How to test it

  1. Grep the code that creates codes and seeds for Math.random, random.random, java.util.Random, rand() and math/rand.
  2. Run a static scan that flags insecure random use in security contexts.
  3. Generate a large sample and confirm no repeats and a flat digit distribution. This will not prove strength but will catch obvious mistakes.

Common mistakes

  • Seeding a normal generator with the current time.
  • Deriving codes from user ID, timestamps or counters.
  • Using a UUID version 1 or a sequential ID as a token.

Try it yourself

Static analysis finds weak random sources in code. Run a scan and see what it reports.

Click through it at your own pace, or open it full screen.

Quick check

Is Python's random.randint acceptable for a six digit code?

No. It is not cryptographically secure. Use the secrets module.

Why not use modulo on a random byte?

When 256 is not divisible by the range, some outputs occur more often, which gives an attacker better odds.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.