193 lines
3.1 KiB
Markdown
193 lines
3.1 KiB
Markdown
# 官方扩展库 `bcrypt`
|
||
|
||
## `bcrypt` 是什么
|
||
|
||
`bcrypt` 是专门用于**密码哈希**的算法,不是可逆加密。
|
||
|
||
```text
|
||
password
|
||
↓
|
||
bcrypt
|
||
↓
|
||
hash
|
||
```
|
||
|
||
### 核心原则
|
||
|
||
```text
|
||
数据库只存 hash
|
||
bcrypt 自动处理 salt
|
||
使用 CompareHashAndPassword 验证
|
||
cost 根据服务器性能决定
|
||
不要自己用 MD5 / SHA256 存密码
|
||
```
|
||
|
||
------
|
||
|
||
### cost
|
||
|
||
`cost` 是 bcrypt 的计算难度。
|
||
|
||
生产环境通常可以从 `10 ~ 12` 开始测试,具体值应根据服务器性能决定。单次 bcrypt 计算控制在大约 `100ms ~ 300ms` 是比较常见的工程范围。
|
||
|
||
`cost` 每增加 1,计算量大约接近翻倍。
|
||
|
||
cost 越高:
|
||
|
||
- 计算越慢
|
||
- 暴力破解成本越高
|
||
- 服务器 CPU 消耗也越高
|
||
|
||
bcrypt 生成的 hash 本身会记录当时使用的 `cost`。
|
||
|
||
因此,即使生产环境将当前配置从:
|
||
|
||
```text
|
||
cost = 10
|
||
```
|
||
|
||
升级为:
|
||
|
||
```text
|
||
cost = 12
|
||
```
|
||
|
||
数据库中原本使用 `cost = 10` 生成的旧 hash,仍然可以通过:
|
||
|
||
```go
|
||
bcrypt.CompareHashAndPassword(...)
|
||
```
|
||
|
||
正常验证。
|
||
|
||
`CompareHashAndPassword` 会根据已有 hash 中记录的信息进行密码验证,不依赖当前系统配置的 cost。
|
||
|
||
登录成功后,可以通过 `bcrypt.Cost()` 获取旧 hash 使用的 cost。如果低于当前配置,可以重新生成 hash 并更新数据库。
|
||
|
||
------
|
||
|
||
### 72-byte 限制
|
||
|
||
bcrypt 密码输入最多支持约 `72 bytes`。
|
||
|
||
如果只允许 ASCII:
|
||
|
||
```text
|
||
72 bytes ≈ 72 个字符
|
||
```
|
||
|
||
通常已经足够。
|
||
|
||
建议业务层主动限制:
|
||
|
||
```go
|
||
if len([]byte(password)) > 72 {
|
||
return errors.New("password too long")
|
||
}
|
||
```
|
||
|
||
------
|
||
|
||
## 基础使用
|
||
|
||
### 依赖导入
|
||
|
||
```go
|
||
import "golang.org/x/crypto/bcrypt"
|
||
```
|
||
|
||
`golang.org/x/crypto/bcrypt` 属于 Go 官方维护的扩展库,但不属于 Go 标准库。
|
||
|
||
------
|
||
|
||
### 生成 hash
|
||
|
||
```go
|
||
hash, err := bcrypt.GenerateFromPassword(
|
||
[]byte(password),
|
||
12,
|
||
)
|
||
```
|
||
|
||
生成的 hash 类似:
|
||
|
||
```text
|
||
$2a$12$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||
```
|
||
|
||
其中大致包含:
|
||
|
||
```text
|
||
$2a$ → bcrypt 版本
|
||
12$ → cost
|
||
后续内容 → 编码后的 salt + hash
|
||
```
|
||
|
||
bcrypt 会自动生成随机 salt,因此不需要自己生成或额外保存 salt。
|
||
|
||
------
|
||
|
||
### 验证密码
|
||
|
||
```go
|
||
err := bcrypt.CompareHashAndPassword(
|
||
[]byte(passwordHash),
|
||
[]byte(password),
|
||
)
|
||
|
||
if err == nil {
|
||
// 密码正确
|
||
}
|
||
```
|
||
|
||
------
|
||
|
||
### 判断密码是否相同
|
||
|
||
不能直接比较两个 bcrypt hash,因为 bcrypt 每次都会生成新的随机 salt。
|
||
|
||
即使密码完全相同:
|
||
|
||
```text
|
||
hash1 != hash2
|
||
```
|
||
|
||
判断某个密码是否和旧密码相同时,应使用:
|
||
|
||
```go
|
||
err := bcrypt.CompareHashAndPassword(
|
||
[]byte(oldHash),
|
||
[]byte(password),
|
||
)
|
||
|
||
if err == nil {
|
||
// password 与 oldHash 对应的密码相同
|
||
}
|
||
```
|
||
|
||
------
|
||
|
||
### 获取 hash 的 cost
|
||
|
||
```go
|
||
cost, err := bcrypt.Cost(
|
||
[]byte(user.PasswordHash),
|
||
)
|
||
```
|
||
|
||
通常用于**用户登录成功后**,判断当前密码 hash 是否使用了旧的 cost。
|
||
|
||
例如:
|
||
|
||
```text
|
||
数据库 hash cost = 10
|
||
当前系统 cost = 12
|
||
↓
|
||
登录成功
|
||
↓
|
||
重新生成 cost = 12 的 hash
|
||
↓
|
||
更新数据库
|
||
```
|
||
|
||
这样可以在用户正常登录过程中逐步升级旧密码 hash。 |