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"
)
func main() {
r := randutil.Default()
b, err := r.Numeric.Bytes(16)
if err != nil {
log.Fatal(err)
}
tok, err := r.String.TokenURLSafe(24)
if err != nil {
log.Fatal(err)
}
u4, err := r.UUID.V4()
if err != nil {
log.Fatal(err)
}
when, err := r.Time.Datetime()
if err != 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) // inclusive
arr := []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"
)
func main() {
src, err := adapters.DeterministicSource([]byte("seed"))
if err != 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(label string, delta uint64) {
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")
if err != 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.