应直接使用 bcrypt.GenerateFromPassword 和 bcrypt.CompareHashAndPassword 处理密码,明文需转 []byte 且长度≤72字节,cost 推荐 bcrypt.DefaultCost(当前为12),哈希值存取须用 string() 转换并校验长度为60、前缀为 $2b$。

bcrypt.GenerateFromPassword 和 bcrypt.CompareHashAndPassword,别碰 AES、SHA256 或手写加盐逻辑。bcrypt 是密码哈希,不是加密——它不可逆,专为存密码设计;用错类型或参数,登录就静默失败。
明文密码传进去前必须转 []byte 且 ≤ 72 字节
Go 的 bcrypt.GenerateFromPassword 第一个参数强制要求 []byte,传 string 直接编译报错。更隐蔽的问题是:UTF-8 编码后超 72 字节的部分会被静默丢弃,导致 "hello世界123..." 和它截断后的哈希值完全一样。
- 注册/登录入口必须校验:
if len([]byte(pwd)) 72 { return errors.New("password length must be 8–72 bytes") } - 中文、emoji、全角符号都算字节,不是字符数;前端限制可被绕过,服务端校验不可省
- 别依赖
utf8.RuneCountInString判长度——它数的是 rune,不是字节
cost 参数别硬写数字,优先用 bcrypt.DefaultCost
截至 2026 年 4 月,bcrypt.DefaultCost 值为 12,对应单次哈希耗时约 150–250ms(主流云服务器),平衡了暴力破解成本与接口延迟。设成 10 太弱,GPU 几秒跑千万次;设成 14+ 可能单次超 1s,高并发下 goroutine 积压、HTTP 超时频发。
- 开发/测试环境用
cost=4加速?可以,但必须和生产配置隔离——否则压测看不出真实延迟 - 从配置读取
cost后,务必做范围校验:if cost 31 { return errors.New("invalid bcrypt cost") } - 迁移旧用户时,若 DB 里存的是
$2a$开头的哈希,需兼容处理,不能直接 panic
哈希值存数据库必须用 string(hashed),字段至少 VARCHAR(255)
bcrypt.GenerateFromPassword 返回的是 []byte,但内容是 ASCII 文本(如 b$...),固定约 60 字节。直接存 []byte 到数据库,读出来大概率乱码或 decode 失败。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 正确做法:
hashedStr := string(hashed),再存进数据库 - MySQL 字段类型用
VARCHAR(255),PostgreSQL 推荐TEXT;VARCHAR(50)会截断,后续所有CompareHashAndPassword都返回bcrypt.ErrMismatch - 从 DB 读出后,先
strings.TrimSpace()再传给比对函数——空格、BOM、换行都会让校验失败 - 调试时直接打长度:
fmt.Println(len(hashStr)),不是 60 就立刻查源头(入库逻辑 or ORM 自动 trim)
CompareHashAndPassword 的调用顺序和前置检查不能漏
这个函数防时序攻击,但不帮你做任何业务逻辑判断。参数顺序反了、传空密码、哈希字段为空,它都可能返回 nil 或错误,但行为不一致。
立即学习“go语言免费学习笔记(深入)”;
- 参数顺序必须是:
bcrypt.CompareHashAndPassword([]byte(storedHash), []byte(inputPwd)),顺序反了会 panic - 调用前显式判空:
if len(inputPwd) == 0 { return errors.New("empty password") } - DB 查询结果为
nil或user.PasswordHash == "",应直接拒绝,别进比对流程 - 错误统一返回泛化提示(如 "invalid credentials"),日志中记录请求 ID + 哈希前缀即可,别打明文密码
CompareHashAndPassword 静默失败。调试时第一反应不该是“算法写错了”,而是先确认 len(hashStr) == 60 且开头是 $2b$。

















