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 mainimport ("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.