0 day streak
go / expert
Snippet

Generating Cryptographically Secure Session IDs

math/rand is a deterministic PRNG seeded from a global source; given a handful of outputs an attacker can recover the seed and predict every future token. crypto/rand draws from the operating system's CSPRNG — getrandom on Linux, RtlGenRandom on Windows, /dev/urandom on macOS — and surfaces read errors instead of silently downgrading. Encoding the raw bytes with base64.RawURLEncoding yields a compact, URL-safe string with no padding. The 16-byte minimum guards against birthday collisions across a realistic session population; 32 bytes is the common production choice.

snippet.go
go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import (
"crypto/rand"
"encoding/base64"
"fmt"
)
func newToken(nBytes int) (string, error) {
if nBytes < 16 {
return "", fmt.Errorf("need >=16 bytes, got %d", nBytes)
}
b := make([]byte, nBytes)
if _, err := rand.Read(b); err != nil {
return "", fmt.Errorf("crypto/rand: %w", err)
}
return base64.RawURLEncoding.EncodeToString(b), nil
}
func main() {
tok, err := newToken(32)
if err != nil {
panic(err)
}
fmt.Println(tok)
}
Breakdown
1
if nBytes < 16 {
Reject parameters that would produce weak tokens. Validating at the API boundary is cheaper than auditing every call site later.
2
if _, err := rand.Read(b); err != nil {
crypto/rand.Read can fail — e.g. when the kernel entropy pool is unavailable in a locked-down container. Never ignore this error.
3
return "", fmt.Errorf("crypto/rand: %w", err)
Wrap with %w so callers can still match the underlying io error via errors.Is while keeping the security-relevant prefix.
4
base64.RawURLEncoding.EncodeToString(b)
RawURLEncoding uses - and _ instead of + and / and drops = padding — safe inside cookies, query strings, and path segments.