How do I generate a secure random token, nonce or salt in EK9?
← Advanced Type System · Ref: Q1388
SecureRandom produces cryptographically secure random OCTETS. It has exactly one operation:
salt <- SecureRandom().nextBytes(16) // Integer count -> Bytes
A TOKEN IS OCTETS RENDERED AS TEXT
token <- SecureRandom().nextBytes(32).toBase64() hexId <- SecureRandom().nextBytes(16).toText("hex")
There is deliberately no next() returning an Integer. Reducing random numbers into a range by hand is how modulo bias creeps into tokens, and octets rendered as Base64 or hex are what every protocol actually wants.
IT IS DELIBERATELY NOT A Random
SystemRandom and SeededRandom both implement the Random trait. SecureRandom does NOT. If it did, any function taking a Random would accept a SeededRandom just as happily - and a seeded generator produces the SAME sequence every run, so a nonce or salt made from it is reproducible by anyone who knows the seed. Keeping SecureRandom outside the trait means a function that needs unpredictable octets declares a SecureRandom parameter, and passing it a SeededRandom is a COMPILE ERROR (E06270), not a code-review catch.
NO SEED, ON PURPOSE
There is no SecureRandom(seed) constructor. The generator seeds itself from the operating system's entropy source; there is no way to make it reproducible, and that is the whole point.
TRI-STATE
An unset or negative count gives UNSET Bytes - never a substitute length. A count of 0 gives EMPTY Bytes, which are SET: asking for nothing and getting nothing is a well-defined answer.
TESTING CODE THAT USES IT
There are no test vectors for a secure generator, by definition. Test the PROPERTIES instead: the requested length comes back, successive draws differ, two generators differ. If a test needs repeatable values, the code under test wanted a Random, not a SecureRandom - the two answer different questions.
See Q1383 for Bytes, Q1386 for Digest and Q1387 for HMAC. Use 'ek9 -h SecureRandom' for the full API.
Example
defines module qa.securerandom.usage defines function //Declaring SecureRandom here is the safety property: a SeededRandom will not compile at the call site. newSalt() as pure -> generator as SecureRandom <- rtn as Bytes: generator.nextBytes(16) defines program SecureRandomUsage() stdout <- Stdout() generator <- SecureRandom() //A salt: exactly the number of octets asked for. salt <- newSalt(generator) stdout.println(`salt length: ${length salt}`) //A token is octets rendered as text - 32 octets is 44 characters of Base64. token <- generator.nextBytes(32).toBase64() stdout.println(`token length: ${length token}`) //Or as hex - two characters per octet. hexId <- generator.nextBytes(16).toText("hex") stdout.println(`hex id length: ${length hexId}`) //Successive draws differ, and so do two generators - there is no seed for them to share. stdout.println(`draws differ: ${generator.nextBytes(32) <> generator.nextBytes(32)}`) stdout.println(`generators differ: ${SecureRandom().nextBytes(32) <> SecureRandom().nextBytes(32)}`) //Asking for nothing gives EMPTY Bytes - which are SET. Empty is not unset. nothing <- generator.nextBytes(0) stdout.println(`empty is set: ${nothing?}`) stdout.println(`empty length: ${length nothing}`) //An unset or negative count gives UNSET - never a substitute length. stdout.println(`unset count: ${generator.nextBytes(Integer())?}`) stdout.println(`negative count: ${generator.nextBytes(-1)?}`)
Common mistakes
E06270 — SecureRandom is deliberately NOT a Random, so no Random - seeded or otherwise - can stand in for it. That is the point: a seeded generator gives the same salt every run. The compiler rejects the substitution at the call site.
Incorrect:
salt <- newSalt(SeededRandom(42))
Correct:
salt <- newSalt(generator)
E50060 — SecureRandom only produces octets. There is no next() returning an Integer; render the octets as Base64 or hex for a token, or use SystemRandom if you genuinely wanted a non-cryptographic number.
Incorrect:
token <- generator.next()
Correct:
token <- generator.nextBytes(32).toBase64()
E50060 — There is no seeded constructor - a secure generator seeds itself from the operating system and cannot be made reproducible. If you need repeatable values (a simulation, a test), you wanted SeededRandom.
Incorrect:
generator <- SecureRandom(42)
Correct:
generator <- SecureRandom()
Other ways to ask this
- What is the EK9 equivalent of java.security.SecureRandom?
- How do I generate cryptographically secure random bytes?
- Why can I not pass a SeededRandom where a SecureRandom is expected?
- How do I make a session token or an API key?
Coming from another language?
Java: java.security.SecureRandom EXTENDS java.util.Random, so any method taking a Random accepts either, and new Random(42) passed where a SecureRandom was intended type-checks and yields the same nonce every run. Python: secrets.token_bytes / token_urlsafe are right, but random.getrandbits looks identical at the point of use and nothing in a signature distinguishes them. Go: crypto/rand.Read and math/rand.Read share a shape, so an io.Reader parameter accepts either. Node: crypto.randomBytes is correct; Math.random is not cryptographic and is the one people reach for. EK9: a separate type outside the Random trait, octets only, no seed, and the wrong generator is a compile error.
Keywords: securerandom, token, salt, csprng, cryptography, unpredictable, nonce, entropy, random, seededrandom