Cryptographically weak random generation

Cryptographically weak random generation

Description

Using math/rand for tokens, session IDs or password-reset codes does not provide the randomness guarantees needed for unpredictable security values. Depending on the algorithm and seed, observed values or guessed seeds may reveal future output. Use crypto/rand for security-sensitive randomness.

Potential impact

  • Predictable tokens or session IDs may enable account or session takeover.
  • Guessed reset or verification codes may bypass authentication.
  • Incorrect generation of cryptographic parameters that require unpredictability can weaken confidentiality and integrity.

Remediation

  • Generate security-sensitive randomness with crypto/rand; also satisfy the algorithm's length and uniqueness requirements for IVs and nonces.
  • Use math/rand only where predictability does not affect security, such as statistical sampling.
  • Give reset tokens sufficient entropy, as in the example, and combine this with expiry, single-use handling and attempt limits.
  • Use shared generation functions and review random length and encoding. Do not log real tokens.

Examples

The time-based seeding explanation applies to older behavior where Seed takes effect. Starting in Go 1.24, package-level math/rand.Seed does nothing by default. This change does not make math/rand a security API. fmt.Println is for demonstrating the result; do not print tokens in production logs.

Before

go
package main

import (
    "fmt"
    mrand "math/rand"
    "time"
)

// Weak reset-token generation using math/rand
func generateResetTokenWeak() string {
    // Legacy behavior: guessable time seed (a no-op by default since Go 1.24)
    mrand.Seed(time.Now().UnixNano())

    letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
    b := make([]rune, 16) // 16-character token
    for i := range b {
        b[i] = letters[mrand.Intn(len(letters))]
    }
    return string(b)
}

func main() {
    token := generateResetTokenWeak()
    fmt.Println("reset token:", token)
}

After

go
package main

import (
    "crypto/rand"
    "encoding/hex"
    "fmt"
)

// Reset-token generation using crypto/rand
func generateResetTokenSecure() (string, error) {
    // 16 random bytes (128 bits) become 32 hexadecimal characters
    b := make([]byte, 16)

    // crypto/rand.Read generates cryptographically secure random bytes
    if _, err := rand.Read(b); err != nil {
        return "", err
    }

    // Represent the bytes as a hexadecimal string
    return hex.EncodeToString(b), nil
}

func main() {
    token, err := generateResetTokenSecure()
    if err != nil {
        panic(err)
    }
    fmt.Println("reset token:", token)
}

Explanation:

  • Before: A nonsecurity random API generates a reset token. In environments that apply the time-based seed, a guessable seed adds risk.
  • After: crypto/rand generates 16 random bytes, represented as 32 hexadecimal characters. Encoding does not increase entropy; storage, expiry and validation on use require separate implementation.

References