Random #
Python menyediakan dua modul untuk bekerja dengan nilai acak yang memiliki tujuan sangat berbeda: modul random untuk kebutuhan umum seperti simulasi, pengujian, dan game, serta modul secrets untuk kebutuhan keamanan seperti token autentikasi dan password. Memilih modul yang salah bisa berujung pada celah keamanan serius — angka dari random bisa diprediksi oleh penyerang, sementara secrets dirancang agar tidak bisa diprediksi.
random vs secrets — Pilih yang Tepat
#
Gunakan modul random untuk:
✓ Simulasi dan permodelan (Monte Carlo, dll)
✓ Game dan animasi
✓ Pengacakan data untuk testing
✓ Sampling statistik
✓ Shuffling playlist
Gunakan modul secrets untuk:
✗ Token autentikasi atau session
✗ Password reset link
✗ API key atau secret key
✗ OTP (One Time Password)
✗ Apapun yang menyangkut keamanan
Jangan gunakanrandomuntuk keperluan keamanan. Generator angka acak dirandommenggunakan Mersenne Twister yang deterministik — jika penyerang bisa mengamati cukup banyak output, mereka bisa memprediksi nilai berikutnya. Gunakansecretsuntuk semua kebutuhan kriptografi.
Modul random — Angka Acak Umum
#
Angka Acak #
import random
# Float acak antara 0.0 (inklusif) sampai 1.0 (eksklusif)
print(random.random()) # contoh: 0.7324...
# Float acak dalam rentang tertentu
print(random.uniform(1.0, 10.0)) # contoh: 7.234...
# Integer acak inklusif di kedua ujung
print(random.randint(1, 6)) # dadu: 1, 2, 3, 4, 5, atau 6
print(random.randrange(0, 10)) # 0 sampai 9 (eksklusif di atas)
print(random.randrange(0, 100, 5)) # 0, 5, 10, ..., 95 (kelipatan 5)
Memilih dari Sequence #
import random
buah = ["apel", "jeruk", "mangga", "pisang", "anggur"]
# Pilih satu elemen acak
print(random.choice(buah)) # contoh: "mangga"
# Pilih n elemen acak tanpa pengulangan
print(random.sample(buah, 3)) # contoh: ['jeruk', 'apel', 'anggur']
# Pilih n elemen acak dengan pengulangan (bisa muncul lebih dari sekali)
print(random.choices(buah, k=3)) # contoh: ['apel', 'apel', 'jeruk']
# choices() dengan bobot -- elemen tertentu lebih sering dipilih
hadiah = ["mobil", "motor", "sepeda", "kaos"]
bobot = [1, 5, 20, 74] # probabilitas relatif
print(random.choices(hadiah, weights=bobot, k=10))
Mengacak Urutan #
import random
kartu = list(range(1, 53)) # 52 kartu
# shuffle() -- acak urutan di tempat (in-place), modifikasi list asli
random.shuffle(kartu)
print(kartu[:5]) # contoh: [34, 7, 51, 2, 19]
# ANTI-PATTERN: shuffle pada tuple atau immutable -- tidak bisa
# random.shuffle((1, 2, 3)) # TypeError
# Untuk sequence immutable, gunakan sample() dengan panjang penuh
tuple_data = (1, 2, 3, 4, 5)
teracak = random.sample(tuple_data, len(tuple_data))
print(teracak) # contoh: [3, 1, 5, 2, 4]
Seed — Membuat Hasil yang Reproducible #
import random
# Tanpa seed -- hasil berbeda setiap run
print(random.random()) # berbeda tiap dijalankan
# Dengan seed -- hasil selalu sama untuk seed yang sama
random.seed(42)
print(random.random()) # 0.6394267984578837 -- selalu sama
print(random.randint(1, 100)) # 2 -- selalu sama
random.seed(42) # reset ke seed yang sama
print(random.random()) # 0.6394267984578837 -- sama persis
# Penggunaan nyata: testing yang reproducible
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] -- selalu sama
Gunakan random.seed() di unit test yang melibatkan operasi acak agar hasilnya reproducible dan test tidak flaky. Jangan gunakan seed di production code yang membutuhkan keacakan sungguhan.Distribusi Statistik #
Modul random juga menyediakan generator dengan distribusi statistik tertentu, berguna untuk simulasi:
import random
# Distribusi normal (Gaussian) -- mean=0, std=1
nilai = random.gauss(mu=0, sigma=1)
# Distribusi normal -- mirip gauss, sedikit lebih lambat tapi thread-safe
nilai = random.normalvariate(mu=170, sigma=10) # tinggi badan
# Distribusi eksponensial -- untuk modeling waktu antar kejadian
waktu_antar_request = random.expovariate(lambd=0.5) # rata-rata 2 detik
# Distribusi seragam (sudah dibahas: random.uniform())
# Distribusi triangular -- ada batas bawah, atas, dan modus
nilai_proyek = random.triangular(low=60, high=100, mode=80)
# Contoh: simulasi tinggi badan 1000 orang
tinggi_badan = [random.normalvariate(165, 10) for _ in range(1000)]
rata_rata = sum(tinggi_badan) / len(tinggi_badan)
print(f"Rata-rata tinggi: {rata_rata:.1f} cm") # mendekati 165
Kelas Random — Instance Terpisah
#
Untuk aplikasi yang perlu beberapa generator acak independen (misalnya di multi-threading), buat instance Random tersendiri daripada menggunakan fungsi module-level.
import random
# ANTI-PATTERN: gunakan fungsi module-level di multi-threading
# -- state generator dibagi antar thread, bisa menghasilkan hasil tidak terduga
# BENAR: buat instance terpisah per thread atau per kebutuhan
gen_simulasi = random.Random(seed=42)
gen_game = random.Random()
print(gen_simulasi.randint(1, 100)) # independen dari gen_game
print(gen_game.choice(["a", "b", "c"]))
Modul secrets — Angka Acak Kriptografi
#
Modul secrets (Python 3.6+) menggunakan sumber entropi dari sistem operasi (seperti /dev/urandom di Linux) yang dirancang untuk keamanan kriptografi.
Token dan ID Aman #
import secrets
# Token hex -- string heksadesimal acak
token = secrets.token_hex(32) # 64 karakter hex = 256 bit entropi
print(token) # contoh: "a3f8b2c1d4e5f6789..."
# Token bytes -- bytes acak
token_bytes = secrets.token_bytes(32) # 32 bytes = 256 bit
# Token URL-safe -- aman digunakan di URL, tidak ada karakter khusus
token_url = secrets.token_urlsafe(32) # base64url encoded
print(token_url) # contoh: "Xk3mP9nL2qR7sT4vW..."
# Contoh penggunaan nyata
def buat_token_reset_password() -> str:
"""Buat token untuk link reset password."""
return secrets.token_urlsafe(32) # 32 bytes = sangat sulit ditebak
def buat_session_id() -> str:
"""Buat session ID untuk autentikasi."""
return secrets.token_hex(32)
Pilihan Acak yang Aman #
import secrets
import string
# secrets.choice() -- pilih elemen acak yang kriptografis aman
alfabet = string.ascii_letters + string.digits + string.punctuation
# ANTI-PATTERN: gunakan random untuk password
import random
password_tidak_aman = "".join(random.choice(alfabet) for _ in range(16))
# BENAR: gunakan secrets untuk password
password_aman = "".join(secrets.choice(alfabet) for _ in range(16))
# secrets.randbelow(n) -- integer acak dari 0 sampai n-1
angka = secrets.randbelow(100) # 0 sampai 99
Generator Password yang Benar #
import secrets
import string
def buat_password(
panjang: int = 16,
pakai_huruf: bool = True,
pakai_angka: bool = True,
pakai_simbol: bool = True,
) -> str:
"""
Buat password acak yang kuat menggunakan secrets.
Memastikan minimal satu karakter dari setiap kategori yang dipilih.
"""
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("Minimal satu kategori karakter harus dipilih")
# Isi sisa karakter
sisa = [secrets.choice(karakter) for _ in range(panjang - len(harus_ada))]
# Gabungkan dan acak urutan
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:
"""Buat OTP numerik yang aman."""
# ANTI-PATTERN: gunakan random
# import random
# return "".join(str(random.randint(0, 9)) for _ in range(panjang))
# BENAR: gunakan secrets
return "".join(str(secrets.randbelow(10)) for _ in range(panjang))
print(buat_otp()) # contoh: "847293"
print(buat_otp(8)) # contoh: "29471836"
Perbandingan Token yang Aman #
Saat membandingkan token (misalnya memvalidasi token reset password), jangan gunakan == biasa karena rentan terhadap timing attack.
import secrets
def validasi_token(token_diterima: str, token_tersimpan: str) -> bool:
# ANTI-PATTERN: perbandingan biasa -- rentan timing attack
# return token_diterima == token_tersimpan
# BENAR: compare_digest membandingkan dalam waktu konstan
return secrets.compare_digest(token_diterima, token_tersimpan)
Timing attack adalah teknik serangan di mana penyerang mengukur waktu yang dibutuhkan untuk membandingkan dua string. Perbandingan==biasa berhenti segera setelah menemukan karakter yang berbeda — semakin banyak karakter yang cocok, semakin lama waktu eksekusi.secrets.compare_digest()selalu mengambil waktu yang sama terlepas dari isi string.
Contoh Penggunaan Nyata #
Simulasi Monte Carlo #
import random
import math
def estimasi_pi(n_titik: int) -> float:
"""
Estimasi nilai π menggunakan metode Monte Carlo.
Lempar titik acak ke dalam kotak 2x2, hitung yang jatuh di dalam lingkaran.
"""
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}") # mendekati 3.141593
print(f"π = {math.pi:.6f}")
A/B Testing — Pembagian Grup yang Reproducible #
import random
import hashlib
def tentukan_grup_ab(user_id: int, persen_grup_a: int = 50) -> str:
"""
Tentukan grup A/B untuk user secara konsisten.
User yang sama selalu masuk grup yang sama.
"""
# Hash user_id untuk mendapat distribusi yang merata
hash_val = int(hashlib.md5(str(user_id).encode()).hexdigest(), 16)
bucket = hash_val % 100
return "A" if bucket < persen_grup_a else "B"
# User yang sama selalu dapat grup yang sama
print(tentukan_grup_ab(1001)) # selalu "A" atau selalu "B"
print(tentukan_grup_ab(1001)) # sama persis dengan baris di atas
Ringkasan #
- Gunakan
randomuntuk simulasi, testing, dan game — cepat, mudah digunakan, tapi tidak aman secara kriptografi.- Gunakan
secretsuntuk semua kebutuhan keamanan — token, password, OTP, session ID. Angka darirandombisa diprediksi.random.seed()membuat hasil reproducible — berguna di unit test yang melibatkan operasi acak.random.choices(weights=...)untuk pemilihan dengan probabilitas berbeda-beda, seperti simulasi loot drop atau hadiah.random.shuffle()mengacak list in-place; gunakanrandom.sample(lst, len(lst))untuk sequence immutable.secrets.token_urlsafe(32)adalah cara standar membuat token untuk URL (reset password, email verification).secrets.compare_digest()untuk membandingkan token — hindari timing attack yang bisa terjadi dengan operator==biasa.- Buat instance
random.Random()tersendiri jika perlu generator acak yang independen, terutama di aplikasi multi-threaded.