Golang中Bcrypt包间歇性问题排查指南

Golang中Bcrypt包间歇性问题排查指南 使用BCrypt包进行密码加密和验证。一切工作正常,但在某些情况下,加密无法正常工作。下面将详细说明。

密码 = Password@123

当我尝试哈希并存储到数据库时,它以两种方式存储,如下所述。

  1. 按预期存储 = $2a$12$<>
  2. 按原样存储密码 = $2a$12Password@123<>

有人能帮我解决这个问题吗?提前感谢。

2 回复

我们可能需要查看您正在运行的导致此问题的代码片段。

更多关于Golang中Bcrypt包间歇性问题排查指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


根据你的描述,问题很可能出现在密码字符串的处理过程中。当BCrypt返回的哈希值包含原始密码时,通常意味着输入给GenerateFromPassword函数的密码参数包含了不应有的前缀。

以下是问题分析和解决方案:

问题分析

BCrypt哈希通常以$2a$开头,后面跟着cost参数和随机salt。当哈希结果中出现原始密码时,说明传递给函数的密码参数已经包含了$2a$前缀。

常见原因和示例代码

1. 密码字符串被意外修改

package main

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

func main() {
    password := "Password@123"
    
    // 错误示例:密码被意外拼接
    var storedHash string
    // 模拟从某处获取了不完整的前缀
    prefix := "$2a$12$"
    wrongPassword := prefix + password  // 错误:密码变成了 "$2a$12$Password@123"
    
    // 使用错误的密码进行哈希
    wrongHash, err := bcrypt.GenerateFromPassword([]byte(wrongPassword), 12)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    
    storedHash = string(wrongHash)
    fmt.Printf("错误的哈希值: %s\n", storedHash)
    // 输出可能包含: $2a$12$...Password@123...
    
    // 正确示例
    correctHash, err := bcrypt.GenerateFromPassword([]byte(password), 12)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    
    fmt.Printf("正确的哈希值: %s\n", string(correctHash))
}

2. 数据库或缓存层的问题

// 检查数据库存储逻辑
func savePasswordToDB(userID string, password string) error {
    hash, err := bcrypt.GenerateFromPassword([]byte(password), 12)
    if err != nil {
        return err
    }
    
    // 错误示例:存储前错误地拼接了字符串
    // wrongHash := "$2a$12$" + string(hash) // 不要这样做!
    
    // 正确示例:直接存储hash
    err = db.Exec("UPDATE users SET password_hash = ? WHERE id = ?", 
        string(hash), userID)
    return err
}

3. 密码验证时的常见错误

func verifyPassword(hashedPassword, password string) bool {
    // 错误示例:在验证前修改了hashedPassword
    // if !strings.HasPrefix(hashedPassword, "$2a$") {
    //     hashedPassword = "$2a$12$" + hashedPassword // 这会导致问题
    // }
    
    // 正确示例:直接比较
    err := bcrypt.CompareHashAndPassword(
        []byte(hashedPassword), 
        []byte(password),
    )
    return err == nil
}

排查步骤

  1. 添加调试日志
func hashPassword(password string) (string, error) {
    fmt.Printf("原始密码长度: %d\n", len(password))
    fmt.Printf("原始密码内容: %q\n", password)
    fmt.Printf("前10个字符: %q\n", password[:min(10, len(password))])
    
    hash, err := bcrypt.GenerateFromPassword([]byte(password), 12)
    if err != nil {
        return "", err
    }
    
    result := string(hash)
    fmt.Printf("生成的哈希长度: %d\n", len(result))
    fmt.Printf("生成的哈希前缀: %q\n", result[:min(20, len(result))])
    
    return result, nil
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}
  1. 检查输入源
// 确保从请求中获取密码时没有额外的处理
func getPasswordFromRequest(r *http.Request) (string, error) {
    password := r.FormValue("password")
    
    // 清理可能的空白字符
    password = strings.TrimSpace(password)
    
    // 但不要添加或修改前缀
    // 不要做这样的操作:
    // if strings.HasPrefix(password, "$2a$") {
    //     password = password[7:] // 错误!
    // }
    
    return password, nil
}

解决方案

在你的代码中搜索以下模式:

  • 字符串拼接操作(+ 运算符)
  • strings.Replacestrings.TrimPrefix 调用
  • 任何修改密码字符串的逻辑
  • 数据库存储前的字符串处理

问题通常出现在密码被哈希之前,某个地方错误地添加了$2a$12$前缀到原始密码上。检查所有处理密码字符串的代码路径,确保密码在传递给GenerateFromPassword之前没有被修改。

回到顶部