skip to content

When is kotlin.random.Random inappropriate, and what concurrency and security considerations apply when using random generators in production Kotlin code?

level: seniorimportance: should knowfreq 45%

answer

  1. PRNG predictable -> never for tokens/keys
  2. use SecureRandom + asKotlinRandom for secrets
  3. Default is thread-safe, Random(seed) is not
  4. predictable seed = guessable output
  5. re-seeding per call is a bug

basics

~10 s

kotlin.random.Random is predictable, so never use it for passwords, tokens, or keys — use a secure generator like SecureRandom. Also, the default generator is thread-safe but a custom seeded one may not be.

solid answer

~40 s

`kotlin.random.Random` is a pseudo-random generator: given the algorithm and observed output an attacker can predict future values, so it must never produce security-sensitive values (session IDs, password-reset tokens, API keys, IVs, salts). For those, use a cryptographically secure source — `java.security.SecureRandom` on the JVM (you can adapt it via `SecureRandom().asKotlinRandom()` to keep the Kotlin API). Concurrency: `Random.Default` is documented thread-safe, but a `Random(seed)` instance you create is **not** guaranteed safe to share across threads — concurrent calls can corrupt its internal state and skew the distribution; give each thread its own instance or synchronize. Seeded generators also leak reproducibility: if a seed is predictable (e.g. `System.currentTimeMillis()`), outputs are guessable. Finally, reusing one seeded instance is what gives reproducibility — re-seeding per call usually defeats the purpose and can bias output.

code

kotlin · 9 lines
kotlin
import java.security.SecureRandom
import kotlin.random.asKotlinRandom

// WRONG: predictable token
// val token = kotlin.random.Random.nextLong()

// RIGHT: cryptographically secure
val secureRng = SecureRandom().asKotlinRandom()
val tokenBytes = ByteArray(32).also { SecureRandom().nextBytes(it) }

go deeper

for a junior

Recognizes that random values for passwords/tokens need a secure generator, not the basic Random.

for a middle

Explains PRNG predictability, names SecureRandom for secrets, and avoids per-call re-seeding.

for a senior

Reasons about Default thread-safety vs custom-instance safety, uses asKotlinRandom to bridge, and articulates the seed-predictability risk.

for a principal

Sets organization-wide policy separating PRNG (tests/sim) from CSPRNG (secrets), with thread-confinement and review rules to enforce it.

## Security: PRNG vs CSPRNG `kotlin.random.Random` is a **pseudo-random number generator (PRNG)**. Its stream is fully determined by internal state; after observing enough output an attacker can reconstruct the state and **predict all future values**. That makes it unsuitable for anything an adversary benefits from guessing: - session identifiers, CSRF tokens, password-reset / email-verification tokens - API keys, nonces, IVs, salts, key material For these you need a **cryptographically secure PRNG (CSPRNG)**. On the JVM that is `java.security.SecureRandom`: ```kotlin import java.security.SecureRandom import kotlin.random.asKotlinRandom val secure = SecureRandom().asKotlinRandom() // keep the Kotlin API, secure source val token = ByteArray(32).also { SecureRandom().nextBytes(it) } ``` `asKotlinRandom()` adapts a `java.util.Random` (including `SecureRandom`) to the `kotlin.random.Random` interface; `asJavaRandom()` goes the other way. This lets shared code keep the Kotlin API while the JVM side supplies a secure implementation. ## Thread-safety - `Random.Default` (what `Random.nextInt(...)` uses) is documented **thread-safe**. - A generator **you** construct with `Random(seed)` is **not guaranteed** thread-safe. Sharing it across threads risks concurrent state mutation that can corrupt the sequence or bias the distribution. Mitigations: one instance per thread (e.g. `ThreadLocal`), confine it to a single coroutine/thread, or synchronize access. In high-throughput code, per-thread instances also avoid contention. ## Reproducibility as a liability Seeding gives determinism — useful for tests, dangerous in production if the seed is predictable. Seeding from `System.currentTimeMillis()` or a counter makes the output guessable. If you need unpredictability, do **not** seed (use `Default`) or use a CSPRNG. ## Common mistakes - **Re-seeding per call**: `Random(seed).nextInt()` in a loop returns the *same* first value every iteration — a classic bug. Create the instance once, reuse it. - Assuming any seed makes output 'random enough' for security. - Sharing a custom instance across coroutines that hop threads. ## Quick decision rule Reproducible test/sim -> `Random(seed)`. General non-security randomness -> `Random.Default`. Anything an attacker could exploit -> `SecureRandom` / CSPRNG.

  • How do you keep using the kotlin.random.Random API while getting cryptographic security on the JVM?
    Wrap a SecureRandom with .asKotlinRandom(), which adapts the secure java.util.Random subclass to the Kotlin Random interface.
  • Why does Random(seed).nextInt() inside a loop keep returning the same number?
    A new generator is created from the same seed each iteration, so it always replays the first value. Construct the instance once outside the loop.
  • Is Random.Default safe to call from multiple threads?
    Yes, Random.Default is documented thread-safe; a custom Random(seed) instance is not guaranteed safe to share.

Using kotlin.random.Random for a token is like a 'random' lock whose combination an attacker can deduce after watching a few openings.

saying these in an interview costs you the question

  • Generating session tokens or keys with kotlin.random.Random
  • Seeding with System.currentTimeMillis() for unpredictability
  • Assuming any custom Random instance is thread-safe like Default
  • Re-creating Random(seed) on every call expecting different values
  • Treating a PRNG and a CSPRNG as interchangeable

context