Random 4 digit number generator

Uniform four-digit draws over the full 0000 to 9999 range, for PINs and short codes, with leading zeros kept.

G

13.29 bits, and why a bank PIN survives anyway

Four decimal digits give 10,000 values and 4 x log2(10) = 13.29 bits of entropy. By any modern standard that is nothing. NIST SP 800-63B requires that any secret with less than 64 bits of entropy be rate-limited, and 13.29 bits is a fifth of that floor. On our own strength scale a four-digit PIN sits in the bottom bucket, and we say so on the page rather than colouring it green.

A card PIN is nonetheless safe in practice, and the reason is worth being precise about. It is not the arithmetic. It is that the issuer allows roughly three attempts before the card is retained or the account is locked. Three guesses against 10,000 values is a 0.03% chance of success. Remove the attempt limit and the same PIN falls in milliseconds. The entropy is not doing the work; the counter is.

That is the rule to carry away. A four-digit PIN is acceptable only where something outside the PIN limits the number of guesses. Where nothing does, four digits is not a control at all.

Chance an attacker guesses a uniformly random 4-digit PIN
Attempts allowedSuccess probability
3, the usual card limit0.03%
100.10%
1001.00%
1,00010.00%
10,000Certain

Real PINs are nothing like uniform

The 0.03% figure assumes the PIN was chosen uniformly at random. Almost no human-chosen PIN is. Every published analysis of leaked PIN sets finds the same handful of values dominating: 1234 first by a wide margin, then 1111, 0000 and other single-digit runs, then keypad shapes like 2580. On top of that, any four-digit string beginning 19 is heavily over-represented, because people use their birth year.

The consequence is that an attacker with three guesses does not try 0000, 0001, 0002. They try the top three values from a frequency table, and their success rate is far above 0.03%. Choosing at random is what moves you out of that head and into the tail, and it is the only thing a generator can actually do for you here.

If you are setting a PIN you have to remember, resist the urge to adjust the generated value into something more memorable. Every adjustment moves it back toward the values an attacker tries first.

Leading zeros, and the generators that discard them

A PIN is a string of four digits, not a number between 1000 and 9999. 0042 is a valid PIN and one in ten valid PINs starts with a zero. A generator that returns an integer in 1000 to 9999 covers only 9,000 values, drops the entropy from 13.29 to log2(9000) = 13.14 bits, and hands an attacker a free fact: the first digit is never zero, which removes a tenth of the space before they start.

The same thing goes wrong downstream. If a system stores the PIN as an integer, 0042 becomes 42, and the comparison on the way back in either fails or, worse, succeeds for both 0042 and 42. Store it as a fixed-width string, and if it is a real credential, store a salted hash of it rather than the value.

What four digits should not be used for

Do not use a four-digit value as a password reset token, an API key, a session identifier or anything that can be attacked without a strict counter in front of it. Those need a value an attacker cannot enumerate at all, which means a longer draw from a larger alphabet.

Six digits is the standard for one-time codes and is still only 19.93 bits, safe for the same rate-limited reason. If you want a value that stands on its own, you need to leave the digit alphabet behind entirely.

Doing this in your own code

JavaScript
// 4 independent digits, uniform, leading zeros preserved.
// 250 is Math.floor(256 / 10) * 10. Bytes at or above it are thrown away,
// which is what keeps every digit at exactly 1 in 10.
function randomDigits(n) {
  const buf = new Uint8Array(1);
  let out = "";
  while (out.length < n) {
    crypto.getRandomValues(buf);
    if (buf[0] >= 250) continue;
    out += buf[0] % 10;
  }
  return out;
}

randomDigits(4);   // a 4-character string, never a number

The usual shortcut, Math.floor(Math.random() * 9e5) + 1e5, cannot produce a leading zero and draws from a PRNG whose state is recoverable from its output.

Python 3.6+
import secrets

# Draw the digits. This never becomes an int, so leading zeros survive.
"".join(secrets.choice("0123456789") for _ in range(4))

# Or draw one integer and pad it back to width.
f"{secrets.randbelow(10 ** 4):04d}"

random.randint() uses the Mersenne Twister and is not a security boundary. secrets draws from os.urandom.

Go
import (
    "crypto/rand"
    "fmt"
    "math/big"
)

limit := new(big.Int).Exp(big.NewInt(10), big.NewInt(4), nil)
v, err := rand.Int(rand.Reader, limit)
if err != nil {
    panic(err)
}

// The width verb is the part that keeps the leading zeros.
fmt.Printf("%04d\n", v)
Java
import java.security.SecureRandom;

SecureRandom rnd = new SecureRandom();
StringBuilder sb = new StringBuilder(4);
for (int i = 0; i < 4; i++) {
    sb.append(rnd.nextInt(10));
}
String value = sb.toString();

nextInt(bound) already rejects the unusable top of the range, so it is unbiased for a bound of 10.

PHP 7+
// random_int() is backed by the system CSPRNG.
// rand() and mt_rand() are not, and must not be used here.
$value = str_pad((string) random_int(0, 10 ** 4 - 1), 4, '0', STR_PAD_LEFT);
Shell
# 4 digits straight from the kernel CSPRNG.
# tr keeps only ASCII digits, so every byte that survives is uniform over 0-9.
LC_ALL=C tr -dc '0-9' < /dev/urandom | head -c 4; echo

Questions

Is a 4-digit PIN secure?

On its own, no. 13.29 bits of entropy is enumerable in under a second. It is secure only when something limits guesses, which is why bank cards allow about three attempts before locking. Applied to anything an attacker can hammer, a four-digit PIN provides no meaningful protection.

Can a PIN start with 0?

Yes. 0000 through 0999 are one tenth of the 10,000 valid PINs, and this generator produces them at their correct rate. Any tool that returns a number between 1000 and 9999 is silently excluding them and reducing the space to 9,000.

Why is 1234 a bad PIN if every PIN is equally likely?

Every PIN is equally likely to a generator, not to an attacker. Attackers order their guesses by observed frequency, and 1234 sits at the top of every leaked PIN frequency table ever published. Uniform mathematics describes the draw, not the population of PINs actually in use.

Can I use this for a one-time code sent by SMS?

Six digits is the convention for one-time codes, because RFC 4226 sets six as the minimum. Four will work if your service enforces a hard attempt limit and a short expiry, but you gain 6.64 bits for free by using six, so there is little reason not to.

Are the generated PINs unique across a batch?

Not unless you ask for that. Independent draws from a 10,000-value space repeat quickly: a batch of 118 has about a 50% chance of containing a duplicate. If you are issuing PINs that must be distinct, enforce uniqueness explicitly.

Related generators