Golang 常用的几种密码加密方式

1,037 阅读8分钟

加密方式

加密有两种方式,一种是直接加密,一种是盐值加密

**直接加密(Plain Hashing)**指的是将原始密码直接进行加密,而不进行任何额外的操作。这种方式可能存在一些安全风险,因为相同的密码在经过加密后会得到相同的哈希值,容易受到彩虹表攻击等攻击方式的影响。

**盐值加密(Salted Hashing)**则是在进行密码加密之前,将密码与一个随机生成的盐值进行拼接。通过将盐值与密码组合后再进行哈希,可以确保即使密码相同,由于盐值不同,生成的哈希值也会不同。这样可以增加密码的安全性,防止彩虹表攻击等。

为什么被称为盐值?

"盐值"(Salt)一词的使用源自于密码学的术语。在密码学中,"盐"是指一个随机生成的值,用于增加密码哈希函数的安全性。

这个术语的由来是因为盐在密码哈希过程中的作用类似于在烹饪中使用的盐。在烹饪中,盐用于增添食物的味道,并且不同的盐可以带来不同的风味。类比到密码学中,盐用于增加密码的安全性,并且不同的盐可以为相同的密码带来不同的哈希值。

盐值的主要目的是增加密码的破解难度。通过将盐值与密码进行组合后再进行哈希,即使两个用户使用相同的密码,由于盐值不同,最终的哈希值也会不同。这样一来,攻击者无法通过对比哈希值来确定是否存在相同的密码。

另外,使用随机生成的盐值还可以防止预先计算的攻击,例如彩虹表攻击。彩虹表是一种预先计算出密码哈希值与明文密码之间的映射关系的攻击技术,通过事先计算大量密码的哈希值并存储在表中,可以快速地找到对应的明文密码。但是,当使用盐值时,即使明文密码相同,由于盐值不同,生成的哈希值也不同,使得彩虹表攻击无效。

因此,盐值在密码存储和验证过程中起着重要的作用,是一种常见的增强密码安全性的方式之一。

加密方法

1. Bcrypt

Bcrypt 是一个基于 Blowfish 密码哈希函数的密码加密算法。它是一个常见且可靠的选择,具有适度的计算复杂性,可以防止暴力破解和彩虹表攻击。

**Bcrypt 加密函数会自动生成盐值并将其嵌入到生成的哈希值中。**在 Bcrypt 哈希值的格式中,前面部分是一个包含算法标识、盐值和其他必要信息的字符串,后面部分是实际的密码哈希值。

Bcrypt 会自动处理盐值的生成和存储,你不需要显式地提供盐值作为参数。每个密码的盐值都是随机生成的,并且与哈希值一起存储在结果中。这样做的目的是增加密码哈希的安全性,使每个密码都使用不同的盐值进行哈希,即使相同的密码在不同用户之间也会生成不同的哈希值。

因此,**当你使用 Bcrypt 进行密码加密时,你只需要提供密码作为输入,而不需要单独提供盐值。**Bcrypt 会自动为每个密码生成并使用不同的随机盐值。在验证密码时,Bcrypt 会从存储的哈希值中提取盐值,并将其与输入密码一起使用来进行验证。这样,你无需担心盐值的管理和处理,Bcrypt 会为你处理这些细节。

安装

go get -u golang.org/x/crypto/bcrypt

导入包

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

加密

// HashPassword 使用 Bcrypt 算法生成密码哈希值
func HashPassword(password string) (string, error) {
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    if err != nil {
        return "", err
    }
    return string(hashedPassword), nil
}

验证

// ComparePasswords 比较输入的密码与哈希值是否匹配
func ComparePasswords(hashedPassword, password string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
    return err == nil
}

加密验证示例

使用Bcrypt加密示例 使用Bcrypt加密示例

2. Argon2

Argon2 是一个密码哈希函数,被选为 Password Hashing Competition (密码哈希竞赛)的获胜者。它是一个现代的、安全的密码哈希算法,提供了防止侧信道攻击和抵抗 GPU 加速的能力。

Argon2 密码哈希函数将密码加密后的结果通常呈现为乱码。这是因为它们使用了强大的哈希算法和盐值,将密码转换为一个不可逆的哈希值。

这些哈希值通常是二进制数据,不适合直接用于存储或传输。**为了安全地存储和比较密码,你应该将哈希值转换为字符串形式进行存储。**我这里就不写转 base64 格式了,展示一下乱码

