← 中文资源

简体中文版本

加密安全的随机令牌

用 crypto/rand 生成 URL 安全令牌并设置熵下限,再用 crypto/rand.Int 生成无偏的数字验证码。

阅读英文原文

功能说明

会话令牌、重置链接和一次性验证码必须使用密码学安全的随机源。math/rand 的默认序列可被预测,适合模拟和测试,绝不能用于安全场景。crypto/rand 从操作系统熵源读取字节,失败时返回错误而不是退化为弱随机。

本例提供两个函数。RandomToken 生成指定字节数的随机数据,并以无填充 URL 安全 Base64 编码,结果可直接放入链接或请求头。RandomDigits 生成指定长度的十进制验证码,使用 crypto/rand.Int 在零到九之间均匀取值,避免对随机字节取模造成的偏置。两个函数都对参数做下限校验。

关键代码

RandomToken 要求至少十六字节,相当于一百二十八位熵,低于该值直接报错。rand.Read 会填满缓冲区,返回错误时不得使用部分结果。RawURLEncoding 不产生加号、斜杠和等号,省去 URL 转义。RandomDigits 用 math/big 的 Int 生成大整数,上界为十,结果天然落在零到九;若直接用字节值对十取模,二百五十六不能被十整除,低位数字会略多。循环按索引写入,最后转换为字符串。

使用注意

令牌长度应根据威胁模型选择,一百二十八位适合多数场景,高价值操作可使用二百五十六位。生成的令牌必须通过 TLS 传输,并在服务端以哈希形式存储,避免数据库泄露后被直接使用。数字验证码熵很低,必须配合有效期、尝试次数限制和速率限制。crypto/rand 在极端环境下可能阻塞或失败,应返回错误并让请求失败,不能回退到时间种子。不要重复使用令牌或将其写入日志。

验证方式

运行脚本,第一行输出一个约三十二字符的 URL 安全字符串,第二行输出六位数字。多次运行结果应完全不同,且数字验证码允许前导零。把 RandomToken 的参数改为八,应返回熵过低错误;把 RandomDigits 的长度改为零,应返回长度必须为正的错误。

代码示例

package main

import (
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"math/big"
)

func RandomToken(size int) (string, error) {
	if size < 16 {
		return "", fmt.Errorf("token entropy too low: %d bytes", size)
	}
	buffer := make([]byte, size)
	if _, err := rand.Read(buffer); err != nil {
		return "", err
	}
	return base64.RawURLEncoding.EncodeToString(buffer), nil
}

func RandomDigits(length int) (string, error) {
	if length < 1 {
		return "", fmt.Errorf("length must be positive: %d", length)
	}
	limit := big.NewInt(10)
	out := make([]byte, length)
	for index := range out {
		digit, err := rand.Int(rand.Reader, limit)
		if err != nil {
			return "", err
		}
		out[index] = byte('0' + digit.Int64())
	}
	return string(out), nil
}

func main() {
	token, tokenErr := RandomToken(24)
	fmt.Println(token, tokenErr)
	code, codeErr := RandomDigits(6)
	fmt.Println(code, codeErr)
}