必须用 crypto/rand 生成密码,因其对接系统安全熵源;避免 math/rand 和模偏差;字符集需白名单分类定义;校验强度须逐 rune 遍历并过滤控制符。

用 crypto/rand 生成密码,别碰 math/rand
直接用 math/rand 生成密码等于给攻击者递钥匙——它不依赖系统熵源,输出可预测,哪怕加了 seed 也扛不住重放或逆向。必须用 crypto/rand,它是唯一对接 /dev/urandom(Linux)或 BCryptGenRandom(Windows)的官方安全入口。
常见错误是写成 rand.Intn(len(charset)):这会引入模偏差,小索引字符出现概率略高;正确做法是用 rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) 配合拒绝采样,保证均匀分布。
- 每次调用
crypto/rand.Read必须传已分配的切片,例如buf := make([]byte, 16);传nil会 panic - 返回的
n理论上等于len(buf),但容器环境或低熵设备可能返回io.ErrUnexpectedEOF,err必须检查,不能忽略 - 不要反复对同一块 buffer 调用
Read——单次已满足密码学强度,重复调用无增益,还可能掩盖错误
字符集组合要显式控制,别依赖 ASCII 范围硬编码
用 "!@#$%&*" 这类字符串当特殊字符集看似省事,但实际业务常禁止某些符号(如 # 在 URL 中需转义、| 在 shell 命令里有歧义)。更糟的是,硬写 33–126 ASCII 范围会漏掉 Unicode 字符,而用户粘贴的密码可能含 emoji 或中文标点。
推荐做法:定义白名单字符集,且按类别拆分,方便后续强制要求“每类至少一个”:
lower := "abcdefghijklmnopqrstuvwxyz"upper := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"digit := "0123456789"special := "!@$_-+=" // 显式列出允许的,不含控制符或易混淆字符
拼接时用 strings.Join([]string{lower, upper, digit, special}, ""),避免手误漏字符或重复;若需支持多语言(如中文密码策略),此处可扩展为 rune 切片而非 string。
校验强度必须逐 rune 遍历,正则靠不住
regexp 包在 Go 里不支持正向先行断言,写 ^[A-Z].*[a-z].*[0-9].* 会漏掉 "123Abc!" 这种数字开头的合法密码;而且 \d 只匹配 ASCII 数字,对全角数字(“123”)或阿拉伯数字无效。
正确方式是遍历每个 rune,用 unicode.IsUpper、unicode.IsLower、unicode.IsDigit、unicode.IsPunct 或自定义 isSpecialRune 函数分类统计:
- 先调
strings.TrimSpace清空首尾空格,再用strings.Map(func(r rune) rune { if unicode.IsControl(r) || r == '\u200b' { return -1 }; return r }, pwd)过滤不可见控制符 - 长度检查放最前:
if len([]rune(pwd)) 就直接返回 error,避免后续无意义遍历 - 别用
strings.ContainsAny(pwd, "0123456789")——它只查 byte,对 Unicode 数字失效
返回 error 而非 bool,字段名必须带上
返回 bool 意味着上层无法区分是“缺大写字母”还是“长度不够”,前端也没法精准高亮对应输入框。必须返回 error,且带字段名和具体原因:
- 用
fmt.Errorf("password: missing uppercase letter"),不用errors.New("missing uppercase")——前者可插 trace ID,后者无法结构化 - 若需批量收集(比如注册页同时校验邮箱+密码+确认密码),内部累积
errs []error,最后用errors.Join(errs)(Go 1.20+)合并 - 真实用户粘贴的密码常含零宽空格、BOM、软连字符等不可见字符,本地测试几乎遇不到——过滤逻辑必须上线前实测,不能只靠单元测试跑 ASCII 字符
真正难的不是写对一个函数,而是让过滤、遍历、错误组装、缓存(如 zxcvbn 结果)、配置加载(最小长度/禁用词表)全部解耦又协同。每个环节都可能在灰度时突然暴露——比如某次更新后,用户反馈“输不了带中文标点的密码”,结果发现是 unicode.IsPunct 把中文顿号也判进了特殊字符,而策略本意只允许 ASCII 符号。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











