Golang中密码编码的实现与最佳实践

Golang中密码编码的实现与最佳实践 我正在接手一个多年前其他开发者用PHP编写的项目。我想编写一个能实现相同功能的Go应用程序,但对一些事情感到困惑。

以下是我试图用Go重新实现的PHP代码:

$salt = substr($correctHash, 0, 64); //从数据库当前哈希值的前部获取盐值                                                                                                              
$validHash = substr($correctHash, 64, 64); //SHA256哈希值                                                                                                                                                                                                                                                                                                                                 
$testHash = hash("sha256", $salt . $password); //创建新哈希值与原始值比较

注意:$password是用户发送的需要验证的文本短语,$correctHash是存储在数据库中用于对比的哈希值。

我的第一个问题是:看起来他最初将盐值与密码的哈希值一起存储。他从数据库存储的哈希值前部提取盐值,然后使用前64个字符作为盐值来处理用户刚刚传入的新调用。这是处理盐值的正确方式吗?因为这种方式似乎让盐值失去了意义。

其次,我很难弄清楚如何在Go中使用盐值执行sha256来获取哈希键。有人能给我指点一下如何实现这个的例子吗?

谢谢,

G


更多关于Golang中密码编码的实现与最佳实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

11 回复

运行得非常完美,感谢您的所有帮助

更多关于Golang中密码编码的实现与最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


谢谢,是的,我已经看过了,但没明白如何向该函数传入盐值。

cybercrypt13:

如何进行sha256哈希计算

Go标准库提供了sha256包。

你好,

只是简单提一下,对于密码来说,使用比sha256更强的加密方式也是个好主意,比如bcrypt,它还能自动处理加盐部分。

这里有一个示例:

func main() {
    fmt.Println("hello world")
}

密码哈希示例链接

感谢您的确认,确实如此。我昨天终于理解了盐值的作用点。😊

盐值就是前64个字节,而实际的哈希值位于末尾。因此我们在用户每次登录时将其剥离,以便将新生成的哈希值与密码字段末尾存储的最后65个字节进行比对。

我会尝试您提供的示例,看看能否成功实现。

再次感谢您提供的所有帮助。

谢谢,但我没看到你对盐值的处理方式。你把它传入函数,却只是将它附加到哈希值前面。但哈希值本身是使用盐值创建的。

请注意我示例代码中的第三行,在 PHP 中通过将待哈希的密码与盐值一起传入哈希函数来生成哈希值。

在 PHP 中我们通过以下方式获取原始盐值:

$salt = bin2hex(random_bytes(32));

你好 @cybercrypt13

根据你获取盐值的示例,我猜测盐值是在密码哈希处理后添加在前缀位置的。如果是这种情况,你可以直接使用类似这样的方法:

func main() {
    fmt.Println("hello world")
}

此外,确实,直接将盐值添加到哈希值前面并不是最佳做法。我认为更常见的做法是:在添加盐值后进行哈希计算,然后将原始盐值存储在数据库的某个位置,在进行比较时再获取(至少这是我的理解)。

感谢,我已经在考虑这个方法了,但在采用之前,我需要在登录过程中转换当前的密码哈希值。这很容易实现,但在替换之前,我需要能够根据旧的盐值和哈希值验证用户提供的密码是否正确。

// 验证密码的示例代码
func verifyPassword(oldHash []byte, salt []byte, inputPassword string) bool {
    // 这里实现密码验证逻辑
    // 使用旧盐值对输入密码进行哈希
    // 比较生成的哈希与存储的旧哈希
    return compareHashes(oldHash, newHash)
}

点赞图标 1个点赞

你好 @cybercrypt13

抱歉,我误读了你在示例中原来的第3行!

无论如何,在这种情况下,你不是从数据库中的哈希密码单独检索盐吗?我认为你不能简单地从加盐哈希中截取字符并“重复使用”它来为新哈希加盐以比较值。

$salt = substr($correctHash, 0, 64);

这是我提到的部分。correctHash 包含什么?它是否存储了完整的盐,其中0-64位是盐,65位之后是实际的哈希?如果是这样,那对我来说就更合理了 🙂

例如,在保存密码时进行初始哈希后,你是否以某种方式存储它,像这样。

$salt = bin2hex(random_bytes(32)); // 新盐
$newpass = hash("sha256", $salt . $password); // 创建哈希密码

