Random 12 digit number generator

Uniform twelve-digit draws over all one trillion values, for longer reference and tracking numbers.

G

A trillion values, 39.86 bits, and collisions anyway

Twelve digits give 1,000,000,000,000 values and 12 x log2(10) = 39.86 bits. This is the length at which a random reference stops feeling cramped: it is long enough that an attacker cannot enumerate it casually and long enough that a moderately busy system will not trip over duplicates in its first year.

It is not, however, collision-free. The chance of at least one duplicate reaches 1% at 141,777 references and 50% at 1,177,410. Issue a million random twelve-digit tracking numbers and it is a coin flip whether two of them match. Every system that issues identifiers at this scale still needs a unique index and a retry, because the alternative is discovering the duplicate in a customer support ticket.

As a secret, 39.86 bits is weak. It sits below the 64-bit floor NIST sets for a secret that stands without rate limiting, so a twelve-digit value is an identifier, not a credential, no matter how long it looks.

Twelve digits is barcode-shaped, and barcodes have check digits

UPC-A, the barcode on almost every retail product in North America, is twelve digits: eleven digits of data and one check digit computed from them with a weighted mod-10 sum. EAN-13 is the same idea at thirteen digits.

That has a direct consequence for random draws. Because the twelfth digit is fully determined by the first eleven, exactly one of the ten possible last digits is correct, so a uniformly random twelve-digit string is a valid UPC-A about one time in ten. Nine times in ten a scanner or a validating importer will reject it. If you need barcode fixtures that scan, compute the check digit rather than drawing it, and use a prefix reserved for internal or restricted circulation rather than one belonging to a real manufacturer.

The same reasoning applies to sixteen-digit payment card numbers, where the checksum is Luhn. Whenever the last digit of a format is derived from the others, a uniform draw over the full width will fail validation nine times out of ten.

This tool draws twelve independent digits. It does not compute a check digit, and the values it produces are not valid UPC or EAN codes except by coincidence.

The last comfortable size before precision problems

A twelve-digit value is under 10^12, comfortably inside the range a 64-bit integer holds and inside the range a JavaScript number represents exactly, which stops at 9,007,199,254,740,991. So twelve digits round-trips through JSON, through JavaScript and through most naive tooling without losing anything.

That stops being true at sixteen digits, where about 11% of the range cannot be represented exactly as a double. Twelve digits is the last size where treating the value as a number is merely bad practice rather than actively lossy. It is still bad practice, for the same reason as at every other length: a number has no fixed width, and a leading zero disappears the moment the value is parsed.

Fixed width, and reading it aloud

One twelve-digit value in ten begins with a zero. Store it as CHAR(12) and the width is guaranteed everywhere, which matters more here than at short lengths because twelve-digit references are typically printed, scanned or read over a phone, and all three of those assume a constant number of characters.

For human handling, group the output. Four groups of three or three groups of four are both easier to read back correctly than an undifferentiated run of twelve characters, and the grouping is presentation only: it must be stripped before comparison, never stored.

Doing this in your own code

JavaScript
// 12 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(12);   // a 12-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(12))

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

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(12), 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("%012d\n", v)
Java
import java.security.SecureRandom;

SecureRandom rnd = new SecureRandom();
StringBuilder sb = new StringBuilder(12);
for (int i = 0; i < 12; 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 ** 12 - 1), 12, '0', STR_PAD_LEFT);
Shell
# 12 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 12; echo

Questions

How many 12-digit references before a duplicate?

The chance of at least one duplicate is 1% at 141,777 references and 50% at 1,177,410. A trillion values sounds like plenty, but collisions arrive near the square root of the space, so a unique index and a retry are still required.

Are these valid UPC barcode numbers?

Only about one in ten. UPC-A is eleven data digits plus a check digit computed from them, so nine of the ten possible last digits are wrong for any given prefix. For fixtures that must scan, compute the check digit and use a prefix reserved for internal use.

Is a 12-digit number secure enough for a tracking link?

It is 39.86 bits, below the 64-bit level NIST treats as the floor for a secret that stands without rate limiting. If the link exposes personal data, put authentication in front of it. If it only exposes a delivery status, twelve digits with rate limiting is a defensible trade-off.

Can I store it as a number?

It will fit, since 10^12 is well inside both a 64-bit integer and the exact range of a JavaScript number. It is still the wrong choice, because a number has no fixed width and any leading zero is lost on the first parse. Use CHAR(12).

How is this different from a 12-character random string?

Alphabet size. Twelve digits is 39.86 bits over an alphabet of 10. Twelve alphanumeric characters is 71.45 bits over an alphabet of 62. If the value only needs to be typed on a numeric keypad, digits are right; otherwise a larger alphabet buys far more per character.

Related generators