bcrypt 是不可逆哈希,明文密码需转为不超过72字节的[]byte,用generatefrompassword生成60字节ascii哈希字符串存varchar(255),comparehashandpassword前须trimspace并校验空密码,错误需区分errmismatch与其他异常。

别用“加密”这个词,bcrypt 是不可逆哈希,不是加密。直接上 bcrypt.GenerateFromPassword 和 bcrypt.CompareHashAndPassword 就对了,但参数、类型、长度、存储方式错一个,登录就挂。
明文密码传进去前必须是 []byte,且长度不能超 72 字节
Go 的 bcrypt.GenerateFromPassword 第一个参数强制要求 []byte,传 string 会编译报错;更隐蔽的坑是:哪怕你转成了 []byte,只要原始字符串 UTF-8 编码后字节数 > 72,超出部分会被静默丢弃。
- “hello世界123456789012345678901234567890123456789012345678901234567890” 和它截断成前 72 字节的结果,哈希值完全一样
- 实操建议:注册/登录入口加校验 ——
if len([]byte(pwd)) 72 { return errors.New("password length must be 8–72 bytes") } - 别依赖前端限制,服务端必须重校;中文、emoji 都算字节,不是字符数
bcrypt.DefaultCost 是当前安全与性能的平衡点,别硬写数字
截至 2026 年,bcrypt.DefaultCost 值为 12,对应单次哈希耗时约 150–250ms(主流云服务器),既防暴力破解,又不拖垮登录接口。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 设成 10:现代 GPU 几秒跑千万次尝试,不够安全
- 设成 14:单次可能超 1s,高并发下 HTTP P95 延迟飙升,容易触发超时或 goroutine 积压
- 测试环境用 4 加速?可以,但必须和生产配置隔离——否则压测看不出真实延迟,上线才爆雷
- 成本值必须范围校验:
if cost 31 { return errors.New("invalid bcrypt cost") }
哈希值存数据库必须用 string(hashed),字段至少 VARCHAR(60)
bcrypt.GenerateFromPassword 返回的是 []byte,但内容是 ASCII 文本(形如 $2b$12$...),长度固定 60 字节。直接存 []byte 二进制到数据库,读出来大概率乱码或 decode 失败。
- 正确做法:
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 的参数顺序和错误处理不能错
这个函数是恒定时间比对,防时序攻击,但用错了等于白搭。最常见错误是参数顺序反了、没判空、或把错误当成功。
- 第一个参数是数据库里读出来的哈希字符串(转成
[]byte),第二个才是用户提交的明文密码[]byte;顺序反了会 panic:panic: hashedPassword is not the hash of a password - 它不校验空密码——
CompareHashAndPassword(hashed, []byte(""))在某些旧实现里可能意外返回 nil,必须前置判空:if len(pwd) == 0 { return errors.New("empty password") } - 返回
err != nil不代表“密码错”,也可能是哈希格式非法(比如开头不是$2a$或$2b$);要显式区分:errors.Is(err, bcrypt.ErrMismatch)才是密码不匹配 - 别在日志里打印明文密码,尤其含中文时,UTF-8 编码混乱+日志系统自动转义=定位困难
真正麻烦的从来不是调哪个函数,而是哈希值在传输、序列化、入库、读出、trim、再传参这一整条链路上,任何一环多一个空格、少一个字节、错一次类型转换,都会让 CompareHashAndPassword 安静地返回 mismatch——而你得顺着整个数据流手动 print 每一步的 len() 和前 10 个字节才能揪出来。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