/* 以某种方式将密码存储在用户中,密码设置为 salt + hash */

如果是这种情况,这里有一个不同的示例来处理这个问题 🙂:

https://play.golang.org/p/WVJJNwEmZl7

现在意识到盐值实际上是经过哈希处理的,这是正确的做法,绝对不是无意义的。

这意味着黑客无法使用彩虹表来匹配所有未加盐的密码,而是必须为每个密码单独获取盐值(因为每个密码都有唯一的盐),基于该盐值为每个密码生成新的彩虹表,然后从该特定的新彩虹表进行比较,这将极其耗时且不可行。因此,即使他们能够破解一个密码,破解数据库中的所有密码也将花费大量时间。

从我在这里找到的一个回答中,解释得可能比我更好(为什么加盐不会使哈希失效?- 密码学 Stack Exchange):

盐值以明文形式存储在哈希旁边,并且在检查密码时使用相同的盐值。

其目的是减缓获取数据库副本的攻击者的速度:

  • 他们必须单独攻击每个密码(他们需要为每个哈希尝试每个输入,而不是尝试每个输入并将输出与所有哈希一次性比较)。
  • 他们无法利用巨大的预计算查找表(“彩虹表”),因为加盐本质上相当于为每个用户提供自己的哈希函数。

无需将盐值保密或比相应的哈希更安全地存储。

在Go中实现与您描述的PHP代码相同的密码验证逻辑是可行的。我来解释一下这个设计的合理性并提供Go的实现示例。

首先,关于盐值存储的问题:将盐值与哈希值一起存储在同一个字段中是一种常见的做法。虽然盐值本身不是秘密,但它的主要作用是防止预计算攻击(如彩虹表)。即使攻击者知道盐值,他们仍然需要为每个盐值单独计算哈希表,这大大增加了攻击成本。所以这种方式并没有让盐值"失去意义"。

以下是Go的实现示例:

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
)

// 验证密码函数
func verifyPassword(password, correctHash string) bool {
	if len(correctHash) < 128 {
		return false
	}
	
	// 从存储的哈希值中提取盐值和原始哈希
	salt := correctHash[:64]
	originalHash := correctHash[64:128]
	
	// 使用盐值和密码计算新的哈希
	hasher := sha256.New()
	hasher.Write([]byte(salt + password))
	testHash := hex.EncodeToString(hasher.Sum(nil))
	
	return testHash == originalHash
}

// 创建密码哈希(用于新用户注册或密码更改)
func createPasswordHash(password string) string {
	// 生成随机盐值(实际应用中应该使用crypto/rand)
	salt := generateSalt()
	
	// 计算盐值+密码的哈希
	hasher := sha256.New()
	hasher.Write([]byte(salt + password))
	passwordHash := hex.EncodeToString(hasher.Sum(nil))
	
	// 返回盐值+哈希的组合
	return salt + passwordHash
}

// 生成64字符的随机盐值
func generateSalt() string {
	// 在实际应用中,应该使用crypto/rand生成安全的随机字节
	// 这里简化为示例
	randomBytes := make([]byte, 32)
	// 在实际代码中应该使用:rand.Read(randomBytes)
	return hex.EncodeToString(randomBytes)
}

// 使用示例
func main() {
	// 创建新密码哈希(注册时使用)
	password := "user_password"
	hashedPassword := createPasswordHash(password)
	fmt.Printf("存储的哈希值: %s\n", hashedPassword)
	
	// 验证密码(登录时使用)
	isValid := verifyPassword("user_password", hashedPassword)
	fmt.Printf("密码验证结果: %t\n", isValid) // 应该输出 true
	
	isInvalid := verifyPassword("wrong_password", hashedPassword)
	fmt.Printf("错误密码验证结果: %t\n", isInvalid) // 应该输出 false
}

对于现代密码哈希,我建议考虑使用更安全的方案:

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

// 使用bcrypt的示例
func modernPasswordHash(password string) (string, error) {
	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return "", err
	}
	return string(hash), nil
}

func modernVerifyPassword(password, hash string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
	return err == nil
}

bcrypt会自动处理盐值生成和存储,并且提供了更好的安全性,因为它使用了自适应成本因子来抵御暴力破解攻击。

回到顶部