Python’s secrets module generates cryptographically secure random values backed by the OS CSPRNG (os.urandom), suitable for tokens, API keys, and passwords. import secrets. Bytes: secrets.token_bytes(nbytes=32) → raw bytes. Hex: secrets.token_hex(nbytes=32) → str of 2*nbytes hex digits. URL-safe: secrets.token_urlsafe(nbytes=32) → URL-safe base64 (no +//) of approximately nbytes * 4/3 characters. Random selection: secrets.choice(seq) → one element; secrets.randbelow(n) → int in [0, n); secrets.randbits(k) → int with k random bits. Class: secrets.SystemRandom() — a random.Random subclass wired to the OS CSPRNG; useful when you need shuffle, sample, or uniform from existing random-API code. Size guidance: 32 bytes (256 bits) for session tokens, API keys, and CSRF tokens; 16 bytes (128 bits) minimum viable; 6 bytes for short-lived OTP; always at least ceil(desired_bits / 8) bytes. Comparison: use hmac.compare_digest(a, b) for timing-safe equality — do not use ==. Claude Code generates API key generators, session token systems, password reset flows, OTP generators, and secure nonce producers.
CLAUDE.md for secrets
## secrets Stack
- Stdlib: import secrets
- Token: secrets.token_bytes(32) # 32 raw bytes
- secrets.token_hex(32) # 64-char hex string
- secrets.token_urlsafe(32) # URL-safe base64, ~43 chars
- Pick: secrets.choice(alphabet) # single secure random element
- Int: secrets.randbelow(1_000_000) # [0, n)
- Note: Never use random module for security-sensitive values
- Always use hmac.compare_digest() to compare tokens
secrets Cryptographic Token Pipeline
# app/secretsutil.py — tokens, API keys, passwords, OTP, CSRF, nonces
from __future__ import annotations
import hashlib
import hmac
import math
import secrets
import string
import time
from dataclasses import dataclass
# ─────────────────────────────────────────────────────────────────────────────
# 1. Token generators
# ─────────────────────────────────────────────────────────────────────────────
def token(nbytes: int = 32) -> str:
"""
Generate a URL-safe cryptographic token of nbytes entropy.
Safe for session IDs, password reset links, and CSRF tokens.
Example:
tok = token() # 43-character URL-safe token
tok = token(16) # ~22 chars (128-bit security)
"""
return secrets.token_urlsafe(nbytes)
def token_hex(nbytes: int = 32) -> str:
"""
Generate a hex-encoded cryptographic token.
Example:
key = token_hex(32) # 64 hex chars, 256-bit security
"""
return secrets.token_hex(nbytes)
def api_key(prefix: str = "", nbytes: int = 32) -> str:
"""
Generate a prefixed API key (e.g., "sk_live_abc123...").
Example:
key = api_key("sk_live") # "sk_live_<64hex>"
key = api_key("tok") # "tok_<64hex>"
"""
raw = secrets.token_hex(nbytes)
return f"{prefix}_{raw}" if prefix else raw
# ─────────────────────────────────────────────────────────────────────────────
# 2. Password and passphrase generators
# ─────────────────────────────────────────────────────────────────────────────
_LOWER = string.ascii_lowercase
_UPPER = string.ascii_uppercase
_DIGITS = string.digits
_PUNCT = "!@#$%^&*()-_=+[]{}|;:,.<>?"
_SAFE_PUNCT = "!@#$%^&*-_+."
def random_password(
length: int = 16,
use_lower: bool = True,
use_upper: bool = True,
use_digits: bool = True,
use_punct: bool = True,
) -> str:
"""
Generate a cryptographically secure random password.
Guarantees at least one character from each enabled class.
Example:
pw = random_password(20)
pw = random_password(12, use_punct=False)
"""
alphabet = ""
required = []
if use_lower:
alphabet += _LOWER
required.append(secrets.choice(_LOWER))
if use_upper:
alphabet += _UPPER
required.append(secrets.choice(_UPPER))
if use_digits:
alphabet += _DIGITS
required.append(secrets.choice(_DIGITS))
if use_punct:
alphabet += _SAFE_PUNCT
required.append(secrets.choice(_SAFE_PUNCT))
if not alphabet:
raise ValueError("At least one character class must be enabled")
extra = [secrets.choice(alphabet) for _ in range(length - len(required))]
chars = required + extra
# Shuffle to avoid required chars always appearing first
for i in range(len(chars) - 1, 0, -1):
j = secrets.randbelow(i + 1)
chars[i], chars[j] = chars[j], chars[i]
return "".join(chars)
def passphrase(
word_count: int = 4,
wordlist: list[str] | None = None,
separator: str = "-",
) -> str:
"""
Generate a diceware-style passphrase from a wordlist.
If wordlist is None, uses a built-in 100-word mini-list.
Example:
phrase = passphrase(5)
# "coral-mighty-lamp-stone-river"
"""
if wordlist is None:
wordlist = [
"alpha","brave","cloud","delta","eagle","flame","grace","house",
"index","joker","kings","laser","micro","noble","ocean","prism",
"quest","rover","stone","trust","ultra","valor","whale","xenon",
"yield","zebra","amber","bloom","coral","drift","ember","frost",
"gloom","haven","ivory","jewel","karma","lemon","maple","nexus",
"orbit","plaza","quart","racer","solar","trail","umbra","vivid",
"warmth","exact","youth","azure","blaze","craft","dusty","epoch",
"field","grove","haste","ilk","jumpy","knave","lingo","marble",
"north","owl","paint","quota","realm","slope","torch","under",
"vault","wrist","oxen","yacht","zeal","ample","boxer","clamp",
"dingo","envy","flare","groan","horse","ingot","jolly","kite",
"lyric","might","nerve","optic","pivot","quirk","rivet","skate",
"tutor","unbox","visor","wager","xerox","yolk","zippy",
]
return separator.join(secrets.choice(wordlist) for _ in range(word_count))
# ─────────────────────────────────────────────────────────────────────────────
# 3. OTP and CSRF tokens
# ─────────────────────────────────────────────────────────────────────────────
def numeric_otp(digits: int = 6) -> str:
"""
Generate a cryptographically secure numeric OTP.
Example:
code = numeric_otp(6) # "827461"
"""
upper = 10 ** digits
return str(secrets.randbelow(upper)).zfill(digits)
def alphanumeric_otp(length: int = 8) -> str:
"""
Generate an alphanumeric OTP (uppercase + digits, no ambiguous chars).
Example:
code = alphanumeric_otp(8) # "A3K9MX2T"
"""
alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" # no 0/O, 1/I
return "".join(secrets.choice(alphabet) for _ in range(length))
@dataclass
class CsrfToken:
value: str
created: float
def is_valid(self, submitted: str, max_age: float = 3600) -> bool:
"""Verify CSRF token matches and has not expired."""
if time.time() - self.created > max_age:
return False
return hmac.compare_digest(self.value, submitted)
def __str__(self) -> str:
return self.value
def new_csrf_token() -> CsrfToken:
"""
Generate a new CSRF token for embedding in forms.
Example:
csrf = new_csrf_token()
# In form: <input type="hidden" name="csrf" value="{csrf}">
# On submit: if not csrf.is_valid(request.form["csrf"]): abort(403)
"""
return CsrfToken(value=secrets.token_urlsafe(32), created=time.time())
# ─────────────────────────────────────────────────────────────────────────────
# 4. Secure nonce and key derivation helpers
# ─────────────────────────────────────────────────────────────────────────────
def nonce(nbytes: int = 12) -> bytes:
"""
Generate a cryptographic nonce (number used once) as raw bytes.
12 bytes is standard for AES-GCM; 24 bytes for XSalsa20.
Example:
iv = nonce(12) # for AES-GCM
"""
return secrets.token_bytes(nbytes)
def entropy_bits(token_str: str, encoding: str = "urlsafe_b64") -> float:
"""
Estimate the entropy of a token string in bits.
Example:
tok = secrets.token_urlsafe(32)
print(entropy_bits(tok)) # ~256.0
"""
if encoding == "urlsafe_b64":
# URL-safe base64: 6 bits per char
return len(token_str) * 6
elif encoding == "hex":
return len(token_str) * 4
elif encoding == "raw":
return len(token_str) * 8
return 0.0
def secure_compare(a: str | bytes, b: str | bytes) -> bool:
"""
Constant-time comparison of two strings or byte strings.
Wraps hmac.compare_digest with input normalization.
Example:
if not secure_compare(token_from_db, token_from_request):
raise PermissionError("Invalid token")
"""
if isinstance(a, str) and isinstance(b, str):
return hmac.compare_digest(a, b)
if isinstance(a, bytes) and isinstance(b, bytes):
return hmac.compare_digest(a, b)
# Length-equalise type to prevent short-circuit
a_bytes = a.encode() if isinstance(a, str) else a
b_bytes = b.encode() if isinstance(b, str) else b
return hmac.compare_digest(a_bytes, b_bytes)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== secrets demo ===")
# ── tokens ────────────────────────────────────────────────────────────────
print("\n--- tokens ---")
print(f" token(32): {token(32)}")
print(f" token_hex(16): {token_hex(16)}")
print(f" api_key('sk_live'):{api_key('sk_live')[:40]}...")
# ── passwords ─────────────────────────────────────────────────────────────
print("\n--- passwords ---")
for _ in range(3):
pw = random_password(16)
bits = entropy_bits(pw, "raw")
print(f" {pw!r} (≥{bits:.0f} bits)")
print("\n--- passphrase ---")
for _ in range(3):
print(f" {passphrase(4)}")
# ── OTP ───────────────────────────────────────────────────────────────────
print("\n--- OTP ---")
print(f" numeric_otp(6): {numeric_otp(6)}")
print(f" alphanumeric_otp(8): {alphanumeric_otp(8)}")
# ── CSRF ──────────────────────────────────────────────────────────────────
print("\n--- CSRF token ---")
csrf = new_csrf_token()
print(f" token: {csrf}")
print(f" valid (correct): {csrf.is_valid(csrf.value)}")
print(f" valid (incorrect): {csrf.is_valid('tampered')}")
# ── nonce + entropy ───────────────────────────────────────────────────────
print("\n--- nonce + entropy ---")
iv = nonce(12)
print(f" nonce(12): {iv.hex()}")
tok = token(32)
print(f" entropy_bits(token(32)): ~{entropy_bits(tok):.0f} bits")
# ── secure_compare ────────────────────────────────────────────────────────
print("\n--- secure_compare ---")
a = token(32)
print(f" secure_compare(a, a): {secure_compare(a, a)}")
print(f" secure_compare(a, b): {secure_compare(a, token(32))}")
print("\n=== done ===")
For the random module alternative — random.random(), random.randint(), random.choice() — these use a Mersenne Twister PRNG that is fast and reproducible (seeded) but cryptographically insecure: past outputs can be predicted from observed outputs — never use random for passwords, tokens, session IDs, CSRF tokens, cryptographic nonces, or any security-sensitive value; use secrets for all of those and random only for simulations, games, sampling, and shuffle operations where predictability is acceptable or desired. For the os.urandom alternative — os.urandom(n) returns n cryptographically random bytes directly from the OS CSPRNG — secrets is a high-level wrapper around os.urandom that adds convenient formatting (token_hex, token_urlsafe), range-limited integers (randbelow), and sequence selection (choice); use secrets unless you specifically need raw bytes for a custom encoding or key derivation function where os.urandom is the cleaner call. The Claude Skills 360 bundle includes secrets skill sets covering token()/token_hex()/api_key() token generators, random_password()/passphrase() credential generators, numeric_otp()/alphanumeric_otp()/CsrfToken with new_csrf_token(), and nonce()/entropy_bits()/secure_compare() utilities. Start with the free tier to try cryptographic token patterns and secrets pipeline code generation.