跳到主要内容

Password

password 包提供可插拔的密码编码,通过组合编码器同时支持多种算法。

编码器接口

每个算法专用的 encoder 以及组合编码器都实现同一个 Encoder 接口:

type Encoder interface {
Encode(password string) (string, error)
Matches(password, encodedPassword string) bool
UpgradeEncoding(encodedPassword string) bool
}

组合编码器

组合编码器将算法标识作为前缀存储在编码后的密码中,实现无缝的算法迁移:

import "github.com/coldsmirk/vef-framework-go/password"

encoder := password.NewCompositeEncoder(
password.EncoderBcrypt, // 新密码默认使用
map[password.EncoderID]password.Encoder{
password.EncoderBcrypt: password.NewBcryptEncoder(),
password.EncoderArgon2: password.NewArgon2Encoder(),
password.EncoderScrypt: password.NewScryptEncoder(),
password.EncoderSha256: password.NewSha256Encoder(),
},
)

编码

encoded, err := encoder.Encode("my-password")
// → "{bcrypt}$2a$10$..."

匹配

编码器自动从 {前缀} 检测算法:

// 支持所有已注册的算法
ok := encoder.Matches("my-password", "{bcrypt}$2a$10$...") // true
ok = encoder.Matches("my-password", "{argon2}$argon2id$...") // true
ok = encoder.Matches("my-password", "{sha256}abc123...") // true

如果 encoded password 没有 {prefix}CompositeEncoder 会在匹配和升级检查时 fallback 到默认 encoder ID。未知前缀会让 Matches 返回 false

升级检测

检查密码是否需要重新编码(例如从 SHA256 迁移到 bcrypt):

needsUpgrade := encoder.UpgradeEncoding("{sha256}abc123...")
// → true(因为默认是 bcrypt,不是 sha256)

UpgradeEncoding 遇到有效但非默认的前缀时会直接返回 true。默认前缀或无前缀 值会继续委托给默认 encoder 自身的 UpgradeEncoding 逻辑,例如 bcrypt cost 比较。

可用编码器

编码器 ID构造函数安全级别
password.EncoderBcryptpassword.NewBcryptEncoder()⭐⭐⭐⭐ 推荐
password.EncoderArgon2password.NewArgon2Encoder()⭐⭐⭐⭐⭐ 最强
password.EncoderScryptpassword.NewScryptEncoder()⭐⭐⭐⭐ 强
password.EncoderPbkdf2password.NewPbkdf2Encoder()⭐⭐⭐ 标准(FIPS 友好)
password.EncoderSha256password.NewSha256Encoder()⭐⭐ 仅用于遗留系统
password.EncoderMd5password.NewMd5Encoder()⭐ 仅用于遗留 / 互通
password.EncoderPlaintextpassword.NewPlaintextEncoder()⭐ 仅用于测试

公开的 encoder ID 常量是 EncoderBcryptEncoderArgon2EncoderScryptEncoderPbkdf2EncoderMd5EncoderSha256EncoderPlaintext

{prefix} 字符串由 EncoderID 常量值决定:EncoderBcrypt{bcrypt}EncoderArgon2{argon2}EncoderScrypt{scrypt}EncoderPbkdf2{pbkdf2}EncoderMd5{md5}EncoderSha256{sha256}EncoderPlaintext{plaintext}

编码器选项

编码器Option 函数
NewBcryptEncoderWithBcryptCost(cost)
NewArgon2EncoderWithArgon2Memory(memory), WithArgon2Iterations(iterations), WithArgon2Parallelism(parallelism)
NewScryptEncoderWithScryptN(n), WithScryptR(r), WithScryptP(p)
NewPbkdf2EncoderWithPbkdf2Iterations(iterations), WithPbkdf2HashFunction(hashFunction)
NewMd5EncoderWithMd5Salt(salt), WithMd5SaltPosition(position)
NewSha256EncoderWithSha256Salt(salt), WithSha256SaltPosition(position)

对应的 option 类型也公开为 BcryptOptionArgon2OptionScryptOptionPbkdf2OptionMd5OptionSha256Option

公开的 option 函数是 WithBcryptCostWithArgon2MemoryWithArgon2IterationsWithArgon2ParallelismWithScryptNWithScryptRWithScryptPWithPbkdf2IterationsWithPbkdf2HashFunctionWithMd5SaltWithMd5SaltPositionWithSha256SaltWithSha256SaltPosition

默认参数:

Encoder默认值
bcryptbcrypt.DefaultCost10);合法 cost 范围是 4..31
Argon2idmemory 64 * 1024 KiB、iterations 3、parallelism 4
scryptN = 32768r = 8p = 1
PBKDF2310000 次迭代,默认 sha256;也支持 sha512
MD5 / SHA-256默认无 salt;配置 salt 后默认位置是 suffix,也支持 prefix

密码传输加密

password.Encoder 始终是一个纯 KDF,同时用于存储和比对——不存在给 encoder 套一层 cipher 的包装器。如果客户端在发送前先加密了密码(常见的"前端加密、 后端校验"模式),解密发生在更上层的 authenticator:注册一个 security.PasswordDecryptorPasswordAuthenticator 会先把收到的凭据解密为 明文,再拿它与存储的哈希比对。完整配置见 登录加固

密码格式

编码后的密码格式:{算法}编码值;源码中的占位描述写作 {algorithm}encoded_value

{bcrypt}$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
{argon2}$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$...
{sha256}5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8

独立的 MD5 和 SHA-256 encoder 在没有 salt 时返回原始 hex digest。配置 salt 后, 内部 hash 格式是 {algorithm}$salt$hash;如果再由 NewCompositeEncoder 包装, composite 会在这个 encoded value 前面追加外层 {algorithm} 前缀。

错误哨兵

错误出现时机
ErrInvalidCostbcrypt cost 不在 4..31 范围内
ErrInvalidMemoryArgon2 memory 参数过小
ErrInvalidIterationsiteration count 小于 1
ErrInvalidParallelismArgon2 parallelism 小于 1
ErrInvalidEncoderIDCompositeEncoder 找不到前缀对应的 encoder
ErrInvalidHashFormat编码后的密码格式不合法
ErrDefaultEncoderNotFoundCompositeEncoder 没有注册默认 encoder ID