必须用 bcrypt.GenerateFromPassword 和 CompareHashAndPassword,明文需转 []byte 且 ≤72 字节,哈希存为 string,cost 设 12 或 13,校验前须判空、trim、验长度和格式。

别用 MD5 加盐,也别自己拼 salt + hash;直接上 bcrypt.GenerateFromPassword 和 bcrypt.CompareHashAndPassword,但参数类型、字节长度、存储格式错一个,密码校验就静默失败。
明文密码必须转 []byte 且 ≤72 字节
Go 的 bcrypt 不接受 string 类型明文,传 string 会编译报错;更危险的是:UTF-8 编码后超 72 字节的部分会被静默截断——"你好世界?123" 和它前 72 字节的哈希完全一样。
- 注册/登录入口必须校验:
if len([]byte(pwd)) == 0 || len([]byte(pwd)) > 72,返回明确错误(如 "密码长度非法") - 别用
utf8.RuneCountInString(pwd)判长度,它数的是 rune,不是字节;中文、emoji、全角符号都按字节算 - 前端限制可被绕过,服务端校验不可省;建议同时限制前端输入 8–64 字符(兼顾用户体验与安全)
bcrypt.GenerateFromPassword 的 cost 参数怎么设才不翻车
cost 决定哈希耗时和爆破难度,不是越高越好。截至 2026 年 7 月,bcrypt.DefaultCost 是 12,单次哈希约 40–60ms(主流云服务器),平衡了安全与延迟。
- 生产环境统一用 12 或 13,通过配置项注入(如
auth.bcrypt_cost=12),禁止硬编码数字 - 配置读出后必须校验:
if cost bcrypt.MaxCost,否则 panic - 测试环境可用 4 加速,但必须和生产配置隔离——否则压测看不出真实延迟
- 别用
time.Now().Unix()或用户名当 cost,它必须是常量整数
哈希值存数据库前必须转 string,字段至少 VARCHAR(255)
bcrypt.GenerateFromPassword 返回的是 []byte,内容是 ASCII 文本(如 b$...),固定约 60 字节。直接存二进制到数据库,读出来大概率乱码或 decode 失败。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 正确写法:
hashedStr := string(hashed),再存入数据库 - MySQL 字段类型必须是
VARCHAR(255)或TEXT;VARCHAR(60)是底线,但留余量更稳妥(旧哈希可能略长) - PostgreSQL 推荐用
TEXT,避免长度限制引发的隐性截断 - 哈希字符串本身是自包含结构(含版本、cost、salt、密文),不能只存后半段,也不能把明文当哈希传给
CompareHashAndPassword
bcrypt.CompareHashAndPassword 验证前必须做三件事
这个函数只比对,不帮你做任何业务判断。传入非法哈希、空字符串、或未 trim 的值,它要么 panic,要么直接返回 crypto/bcrypt: hashedPassword is not the hash of the given password。
- 查用户后先判空:
if user.Password == "",直接拒绝,不进比对流程 - 从 DB 读出后立刻
strings.TrimSpace()——空格、BOM、换行都会导致校验失败 - 调用前检查长度:
if len(user.Password) != 60,说明入库或读取环节已损坏,立刻查源头 - 参数顺序不能颠倒:第一个是数据库里读出的完整哈希字符串转成的
[]byte,第二个才是用户输入的明文[]byte
最易被忽略的是哈希字符串的格式校验——它必须以 $2a$、$2b$ 或 $2y$ 开头,且总长为 60。迁移旧数据时若发现字段存的是明文,必须走重哈希流程,不能“兼容”。

















