feat: 添加 Go 内存分配示例并整理笔记
拆分 Go 第三方库笔记并更新索引。 新增 Gitleaks 使用说明,清理过时笔记。
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
# 官方扩展库 `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。
|
||||
Reference in New Issue
Block a user