Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

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

Choose a generator

Use caseAPINotes
Secure default utilitiesrandutil.Default() or package functionsUses crypto/rand.Reader.
Inject a source or RNGrandutil.New(src), package New functionsUse for tests, fixtures, wrappers, and custom sources.
Named derived streamsrandutil.NewWorkspace(root)Domain-separates labels from a shared root.
One derived streamrandutil.Derive(seed, label)Requires high-entropy secret seeds for security-sensitive use.
Fast CSPRNG streamrandutil.Fast()Seeded from crypto/rand; not for strict FIPS/OS RNG compliance.
Deterministic fixturesadapters.DeterministicSource, randutil.DeterministicRootTesting 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) // 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"})

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

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(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 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

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

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

Choose a generator

Use caseAPINotes
Secure default utilitiesrandutil.Default() or package functionsUses crypto/rand.Reader.
Inject a source or RNGrandutil.New(src), package New functionsUse for tests, fixtures, wrappers, and custom sources.
Named derived streamsrandutil.NewWorkspace(root)Domain-separates labels from a shared root.
One derived streamrandutil.Derive(seed, label)Requires high-entropy secret seeds for security-sensitive use.
Fast CSPRNG streamrandutil.Fast()Seeded from crypto/rand; not for strict FIPS/OS RNG compliance.
Deterministic fixturesadapters.DeterministicSource, randutil.DeterministicRootTesting 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) // 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"})

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

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(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 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

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

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

Choose a generator

Use caseAPINotes
Secure default utilitiesrandutil.Default() or package functionsUses crypto/rand.Reader.
Inject a source or RNGrandutil.New(src), package New functionsUse for tests, fixtures, wrappers, and custom sources.
Named derived streamsrandutil.NewWorkspace(root)Domain-separates labels from a shared root.
One derived streamrandutil.Derive(seed, label)Requires high-entropy secret seeds for security-sensitive use.
Fast CSPRNG streamrandutil.Fast()Seeded from crypto/rand; not for strict FIPS/OS RNG compliance.
Deterministic fixturesadapters.DeterministicSource, randutil.DeterministicRootTesting 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) // 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"})

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

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(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 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

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

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

Choose a generator

Use caseAPINotes
Secure default utilitiesrandutil.Default() or package functionsUses crypto/rand.Reader.
Inject a source or RNGrandutil.New(src), package New functionsUse for tests, fixtures, wrappers, and custom sources.
Named derived streamsrandutil.NewWorkspace(root)Domain-separates labels from a shared root.
One derived streamrandutil.Derive(seed, label)Requires high-entropy secret seeds for security-sensitive use.
Fast CSPRNG streamrandutil.Fast()Seeded from crypto/rand; not for strict FIPS/OS RNG compliance.
Deterministic fixturesadapters.DeterministicSource, randutil.DeterministicRootTesting 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) // 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"})

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

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(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 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

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

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

Choose a generator

Use caseAPINotes
Secure default utilitiesrandutil.Default() or package functionsUses crypto/rand.Reader.
Inject a source or RNGrandutil.New(src), package New functionsUse for tests, fixtures, wrappers, and custom sources.
Named derived streamsrandutil.NewWorkspace(root)Domain-separates labels from a shared root.
One derived streamrandutil.Derive(seed, label)Requires high-entropy secret seeds for security-sensitive use.
Fast CSPRNG streamrandutil.Fast()Seeded from crypto/rand; not for strict FIPS/OS RNG compliance.
Deterministic fixturesadapters.DeterministicSource, randutil.DeterministicRootTesting 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) // 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"})

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

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(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 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

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

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

Choose a generator

Use caseAPINotes
Secure default utilitiesrandutil.Default() or package functionsUses crypto/rand.Reader.
Inject a source or RNGrandutil.New(src), package New functionsUse for tests, fixtures, wrappers, and custom sources.
Named derived streamsrandutil.NewWorkspace(root)Domain-separates labels from a shared root.
One derived streamrandutil.Derive(seed, label)Requires high-entropy secret seeds for security-sensitive use.
Fast CSPRNG streamrandutil.Fast()Seeded from crypto/rand; not for strict FIPS/OS RNG compliance.
Deterministic fixturesadapters.DeterministicSource, randutil.DeterministicRootTesting 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) // 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"})

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

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(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 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

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

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

Choose a generator

Use caseAPINotes
Secure default utilitiesrandutil.Default() or package functionsUses crypto/rand.Reader.
Inject a source or RNGrandutil.New(src), package New functionsUse for tests, fixtures, wrappers, and custom sources.
Named derived streamsrandutil.NewWorkspace(root)Domain-separates labels from a shared root.
One derived streamrandutil.Derive(seed, label)Requires high-entropy secret seeds for security-sensitive use.
Fast CSPRNG streamrandutil.Fast()Seeded from crypto/rand; not for strict FIPS/OS RNG compliance.
Deterministic fixturesadapters.DeterministicSource, randutil.DeterministicRootTesting 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) // 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"})

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

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(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 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

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

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

Choose a generator

Use caseAPINotes
Secure default utilitiesrandutil.Default() or package functionsUses crypto/rand.Reader.
Inject a source or RNGrandutil.New(src), package New functionsUse for tests, fixtures, wrappers, and custom sources.
Named derived streamsrandutil.NewWorkspace(root)Domain-separates labels from a shared root.
One derived streamrandutil.Derive(seed, label)Requires high-entropy secret seeds for security-sensitive use.
Fast CSPRNG streamrandutil.Fast()Seeded from crypto/rand; not for strict FIPS/OS RNG compliance.
Deterministic fixturesadapters.DeterministicSource, randutil.DeterministicRootTesting 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) // 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"})

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

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(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 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

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages