bcrypt加密

Go 中 bcrypt 加密详细讲解

bcrypt 是一种自适应密码哈希算法,由 Niels Provos 和 David Mazières 设计,用于安全存储密码。它基于 Blowfish 加密算法,通过增加计算成本(work factor)来抵抗暴力破解攻击。在 Go 语言中,bcrypt 实现位于 golang.org/x/crypto/bcrypt 包中,这是标准库的扩展包。 它不是对称/非对称加密,而是单向哈希函数,旨在使密码存储安全,无法逆向解密。

1. 安装 bcrypt 包

在 Go 项目中,通过以下命令安装:

1
go get golang.org/x/crypto/bcrypt

导入包:

1
import "golang.org/x/crypto/bcrypt"

这是一个纯 Go 实现,无需外部依赖。

2. 核心函数和用法

bcrypt 包提供了两个主要函数:GenerateFromPassword 用于生成哈希,CompareHashAndPassword 用于验证。哈希格式为 $2a$cost$salt$hash,其中包含版本、成本、盐和哈希值。

生成哈希(GenerateFromPassword)

  • 参数
    • password []byte:明文密码(字节切片)。密码长度限制在 72 字节以内,否则返回 ErrPasswordTooLong
    • cost int:计算成本(work factor),范围 4(MinCost)到 31(MaxCost)。默认值为 10(DefaultCost)。成本越高,哈希越慢(指数级增长),推荐根据硬件调整(例如,生产中用 12-14)。
  • 返回值
    • []byte:生成的哈希。
    • error:可能的错误如 ErrPasswordTooLong 或无效成本。
  • 内部机制:自动生成随机盐(22 字符),结合密码进行多次迭代哈希。盐防止彩虹表攻击,成本抵抗暴力破解。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"golang.org/x/crypto/bcrypt"
)

func main() {
password := []byte("my_secure_password")
cost := 12 // 推荐生产成本

hashed, err := bcrypt.GenerateFromPassword(password, cost)
if err != nil {
fmt.Println("Error generating hash:", err)
return
}
fmt.Printf("Hashed password: %s\n", hashed) // 输出如 $2a$12$...
}
  • 生成的哈希每次不同(因随机盐),但验证时一致。

验证哈希(CompareHashAndPassword)

  • 参数
    • hashedPassword []byte:存储的哈希。
    • password []byte:用户输入的明文密码。
  • 返回值
    • error:如果匹配,返回 nil;否则返回 ErrMismatchedHashAndPassword 或其他(如 ErrHashTooShort 如果哈希太短)。
  • 机制:从哈希中提取盐和成本,重新哈希输入密码并比较。时间恒定,防止时序攻击。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"golang.org/x/crypto/bcrypt"
)

func main() {
storedHash := []byte("$2a$12$examplehashedvalue...") // 从数据库获取
inputPassword := []byte("my_secure_password")

err := bcrypt.CompareHashAndPassword(storedHash, inputPassword)
if err != nil {
fmt.Println("Password does not match:", err)
return
}
fmt.Println("Password matches!")
}
  • 注意:始终处理错误,不要用字符串比较哈希。

其他辅助函数

  • Cost(hashedPassword []byte) (int, error):返回哈希的成本,用于检查是否需要升级哈希(例如,当硬件升级时,提高成本重新哈希旧密码)。
  • 错误类型:包括 ErrHashTooShortErrMismatchedHashAndPasswordErrPasswordTooLong 等。始终检查这些错误以处理无效输入。

3. 成本因子(Cost Factor)和性能

  • 成本决定了哈希的迭代次数(2^cost 次)。例如:
    • Cost 4:快速(测试用)。
    • Cost 10:默认,适合大多数应用(~100ms)。
    • Cost 12-14:生产推荐,平衡安全和性能。
    • Cost > 20:非常慢,适合高安全需求,但可能导致 DoS 风险。
  • 调整建议:基准测试你的硬件,确保哈希时间在 100-500ms。过低易被破解,过高影响用户体验。
  • 升级策略:在验证时检查成本,如果低于当前标准,重新哈希并存储。

4. 安全考虑

  • 优势:自适应(成本可调)、内置盐、抵抗 GPU/ASIC 破解(内存密集)。
  • 限制:密码 > 72 字节不支持(截断或预哈希);不适合通用数据加密,只用于密码。
  • 最佳实践
    • 从不存储明文密码。
    • 使用 HTTPS 传输密码。
    • 结合其他安全措施如 2FA。
    • 定期审计:监控暴力攻击,使用 rate limiting。
    • 避免自定义实现,使用标准包。

5. 项目中的实际应用