安装

go get -u golang.org/x/crypto/argon2

导入包

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

加密

// HashPassword 使用 Argon2 算法生成密码哈希值
func HashPassword(password, salt string) (string, error) {
	time := uint32(1)           // 迭代次数
	memory := uint32(64 * 1024) // 内存使用量(以字节为单位)
	threads := uint8(4)         // 并行线程数
	keyLen := uint32(32)        // 输出密钥长度为 32 字节
	hashedPassword := argon2.IDKey([]byte(password), []byte(salt), time, memory, threads, keyLen)
	return string(hashedPassword), nil
}

验证

验证使用 Go 语言标准库中 crypto/subtle 包中的一个函数ConstantTimeCompare,用于在恒定时间内比较两个字节切片(byte slices)的内容

// ComparePasswords 验证密码是否匹配
func ComparePasswords(hashedPassword, password, salt string) bool {
	time := uint32(1)           // 迭代次数
	memory := uint32(64 * 1024) // 内存使用量(以字节为单位)
	threads := uint8(4)         // 并行线程数
	keyLen := uint32(32)        // 输出密钥长度为 32 字节
	derivedKey := argon2.IDKey([]byte(password), []byte(salt), time, memory, threads, keyLen)
	return subtle.ConstantTimeCompare([]byte(hashedPassword), derivedKey) == 1
}

加密验证示例

使用Argon2加密示例 使用Argon2加密示例

3. Scrypt

Scrypt 是一种基于密码学的 key-derivation function(密钥派生函数),主要用于密码加密和密钥衍生。它相对于一些其他算法,如 MD5 和 SHA-2,具有更高的计算复杂性。

Scrypt 密码哈希函数将密码加密后的结果通常呈现为乱码。这是因为它们使用了强大的哈希算法和盐值,将密码转换为一个不可逆的哈希值。

这些哈希值通常是二进制数据,不适合直接用于存储或传输。**为了安全地存储和比较密码,你应该将哈希值转换为字符串形式进行存储。**我这里就不写转 base64 格式了,展示一下乱码

安装

go get -u golang.org/x/crypto/scrypt

导入包

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

加密

// HashPassword 使用 Scrypt 算法生成密码哈希值
func HashPassword(password, salt string) (string, error) {
	N := 16384   // 迭代次数,表示对哈希函数的重复计算次数。值越大,计算成本越高,安全性越好,但性能也会受到影响。通常建议选择一个大于 2^14 的值,例如 16384
	r := 8       // 块大小,表示每次哈希计算中使用的内存块大小。较大的值会增加内存消耗,并增加攻击成本。通常建议选择一个合适的值,例如 8
	p := 1       // 并行度,表示并行计算的线程或处理器数目。较高的值会增加计算成本,适当的值取决于你的硬件配置。通常建议选择一个合适的值,例如 1
	keyLen := 32 // 参数指定生成的密钥的长度
	hashedPassword, err := scrypt.Key([]byte(password), []byte(salt), N, r, p, keyLen)
	if err != nil {
		return "", err
	}
	return string(hashedPassword), nil
}

验证

验证使用 Go 语言标准库中 crypto/subtle 包中的一个函数ConstantTimeCompare,用于在恒定时间内比较两个字节切片(byte slices)的内容

// ComparePasswords 验证密码是否匹配
func ComparePasswords(hashedPassword, password, salt string) bool {
	N := 16384   // 迭代次数,表示对哈希函数的重复计算次数。值越大,计算成本越高,安全性越好,但性能也会受到影响。通常建议选择一个大于 2^14 的值,例如 16384
	r := 8       // 块大小,表示每次哈希计算中使用的内存块大小。较大的值会增加内存消耗,并增加攻击成本。通常建议选择一个合适的值,例如 8
	p := 1       // 并行度,表示并行计算的线程或处理器数目。较高的值会增加计算成本,适当的值取决于你的硬件配置。通常建议选择一个合适的值,例如 1
	keyLen := 32 // 参数指定生成的密钥的长度
	derivedKey, err := scrypt.Key([]byte(password), []byte(salt), N, r, p, keyLen)
	if err != nil {
		return false
	}
	return subtle.ConstantTimeCompare([]byte(hashedPassword), derivedKey) == 1
}

使用Scrypt加密示例 使用Scrypt加密示例