CSPRNG-first random utilities for Go. Small, composable generators with bias-free numeric helpers, strings/tokens, distributions, UUIDs, ULIDs, NanoIDs, and sampling utilities.
go get github.com/aatuh/randutil/v2Requires Go 1.25.0+.
Use Default for the normal secure generator bundle. It uses
crypto/rand.Reader through the package generators.
package main
import (
"fmt""log""github.com/aatuh/randutil/v2"
)
funcmain() {
r:=randutil.Default()
b, err:=r.Numeric.Bytes(16)
iferr!=nil {
log.Fatal(err)
}
tok, err:=r.String.TokenURLSafe(24)
iferr!=nil {
log.Fatal(err)
}
u4, err:=r.UUID.V4()
iferr!=nil {
log.Fatal(err)
}
when, err:=r.Time.Datetime()
iferr!=nil {
log.Fatal(err)
}
fmt.Println(len(b), len(tok), u4, when)
}| Use case | API | Notes |
|---|---|---|
| Secure default utilities | randutil.Default() or package functions | Uses crypto/rand.Reader. |
| Inject a source or RNG | randutil.New(src), package New functions | Use for tests, fixtures, wrappers, and custom sources. |
| Named derived streams | randutil.NewWorkspace(root) | Domain-separates labels from a shared root. |
| One derived stream | randutil.Derive(seed, label) | Requires high-entropy secret seeds for security-sensitive use. |
| Fast CSPRNG stream | randutil.Fast() | Seeded from crypto/rand; not for strict FIPS/OS RNG compliance. |
| Deterministic fixtures | adapters.DeterministicSource, randutil.DeterministicRoot | Testing and replay only unless the seed is high-entropy and secret. |
URL-safe token:
s, _:=randstring.TokenURLSafe(24) // ~32 chars, URL-safeRange and sampling:
n, _:=numeric.IntRange(10, 20) // inclusivearr:= []int{1, 2, 3, 4, 5}
_=collection.Shuffle(arr)
subset, _:=collection.Sample(arr, 2)UUIDs:
u4, _:=uuid.V4()
u7, _:=uuid.V7()ULID / NanoID:
u, _:=ulid.ID()
id, _:=nanoid.ID()UUID v7 and ULID values encode time for ordering, but they are not monotonic sequence counters within the same millisecond.
Distributions:
x, _:=dist.Normal(0, 1)
k, _:=dist.Poisson(12)Email:
mail, _:=email.Email(email.Options{TLD: "org"})Use a deterministic source and pass it into core.New, then share the RNG
across generators:
package main
import (
"fmt""github.com/aatuh/randutil/v2/adapters""github.com/aatuh/randutil/v2/core""github.com/aatuh/randutil/v2/randstring"
)
funcmain() {
src, err:=adapters.DeterministicSource([]byte("seed"))
iferr!=nil {
panic(err)
}
rng:=core.New(src)
gen:=randstring.New(rng)
s, _:=gen.String(12)
fmt.Println(s)
}For exact byte control in tests, pass a custom io.Reader into core.New.
If you want the intent to be explicit, use adapters/deterministic.
Deterministic sources are for tests and benchmarks only; DO NOT USE FOR
TOKENS / AUTH unless the seed is high-entropy and kept secret.
To check that deterministic constructors fail where policy mode is enabled:
go test -tags=randutil_policy ./...ws:=randutil.NewWorkspace(randutil.SecureRoot())
sessions, _:=ws.Rand("sessions")
tok, _:=sessions.String.TokenURLSafe(24)
fmt.Println(len(tok))For deterministic fixtures:
ws:=randutil.NewWorkspace(randutil.DeterministicRoot([]byte("seed")))
sampling, _:=ws.Rand("sampling")
v, _:=sampling.Numeric.IntRange(1, 10)
fmt.Println(v)Sub-workspaces reduce label collisions:
billing, _:=ws.Sub("billing")
nonces, _:=billing.Rand("nonces")
nonce, _:=nonces.String.TokenURLSafe(24)
fmt.Println(len(nonce))Workspaces can also track bytes read per cached stream:
ws:=randutil.NewWorkspaceWithOptions(randutil.SecureRoot(), randutil.WorkspaceOptions{
UsageHook: func(labelstring, deltauint64) {
fmt.Println(label, delta)
},
})
tokens, _:=ws.Rand("tokens")
_, _=tokens.String.TokenURLSafe(24)
used, _:=ws.Usage("tokens")
fmt.Println(used)For a single derived stream without a workspace:
r, err:=randutil.Derive([]byte("seed"), "payments")
iferr!=nil {
panic(err)
}
id, _:=r.UUID.V7()
fmt.Println(id)Workspace streams are derived via HKDF-SHA256 + ChaCha20; for strict FIPS/OS
RNG compliance, use crypto/rand.Reader directly.
Each derived stream is limited to 256 GiB of output per seed+label and returns
core.ErrSourceExhausted if that limit would be exceeded; derive a fresh label
for longer high-throughput streams.
Workspace serializes custom root derivation and stream reads, so Stream,
Rand, and Sub are safe for concurrent use.
When caching is disabled with WorkspaceOptions{MaxCached: -1}, returned
streams are not retained by the workspace; callers own and should close them.
For a fast derived CSPRNG seeded from crypto/rand:
fast, _:=randutil.Fast()
id, _:=fast.UUID.V7()
fmt.Println(id)Record entropy when debugging a deterministic failure:
src:=adapters.NewRecorder(adapters.CryptoSource())
rng:=core.New(src)
_, _=rng.Bytes(16)
replay:=src.Replay()
_=replayMust* helpers are gated behind the build tag randutil_must to avoid
accidental panics in production. Enable the tag if you want them:
go test -tags=randutil_must ./...
go build -tags=randutil_must ./...- Default entropy is
crypto/rand.Reader. - No process-wide configurable RNG state; package defaults bind their own source/RNG.
- Generators are concurrency-safe iff the injected RNG is;
crypto/rand.Readeris safe for concurrent use. - Workspace serializes root derivation and stream reads for its returned streams.
- HKDF/ChaCha20-derived streams return
core.ErrSourceExhaustedbefore the ChaCha20 per-stream counter limit is exceeded. - For high-throughput workloads, wrap sources with
adapters.BufferedSourceto amortize small reads. - Unbiased sampling (rejection sampling for ranges/charsets).
- Token string helpers return immutable strings; use
Token*Byteswhen secrets must be wipeable. - Deterministic sources are for testing and benchmarks only.
- Build with
-tags=randutil_policyto make deterministic source/root constructors fail withErrDeterministicDisabled.
If your source or RNG is not thread-safe, wrap it with
adapters.LockedSource or adapters.LockedRNG.
Every MustX(...) has a non-panicking X(...) variant that returns
(T, error) for server/CLI contexts.
Package example_test.go files contain executable examples that appear in
pkg.go.dev and are run by go test to prevent documentation drift.