在 Web 项目(如使用 Gin 或 Echo)中:

  • 注册:生成哈希存储到数据库(GORM 或 sqlx)。
  • 登录:从 DB 获取哈希,验证输入。
    示例集成(假设 GORM):
1
2
3
4
5
6
7
8
// 注册
user.Password = string(hashed) // hashed from GenerateFromPassword
db.Create(&user)

// 登录
var storedUser User
db.Where("email = ?", email).First(&storedUser)
err := bcrypt.CompareHashAndPassword([]byte(storedUser.Password), []byte(inputPassword))

与 base64 或其他加密/哈希的比较

bcrypt 是密码哈希函数,不是加密或编码。以下比较 base64 和其他常见方法在 Go 中的使用、安全性和适用性。Go 标准库提供 cryptoencoding/base64 包。

1. bcrypt vs Base64

  • Base64

    • 类型:编码(Encoding),不是加密或哈希。将二进制数据转换为 ASCII 字符串(A-Z、a-z、0-9、+/=)。

    • Go 实现encoding/base64 包。

    • 用法

      1
      2
      3
      import "encoding/base64"
      encoded := base64.StdEncoding.EncodeToString([]byte("password")) // 输出 cGFzc3dvcmQ=
      decoded, _ := base64.StdEncoding.DecodeString(encoded) // 恢复原数据
    • 安全:零安全。可逆转,无保护。用于传输(如 JWT),不是存储密码。

    • 比较:bcrypt 是单向、慢速哈希,设计用于密码;base64 是快速、可逆编码。base64 不提供任何保密,仅改变表示形式。

    • 场景:base64 用于数据传输/存储(如图像);bcrypt 只用于密码哈希。不要用 base64 存储密码,即使结合哈希(bcrypt 已内置 base64 变体编码盐/哈希)。

2. bcrypt vs 其他哈希/加密

  • **MD5/SHA-1/SHA-256 (crypto/md5, crypto/sha1, crypto/sha256)**:

    • 类型:快速消息摘要哈希。

    • 用法

      1
      2
      3
      4
      5
      6
      import (
      "crypto/sha256"
      "fmt"
      )
      hash := sha256.Sum256([]byte("password"))
      fmt.Printf("%x\n", hash) // 输出 32 字节十六进制
    • 安全:快速,易暴力破解;无内置盐。MD5/SHA-1 已废弃(碰撞攻击);SHA-256 更好但不适合密码(太快)。

    • 比较:bcrypt 慢速、自适应、带盐;SHA 等快速,用于校验文件/数据完整性,不是密码。bcrypt 更安全,但计算开销大。

    • 场景:SHA 用于签名/校验;bcrypt 用于密码。

  • **Argon2 (golang.org/x/crypto/argon2)**:

    • 类型:内存硬化哈希,OWASP 推荐。

    • 用法:类似 bcrypt,但参数包括时间、内存、线程。

      1
      2
      3
      import "golang.org/x/crypto/argon2"
      salt := []byte("somesalt")
      hash := argon2.IDKey([]byte("password"), salt, 1, 64*1024, 4, 32)
    • 安全:抵抗侧信道攻击,内存/CPU 可调。更现代,比 bcrypt 灵活。

    • 比较:Argon2 更可配置(内存硬化防 GPU 攻击);bcrypt 简单、成熟。Argon2 可能取代 bcrypt,但 bcrypt 仍广泛使用。

    • 场景:高安全需求用 Argon2;简单应用用 bcrypt。

  • **scrypt (golang.org/x/crypto/scrypt)**:

    • 类型:内存硬化密钥派生函数。

    • 用法

      1
      2
      3
      import "golang.org/x/crypto/scrypt"
      salt := []byte("somesalt")
      dk, _ := scrypt.Key([]byte("password"), salt, 16384, 8, 1, 32)
    • 安全:类似 Argon2,防暴力。

    • 比较:scrypt 内存密集;bcrypt CPU 密集。scrypt 更灵活,但参数复杂。

    • 场景:替代 bcrypt 时使用。

  • **AES/RSA 等加密 (crypto/aes, crypto/rsa)**:

    • 类型:对称/非对称加密,可逆。

    • 用法:AES 用于数据加密。

      1
      2
      import "crypto/aes"
      // 复杂,需要密钥、IV 等
    • 安全:可解密;用于数据保护,不是密码存储。

    • 比较:加密是双向;bcrypt/哈希单向。密码应哈希,不加密(OWASP 推荐)。

    • 场景:AES 用于文件加密;bcrypt 用于认证。

总结比较:bcrypt 是密码哈希首选(安全、易用);base64 仅编码;快速哈希如 MD5 不安全;Argon2/scrypt 是现代替代。选择取决于需求:密码用慢哈希,数据用加密,传输用编码。

[up主专用,视频内嵌代码贴在这]