Banyak operasi keamanan bergantung pada angka acak: pembangkitan kunci enkripsi, token sesi, nonce, serta password reset. Bila angka yang dianggap acak ternyata dapat diprediksi, penyerang dapat menebak kunci atau token dengan menebus kemungkinan terbatas. Sayangnya, generator angka acak bawaan bahasa pemrograman umumnya tidak dirancang untuk tujuan kriptografis—mereka mengutamakan kecepatan dan reproduksibilitas, bukan keacakan yang kuat. Pada chapter ini kita akan membedakan kotlin.random.Random dari java.security.SecureRandom, mempraktikkan pembangkitan token hex dan base64url, serta memahami UUID versi 3 dan versi 4 dari sudut pandang keamanan.
Random Biasa vs SecureRandom #
kotlin.random.Random adalah generator pseudo-random cepat yang cocok untuk simulasi, pengujian, dan logika non-keamanan. Bila diberi seed, output bersifat deterministik—berguna untuk pengujian yang dapat direproduksi, namun fatal untuk kunci enkripsi. Sebaliknya, java.security.SecureRandom memakai sumber entropi sistem operasi (misalnya /dev/urandom pada Linux) dan algoritma kriptografis yang aman.
// kotlin.random.Random: cepat, dapat di-seed, TIDAK aman untuk kriptografi
val r1 = kotlin.random.Random(42)
println(r1.nextInt(0, 1000)) // selalu 333 dengan seed 42
// java.security.SecureRandom: lambat, entropi OS, AMAN untuk kriptografi
val r2 = SecureRandom.getInstance("SHA1PRNG")
r2.setSeed(...) // entropi OS; jangan hardcode seed di produksiPada produksi, panggil SecureRandom() tanpa argumen agar JVM memilih implementasi terkuat yang tersedia. Hindari setSeed() dengan nilai hardcode—seed yang dapat ditebak membuat SecureRandom tidak lagi aman.
Pembangkitan Token #
Token sesi dan API umumnya berupa string panjang yang dibangkitkan dari byte acak. Dua format umum: hex (panjang dua kali byte asli) dan base64url (panjang sekitar 1,33 kali byte asli, aman untuk URL). Untuk token sesi 128-bit keamanan, bangkitkan 16 byte—cukup untuk mencegah brute force.
// secure_randomness.kt
// SecureRandom vs Random. UUID generation. Token generation.
// Note: For deterministic test output, the demo uses a seeded SHA1PRNG.
// In production, use no-arg SecureRandom() to get OS entropy.
import java.security.SecureRandom
import java.util.UUID
import java.util.Base64
// --- Stable demo SecureRandom: SHA1PRNG with fixed seed ---
// Produces same output across runs/JVMs for verifiable demo output.
private val DEMO_RNG: SecureRandom = SecureRandom.getInstance("SHA1PRNG").apply {
setSeed("kotlin-course-stable-seed".toByteArray())
}
// --- Demo: kotlin.random.Random (NOT cryptographically secure) ---
fun kotlinRandomDemo(): List<Int> {
val r = kotlin.random.Random(42) // seeded -> deterministic
return (1..5).map { r.nextInt(0, 1000) }
}
// --- Demo: SecureRandom seeded for stable output (demo only) ---
fun secureRandomDemo(): List<Int> {
return (1..5).map { DEMO_RNG.nextInt(0, 1000) }
}
// --- Token generation: hex string of N bytes from a given SecureRandom ---
fun generateToken(byteLength: Int, rng: SecureRandom = DEMO_RNG): String {
require(byteLength in 16..64) { "panjang token di luar rentang aman" }
val bytes = ByteArray(byteLength)
rng.nextBytes(bytes)
return bytes.joinToString("") { "%02x".format(it) }
}
// --- URL-safe base64 token (RFC 4648 base64url) ---
fun generateUrlSafeToken(byteLength: Int, rng: SecureRandom = DEMO_RNG): String {
val bytes = ByteArray(byteLength)
rng.nextBytes(bytes)
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes)
}
// --- Stable UUID v3 (name-based) from a fixed input for reproducible demo ---
fun stableUuidDemo(): UUID = UUID.nameUUIDFromBytes("[email protected]".toByteArray())
fun main() {
// kotlin.random.Random: seeded, deterministic, NOT secure
println("kotlin.random.Random(42): ${kotlinRandomDemo()}")
println("kotlin.random.Random(42): ${kotlinRandomDemo()} (sama = deterministik)")
println()
// SecureRandom: cryptographically secure
println("SecureRandom seeded (demo): ${secureRandomDemo()}")
println()
// Token generation (raw bytes -> hex)
println("generateToken(16): ${generateToken(16)}")
println("generateToken(32): ${generateToken(32)}")
println()
// URL-safe token
println("generateUrlSafeToken(16): ${generateUrlSafeToken(16)}")
println()
// Stable UUID v3 (name-based) — deterministic per input
println("stableUuidDemo() = ${stableUuidDemo()}")
println("stableUuidDemo() = ${stableUuidDemo()} (sama = deterministik)")
}Output yang Dihasilkan:
kotlin.random.Random(42): [333, 440, 941, 302, 341]
kotlin.random.Random(42): [333, 440, 941, 302, 341] (sama = deterministik)
SecureRandom seeded (demo): [633, 760, 885, 692, 254]
generateToken(16): 805bab363726631fab4237968c11b0b7
generateToken(32): b6c68ddd75d4320d00eac64b8a5284955a9002b8571fd331e31070d0ece99137
generateUrlSafeToken(16): uaMu7Z2DF2TgDWtJ4fKBSw
stableUuidDemo() = 63c7a0cf-69f5-369b-88cb-febc1dc4a2f0
stableUuidDemo() = 63c7a0cf-69f5-369b-88cb-febc1dc4a2f0 (sama = deterministik)Mari kita bedah bagian penting dari kode di atas:
DEMO_RNG: instanceSecureRandomdenganSHA1PRNGdan seed stabil"kotlin-course-stable-seed". Demo ini sengaja memakai seed agar output dapat diverifikasi—jangan tiru pola ini di produksi. Pada produksi, panggilSecureRandom()tanpa argumen.kotlinRandomDemo: memakaikotlin.random.Random(42). Dengan seed yang sama, output selalu[333, 440, 941, 302, 341]. Inilah sebabnyaRandomtidak boleh dipakai untuk kunci—penyerang yang mengetahui seed dapat memprediksi seluruh urutan.generateToken: membangkitkanByteArray(byteLength)lalu mengisinya viarng.nextBytes(bytes). Setiap byte disandikan menjadi dua digit heksadesimal dengan"%02x".format(it). Token 16 byte menghasilkan 32 karakter hex.generateUrlSafeToken: memakaiBase64.getUrlEncoder().withoutPadding(). Base64url mengganti+dengan-dan/dengan_, sehingga token aman ditempel di URL tanpa percent encoding. Tanpa padding, karakter=di akhir dihilangkan.stableUuidDemo: memakaiUUID.nameUUIDFromBytesyang menghasilkan UUID versi 3 (berbasis nama MD5). Karena input berupa string konstan, output UUID juga konstan—berguna untuk identitas deterministik berbasis nama domain.
UUID dan Keamanan #
UUID (Universally Unique Identifier) tersedia dalam beberapa versi. Versi 4 adalah UUID acak yang dibangkitkan dari angka acak; versi 3 dibangkitkan dari nama via MD5; versi 5 via SHA-1. Versi 1 menggunakan timestamp MAC address—berbahaya karena membocorkan informasi mesin.
// Versi 4 (acak) — gunakan SecureRandom, jangan java.util.Random
val v4 = UUID.randomUUID() // memakai SecureRandom di JDK modern
// Versi 3 (name-based) — deterministik untuk input yang sama
val v3 = UUID.nameUUIDFromBytes("[email protected]".toByteArray())Pada JDK modern, UUID.randomUUID() memakai SecureRandom secara internal, sehingga UUID v4 aman untuk identifier sesi. Versi 3 cocok untuk identitas deterministik berbasis nama domain (misalnya menghasilkan UUID konsisten untuk email tertentu), namun tidak boleh dipakai sebagai token rahasia karena dapat dihitung ulang oleh siapa pun yang mengetahui input.
Anti-pattern Umum #
Berikut kesalahan umum yang harus dihindari:
// 1. java.util.Random untuk kunci enkripsi (dapat di-prediksi)
val bad = java.util.Random().nextLong()
// 2. System.currentTimeMillis() sebagai token (dapat ditebak)
val worse = System.currentTimeMillis()
// 3. SecureRandom dengan seed hardcode
val worst = SecureRandom("seed".toByteArray())Panjang Token yang Direkomendasikan #
| Kegunaan | Panjang byte | Panjang hex | Catatan |
|---|---|---|---|
| Token reset password | 16 | 32 | Cukup untuk mencegah brute force 128-bit |
| API key | 32 | 64 | Aman untuk jangka panjang |
| Kunci enkripsi AES-256 | 32 | 64 | Sesuai panjang kunci |
| Nonce AES-GCM | 12 | 24 | Standar GCM, jangan kurang dari 12 byte |
| Session ID browser | 32 | 64 | Aman untuk sesi jangka panjang |
Tabel di atas memberi patokan praktis. Token yang lebih pendek dari rekomendasi dapat menjadi target brute force; token yang lebih panjang memberi keamanan tambahan tetapi memboroskan penyimpanan dan bandwidth.
Referensi Lanjutan #
- SecureRandom
— dokumentasi
SecureRandomJava SE 21 untuk generator nilai acak kriptografis
Penutup #
Pilihan generator acak menentukan apakah kunci, token, dan UUID dapat diprediksi atau tidak. Gunakan SecureRandom() tanpa argumen untuk kebutuhan kriptografis, dan simpan kotlin.random.Random untuk pengujian serta simulasi non-keamanan. Chapter berikutnya akan membahas password hashing dengan algoritma khusus seperti bcrypt, scrypt, dan Argon2 yang dirancang untuk memperlambat serangan tebakan password.