Skip to content

aatuh/randutil

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

randutil

CSPRNG-first random utilities for Go. Small, composable generators with bias-free numeric helpers, strings/tokens, distributions, UUIDs, ULIDs, NanoIDs, and sampling utilities.

Documentation

Install

go get github.com/aatuh/randutil/v2

Requires Go 1.25.0+.

Quick start

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)
}

Choose a generator

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.

Common recipes

URL-safe token:

s, _ := randstring.TokenURLSafe(24) // ~32 chars, URL-safe

Range 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"})

Deterministic testing

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 ./...

Workspace and domain separation

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 and replay

Record entropy when debugging a deterministic failure:

src := adapters.NewRecorder(adapters.CryptoSource())
rng := core.New(src)
_, _ = rng.Bytes(16)
replay := src.Replay()
_ = replay

Must helpers (opt-in)

Must* 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 ./...

Security model

  • 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.Reader is safe for concurrent use.
  • Workspace serializes root derivation and stream reads for its returned streams.
  • HKDF/ChaCha20-derived streams return core.ErrSourceExhausted before the ChaCha20 per-stream counter limit is exceeded.
  • For high-throughput workloads, wrap sources with adapters.BufferedSource to amortize small reads.
  • Unbiased sampling (rejection sampling for ranges/charsets).
  • Token string helpers return immutable strings; use Token*Bytes when secrets must be wipeable.
  • Deterministic sources are for testing and benchmarks only.
  • Build with -tags=randutil_policy to make deterministic source/root constructors fail with ErrDeterministicDisabled.

If your source or RNG is not thread-safe, wrap it with adapters.LockedSource or adapters.LockedRNG.

Notes

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.

About

Cryptographically safe random utilities for Go. Helpers for generating random hex strings, tokens, numbers, bytes, UUIDs, emails etc. Thin wrappers around crypto/rand for everyday use.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages