Random #
Python provides two modules for working with random values that serve very different purposes: the random module for general needs like simulation, testing, and games, and the secrets module for security needs like authentication tokens and passwords. Choosing the wrong module can lead to serious security holes — numbers from random can be predicted by attackers, while secrets is designed to be unpredictable.
random vs secrets — Choose the Right One
#
Use the random module for:
✓ Simulation and modeling (Monte Carlo, etc.)
✓ Games and animations
✓ Data shuffling for testing
✓ Statistical sampling
✓ Shuffling playlists
Use the secrets module for:
✗ Authentication tokens or sessions
✗ Password reset links
✗ API keys or secret keys
✗ OTP (One Time Password)
✗ Anything security-related
Don’t userandomfor security purposes. The random number generator inrandomuses Mersenne Twister, which is deterministic — if an attacker can observe enough output, they can predict the next values. Usesecretsfor all cryptographic needs.
The random Module — General Random Numbers
#
Random Numbers #
import random
# Random float between 0.0 (inclusive) and 1.0 (exclusive)
print(random.random()) # example: 0.7324...
# Random float in a given range
print(random.uniform(1.0, 10.0)) # example: 7.234...
# Random integer inclusive at both ends
print(random.randint(1, 6)) # dice: 1, 2, 3, 4, 5, or 6
print(random.randrange(0, 10)) # 0 to 9 (exclusive at the top)
print(random.randrange(0, 100, 5)) # 0, 5, 10, ..., 95 (multiples of 5)
Choosing from a Sequence #
import random
buah = ["apel", "jeruk", "mangga", "pisang", "anggur"]
# Pick one random element
print(random.choice(buah)) # example: "mangga"
# Pick n random elements without repetition
print(random.sample(buah, 3)) # example: ['jeruk', 'apel', 'anggur']
# Pick n random elements with repetition (can appear more than once)
print(random.choices(buah, k=3)) # example: ['apel', 'apel', 'jeruk']
# choices() with weights -- certain elements are picked more often
hadiah = ["mobil", "motor", "sepeda", "kaos"]
bobot = [1, 5, 20, 74] # relative probabilities
print(random.choices(hadiah, weights=bobot, k=10))
Shuffling Sequences #
import random
kartu = list(range(1, 53)) # 52 cards
# shuffle() -- shuffle in place, modifies the original list
random.shuffle(kartu)
print(kartu[:5]) # example: [34, 7, 51, 2, 19]
# ANTI-PATTERN: shuffling a tuple or immutable -- not possible
# random.shuffle((1, 2, 3)) # TypeError
# For immutable sequences, use sample() with the full length
tuple_data = (1, 2, 3, 4, 5)
teracak = random.sample(tuple_data, len(tuple_data))
print(teracak) # example: [3, 1, 5, 2, 4]
Seed — Making Results Reproducible #
import random
# Without a seed -- different results every run
print(random.random()) # different each time it runs
# With a seed -- always the same results for the same seed
random.seed(42)
print(random.random()) # 0.6394267984578837 -- always the same
print(random.randint(1, 100)) # 2 -- always the same
random.seed(42) # reset to the same seed
print(random.random()) # 0.6394267984578837 -- exactly the same
# Real use: reproducible testing
def simulasi_dadu(n_lempar: int) -> list:
return [random.randint(1, 6) for _ in range(n_lempar)]
random.seed(0)
hasil_test = simulasi_dadu(5) # [4, 1, 4, 3, 5] -- always the same
Use random.seed() in unit tests involving random operations so results are reproducible and tests aren’t flaky. Don’t use seeds in production code that needs true randomness.Statistical Distributions #
The random module also provides generators with specific statistical distributions, useful for simulation:
import random
# Normal (Gaussian) distribution -- mean=0, std=1
nilai = random.gauss(mu=0, sigma=1)
# Normal distribution -- similar to gauss, slightly slower but thread-safe
nilai = random.normalvariate(mu=170, sigma=10) # height
# Exponential distribution -- for modeling time between events
waktu_antar_request = random.expovariate(lambd=0.5) # average 2 seconds
# Uniform distribution (already covered: random.uniform())
# Triangular distribution -- has lower bound, upper bound, and mode
nilai_proyek = random.triangular(low=60, high=100, mode=80)
# Example: simulating the heights of 1000 people
tinggi_badan = [random.normalvariate(165, 10) for _ in range(1000)]
rata_rata = sum(tinggi_badan) / len(tinggi_badan)
print(f"Average height: {rata_rata:.1f} cm") # close to 165
The Random Class — Separate Instances
#
For applications needing several independent random generators (e.g., in multi-threading), create a separate Random instance instead of using module-level functions.
import random
# ANTI-PATTERN: using module-level functions in multi-threading
# -- the generator state is shared between threads, can produce unexpected results
# CORRECT: create a separate instance per thread or need
gen_simulasi = random.Random(seed=42)
gen_game = random.Random()
print(gen_simulasi.randint(1, 100)) # independent from gen_game
print(gen_game.choice(["a", "b", "c"]))
The secrets Module — Cryptographic Random Numbers
#
The secrets module (Python 3.6+) uses entropy sources from the operating system (like /dev/urandom on Linux) designed for cryptographic security.
Secure Tokens and IDs #
import secrets
# Hex token -- random hexadecimal string
token = secrets.token_hex(32) # 64 hex characters = 256 bits of entropy
print(token) # example: "a3f8b2c1d4e5f6789..."
# Bytes token -- random bytes
token_bytes = secrets.token_bytes(32) # 32 bytes = 256 bits
# URL-safe token -- safe to use in URLs, no special characters
token_url = secrets.token_urlsafe(32) # base64url encoded
print(token_url) # example: "Xk3mP9nL2qR7sT4vW..."
# Real usage examples
def buat_token_reset_password() -> str:
"""Create a token for a password reset link."""
return secrets.token_urlsafe(32) # 32 bytes = very hard to guess
def buat_session_id() -> str:
"""Create a session ID for authentication."""
return secrets.token_hex(32)
Secure Random Choices #
import secrets
import string
# secrets.choice() -- pick a cryptographically secure random element
alfabet = string.ascii_letters + string.digits + string.punctuation
# ANTI-PATTERN: using random for passwords
import random
password_tidak_aman = "".join(random.choice(alfabet) for _ in range(16))
# CORRECT: use secrets for passwords
password_aman = "".join(secrets.choice(alfabet) for _ in range(16))
# secrets.randbelow(n) -- random integer from 0 to n-1
angka = secrets.randbelow(100) # 0 to 99
A Correct Password Generator #
import secrets
import string
def buat_password(
panjang: int = 16,
pakai_huruf: bool = True,
pakai_angka: bool = True,
pakai_simbol: bool = True,
) -> str:
"""
Create a strong random password using secrets.
Ensures at least one character from each selected category.
"""
karakter = ""
harus_ada = []
if pakai_huruf:
karakter += string.ascii_letters
harus_ada.append(secrets.choice(string.ascii_lowercase))
harus_ada.append(secrets.choice(string.ascii_uppercase))
if pakai_angka:
karakter += string.digits
harus_ada.append(secrets.choice(string.digits))
if pakai_simbol:
simbol = "!@#$%^&*()-_=+"
karakter += simbol
harus_ada.append(secrets.choice(simbol))
if not karakter:
raise ValueError("At least one character category must be selected")
# Fill the remaining characters
sisa = [secrets.choice(karakter) for _ in range(panjang - len(harus_ada))]
# Combine and shuffle the order
semua = harus_ada + sisa
secrets.SystemRandom().shuffle(semua)
return "".join(semua)
print(buat_password(16))
print(buat_password(24, pakai_simbol=False))
OTP — One Time Password #
import secrets
def buat_otp(panjang: int = 6) -> str:
"""Create a secure numeric OTP."""
# ANTI-PATTERN: using random
# import random
# return "".join(str(random.randint(0, 9)) for _ in range(panjang))
# CORRECT: use secrets
return "".join(str(secrets.randbelow(10)) for _ in range(panjang))
print(buat_otp()) # example: "847293"
print(buat_otp(8)) # example: "29471836"
Secure Token Comparison #
When comparing tokens (e.g., validating a password reset token), don’t use plain == because it’s vulnerable to timing attacks.
import secrets
def validasi_token(token_diterima: str, token_tersimpan: str) -> bool:
# ANTI-PATTERN: plain comparison -- vulnerable to timing attacks
# return token_diterima == token_tersimpan
# CORRECT: compare_digest compares in constant time
return secrets.compare_digest(token_diterima, token_tersimpan)
A timing attack is an attack technique where the attacker measures the time needed to compare two strings. Plain==comparison stops as soon as it finds a differing character — the more matching characters, the longer the execution time.secrets.compare_digest()always takes the same time regardless of the string contents.
Real-World Examples #
Monte Carlo Simulation #
import random
import math
def estimasi_pi(n_titik: int) -> float:
"""
Estimate π using the Monte Carlo method.
Throw random points into a 2x2 box, count those falling inside the circle.
"""
random.seed(42)
dalam_lingkaran = 0
for _ in range(n_titik):
x = random.uniform(-1, 1)
y = random.uniform(-1, 1)
if x**2 + y**2 <= 1:
dalam_lingkaran += 1
return 4 * dalam_lingkaran / n_titik
print(f"π ≈ {estimasi_pi(1_000_000):.6f}") # close to 3.141593
print(f"π = {math.pi:.6f}")
A/B Testing — Reproducible Group Assignment #
import random
import hashlib
def tentukan_grup_ab(user_id: int, persen_grup_a: int = 50) -> str:
"""
Determine the A/B group for a user consistently.
The same user always ends up in the same group.
"""
# Hash user_id for an even distribution
hash_val = int(hashlib.md5(str(user_id).encode()).hexdigest(), 16)
bucket = hash_val % 100
return "A" if bucket < persen_grup_a else "B"
# The same user always gets the same group
print(tentukan_grup_ab(1001)) # always "A" or always "B"
print(tentukan_grup_ab(1001)) # exactly the same as the line above
Summary #
- Use
randomfor simulation, testing, and games — fast, easy to use, but not cryptographically secure.- Use
secretsfor all security needs — tokens, passwords, OTPs, session IDs. Numbers fromrandomcan be predicted.random.seed()makes results reproducible — useful in unit tests involving random operations.random.choices(weights=...)for selection with different probabilities, like simulating loot drops or prizes.random.shuffle()shuffles a list in place; userandom.sample(lst, len(lst))for immutable sequences.secrets.token_urlsafe(32)is the standard way to create tokens for URLs (password reset, email verification).secrets.compare_digest()for comparing tokens — avoids the timing attack possible with the plain==operator.- Create a separate
random.Random()instance when you need independent random generators, especially in multi-threaded applications.