Last Updated: 3/9/2026
Security
Nano ID is designed for cryptographic security. This guide explains its security properties and best practices.
Overview
Nano ID provides three core security guarantees:
- Unpredictability: Cannot predict future IDs from past IDs
- Uniformity: All characters have equal probability
- Collision resistance: Extremely low probability of duplicates
Unpredictability
Hardware Random Generation
Nano ID uses cryptographically secure random number generators (CSPRNG):
Node.js:
import { webcrypto as crypto } from 'node:crypto'
crypto.getRandomValues(buffer)
// Uses OS-level CSPRNG (e.g., /dev/urandom on Linux)Browsers:
crypto.getRandomValues(new Uint8Array(21))
// Uses Web Crypto API (hardware-backed when available)Why Not Math.random()?
Math.random() is not cryptographically secure:
// INSECURE: Predictable
function insecureId() {
return Math.random().toString(36).slice(2)
}Problems:
- ❌ Predictable: Seeded by time, can be guessed
- ❌ Low entropy: Only ~52 bits of randomness
- ❌ Reproducible: Same seed = same sequence
Nano ID is secure:
- ✅ Unpredictable: Uses hardware entropy
- ✅ High entropy: 126 bits for 21-char IDs
- ✅ Non-reproducible: Cannot recreate sequence
Entropy Sources
Node.js uses OS-level entropy:
- Linux:
/dev/urandom(ChaCha20-based) - macOS:
SecRandomCopyBytes(hardware RNG) - Windows:
BCryptGenRandom(CNG)
Browsers use:
- Hardware RNG: Intel RDRAND, ARM TrustZone
- OS entropy: Falls back to OS CSPRNG
- Entropy pool: Mixed with user interactions, timings
Uniformity
The Problem with Modulo
A common mistake creates biased distribution:
// WRONG: Non-uniform distribution
function biasedId(alphabet) {
let id = ''
for (let i = 0; i < 21; i++) {
const byte = crypto.getRandomValues(new Uint8Array(1))[0]
id += alphabet[byte % alphabet.length] // ❌ BIASED
}
return id
}Why it’s biased (alphabet size 30):
Random bytes: 0-255 (256 values)
256 % 30 = 16 remainder
Characters 0-15: appear 9 times (0, 30, 60, ..., 240)
Characters 16-29: appear 8 times (16, 46, 76, ..., 226)
Bias: First 16 chars are 12.5% more likely!Nano ID’s Solution: Rejection Sampling
Nano ID uses bitmask + rejection for perfect uniformity:
// Find smallest power of 2 >= alphabet size
const mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1
// Map byte to alphabet, reject if out of range
const char = alphabet[randomByte & mask] || ''
if (char) id += char // Only accept valid charsResult: Every character has exactly 1/alphabet.length probability.
Collision Resistance
Birthday Paradox
Collision probability follows the birthday problem :
P(collision) ≈ (n² / 2) × (1 / alphabet^size)
For default Nano ID (21 chars, 64-char alphabet):
P(collision) ≈ (n² / 2) / (64^21)
≈ n² / (2 × 2^126)Collision Probability Table
| IDs Generated | Collision Probability | Time at 1M IDs/sec |
|---|---|---|
| 1,000 | ~0.000000000000000000000000000000001% | 1 second |
| 1,000,000 | ~0.0000000000000000000000001% | 16 minutes |
| 1,000,000,000 | ~0.00000000000000001% | 11 days |
| 103,000,000,000,000 | ~0.0000001% (1 in billion) | 3,268 years |
Practical interpretation:
To have a 1% chance of collision, you’d need to generate ~2.8 × 10^18 IDs (2.8 quintillion).
Size vs Security
| Size | Bits | IDs for 1% collision | Use Case |
|---|---|---|---|
| 8 | 48 | ~16 million | Short codes (check calculator) |
| 10 | 60 | ~1 billion | Medium security |
| 21 | 126 | ~2.8 × 10^18 | Default (UUID equivalent) |
| 32 | 192 | ~1.9 × 10^28 | High security tokens |
Always use the collision calculator to verify safety for your use case.
Attack Vectors
Timing Attacks
Not vulnerable: Nano ID’s random generation time is constant (doesn’t leak information).
// Time to generate is independent of ID value
const id1 = nanoid() // ~2700 ns
const id2 = nanoid() // ~2700 nsBrute Force
Infeasible: For 21-character IDs:
Search space: 64^21 = 2^126 = 85 undecillion
At 1 trillion guesses/second:
Time to brute force 50% = 2^125 / 10^12 seconds
= 1.3 × 10^18 years
= 100 million × age of universePrediction Attacks
Not vulnerable: CSPRNG output cannot be predicted even with:
- Past IDs
- Partial ID knowledge
- Timing information
Side-Channel Attacks
Partially vulnerable: Like all software, vulnerable to:
- Cache timing: Theoretical (requires local access)
- Power analysis: Requires hardware access
- Spectre/Meltdown: OS/hardware level
Mitigation: Use OS-level CSPRNG (already done).
Best Practices
✅ Do’s
Use default size for security-critical IDs:
const sessionId = nanoid() // 21 chars (126 bits)
const apiToken = nanoid(32) // 32 chars (192 bits)Use secure variant for tokens:
import { nanoid } from 'nanoid' // ✅ Secure
const authToken = nanoid()Check collision probability:
// For 1 billion IDs at size 10:
// Use https://zelark.github.io/nano-id-cc/
// Result: ~0.1% collision probability❌ Don’ts
Don’t use non-secure for sensitive data:
import { nanoid } from 'nanoid/non-secure' // ❌ Not for production
const sessionId = nanoid() // ❌ Predictable!Don’t reduce size without checking:
const id = nanoid(6) // ❌ High collision risk!
// For 1 million IDs: ~1.5% collision probabilityDon’t use custom random generators unless necessary:
import { customRandom } from 'nanoid'
const nanoid = customRandom(alphabet, 21, () => {
return Array(21).fill(0).map(() => Math.random() * 256) // ❌ INSECURE
})Vulnerability Reporting
To report security vulnerabilities:
- Do NOT open a public GitHub issue
- Use Tidelift security contact
- Tidelift will coordinate fix and disclosure
Compliance
FIPS 140-2
Nano ID uses OS-level CSPRNGs which are typically FIPS 140-2 compliant:
- Node.js: Depends on OpenSSL FIPS module
- Browsers: Depends on OS crypto implementation
Check your environment:
# Node.js
node -p "crypto.getFips()" # Should return 1 if FIPS enabledGDPR / Privacy
Nano IDs are not personally identifiable:
- Random generation (no user data)
- Cannot be reversed to user information
- Safe to log and store
However, IDs can become PII through association:
// The ID itself is not PII
const userId = nanoid()
// But this association makes it PII under GDPR
db.users.insert({ id: userId, email: 'user@example.com' })Further Reading
- Secure random values (in Node.js)
- Nano ID source code (well-documented)
- NIST SP 800-90A - CSPRNG recommendations
What’s Next?
- Performance - Benchmarks and optimization
- Collision Probability - Mathematical deep dive
- Custom Random Bytes Generator - Advanced customization