go语言中的数据哈希

哈希算法

哈希函数的基本特征

  1. 输入可以是任意长度。
  2. 输出是固定长度。
  3. 根据输入很容易计算出输出。
  4. 根据输出很难计算出输入(几乎不可能)。
  5. 两个不同的输入几乎不可能得到相同的输出。

SHA

SHA(Secure Hash Algorithm) 安全散列算法,是一系列密码散列函数,有多个不同安全等级的版本:SHA-1,SHA-224,SHA-256,SHA-384,SHA-512。

其作用是防伪装,防窜扰,保证信息的合法性和完整性。

const (
	MD4         Hash = 1 + iota // import golang.org/x/crypto/md4
	MD5                         // import crypto/md5
	SHA1                        // import crypto/sha1
	SHA224                      // import crypto/sha256
	SHA256                      // import crypto/sha256
	SHA384                      // import crypto/sha512
	SHA512                      // import crypto/sha512
	MD5SHA1                     // no implementation; MD5+SHA1 used for TLS RSA
	RIPEMD160                   // import golang.org/x/crypto/ripemd160
	SHA3_224                    // import golang.org/x/crypto/sha3
	SHA3_256                    // import golang.org/x/crypto/sha3
	SHA3_384                    // import golang.org/x/crypto/sha3
	SHA3_512                    // import golang.org/x/crypto/sha3
	SHA512_224                  // import crypto/sha512
	SHA512_256                  // import crypto/sha512
	BLAKE2s_256                 // import golang.org/x/crypto/blake2s
	BLAKE2b_256                 // import golang.org/x/crypto/blake2b
	BLAKE2b_384                 // import golang.org/x/crypto/blake2b
	BLAKE2b_512                 // import golang.org/x/crypto/blake2b
)

这里以sha-1为例

sha-1算法大致过程

  1. 填充。使得数据长度对512求余的结果为448。
  2. 在信息摘要后面附加64bit,表示原始信息摘要的长度。
  3. 初始化h0到h4,每个h都是32位。
  4. h0到h4历经80轮复杂的变换。
  5. 把h0到h4拼接起来,构成160位,返回。
package main

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

func Sha1(data string) string {
	sha1 := sha1.New()
	sha1.Write([]byte(data))
	return hex.EncodeToString(sha1.Sum(nil))
}

func main() {
	data := "因为我们没有什么不同"
	fmt.Printf("SHA-1: %s\n", Sha1(data))
}
//SHA-1: 0cfea402af137e3793a6c8a80152b5ab74ba380b

MD5

MD5(Message-Digest Algorithm 5)信息-摘要算法5,算法流程跟SHA-1大体相似。MD5的输出是128位,比SHA-1短了32位。MD5相对易受密码分析的攻击,运算速度比SHA-1快。

package main

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

func Md5(data string) string {
	md5 := md5.New()
	md5.Write([]byte(data))
	return hex.EncodeToString(md5.Sum(nil))
}

func main() {
	data := "因为我们没有什么不同"
	fmt.Printf("MD5: %s\n", Md5(data))
}

哈希函数的应用场景

  • 用户密码的存储。
  • 文件上传/下载完整性校验。
  • mysql大字段的快速对比。

HMac

Hmac算法也是一种哈希算法,它可以利用MD5或SHA1等哈希算法。不同的是,Hmac还需要一个密钥, 只要密钥发生了变化,那么同样的输入数据也会得到不同的签名,因此,可以把Hmac理解为用随机数“增强”的哈希算法

package main

import (
	"crypto/hmac"
	"fmt"
	"io"
)

// 使用sha1的Hmac散列算法
func hmacHash(msg string, key string) (hashData []byte) {
	k := []byte(key)
	mac := hmac.New(sha1.New, k)
	io.WriteString(mac, msg)
	hashData = mac.Sum(nil)
	return
}

func main() {
	msg := "This is the message to hash!"
	// hmac
	hmacData := hmacHash(msg, "The key string!")
	fmt.Printf("HMAC: %x\n", hmacData)
}

你觉得上面这些算法好吗?

如果你使用的是MD5算法来加密你的口令,你的口令长度只有小写字母再加上数字,假设口令的长度是6位,那么在目前一台比较新一点的PC机上,穷举所有的口令只需要40秒钟。几乎有90%以上的用户只用小写字母和数字来组织其口令。对于6位长度的密码只需要最多40秒就可以破解了,这可能会吓到你

因为MD5,SHA的算法速度太快了, 当用于消息摘要,还是很不错的, 但是用于password hash就不行了
,有没有适合password hash的算法喃?

bcrypt

bcrypt是这样的一个算法,因为它很慢,对于计算机来说,其慢得有点BT了,但却慢得刚刚好!对于验证用户口令来说是不慢的,对于穷举用户口令来说,其会让那些计算机变得如同蜗牛一样

bcrypt采用了一系列各种不同的Blowfish加密算法,并引入了一个work factor,这个工作因子可以让你决定这个算法的代价有多大。因为这些,这个算法不会因为计算机CPU处理速度变快了,而导致算法的时间会缩短了。因为,你可以增加work factor来把其性能降下来

同时bcrypt也是一种加盐的Hash方法,MD5 Hash时候,同一个报文经过hash的时候生成的是同一个hash值,在大数据的情况下,有些经过md5 hash的方法将会被破解(碰撞).使用BCrypt进行加密,同一个密码每次生成的hash值都是不相同的。每次加密的时候首先会生成一个随机数就是盐,之后将这个随机数与报文进行hash,得到 一个hash值

那一个被bcrypt hash过后的结果长啥样喃:

Bcrypt有四个变量:

  • saltRounds: 正数,代表hash杂凑次数,数值越高越安全,默认10次。
  • myPassword: 明文密码字符串。
  • salt: 盐,一个128bits随机字符串,22字符
  • myHash: 经过明文密码password和盐salt进行hash,个人的理解是默认10次下 ,循环加盐hash10次,得到myHash
package main

import (
    "fmt"

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

func HashPassword(password string) (string, error) {
    bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
    return string(bytes), err
}

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

func main() {
    password := "secret"
    hash, _ := HashPassword(password) // ignore error for the sake of simplicity

    fmt.Println("Password:", password)
    fmt.Println("Hash:    ", hash)

    match := CheckPasswordHash(password, hash)
    fmt.Println("Match:   ", match)
}

应用

用户名密码校验

密码校验则是一个很常见的问题, 当我们设计用户中心时,是一个必不可少的功能, 为了安全,我们都不会保存用户的明文密码, 最好的方式就是保存为Hash, 这样即使是数据泄露了,也不会导致用户的明文密码泄露(hash的过程是不可逆的)

需求:

  • 用户可以修改密码
  • 修改密码时,禁止使用最近已经使用过的密码
  • 能校验密码
// NewHashedPassword 生产hash后的密码对象
func NewHashedPassword(password string) (*Password, error) {
	bytes, err := bcrypt.GenerateFromPassword([]byte(password), 10)
	if err != nil {
		return nil, err
	}

	return &Password{
		Password: string(bytes),
		CreateAt: ftime.Now().Timestamp(),
		UpdateAt: ftime.Now().Timestamp(),
	}, nil
}

type Password struct {
	// hash过后的密码
	Password string
	// 密码创建时间
	CreateAt int64
	// 密码更新时间
	UpdateAt int64
	// 密码需要被重置
	NeedReset bool
	// 需要重置的原因
	ResetReason string
	// 历史密码
	History []string
	// 是否过期
	IsExpired bool
}
// Update 更新密码
func (p *Password) Update(new *Password, maxHistory uint, needReset bool) {
	p.rotaryHistory(maxHistory)
	p.Password = new.Password
	p.NeedReset = needReset
	p.UpdateAt = ftime.Now().Timestamp()
	if !needReset {
		p.ResetReason = ""
	}
}

// IsHistory 检测是否是历史密码
func (p *Password) IsHistory(password string) bool {
	for _, pass := range p.History {
		err := bcrypt.CompareHashAndPassword([]byte(pass), []byte(password))
		if err == nil {
			return true
		}
	}

	return false
}

// HistoryCount 保存了几个历史密码
func (p *Password) HistoryCount() int {
	return len(p.History)
}

func (p *Password) rotaryHistory(maxHistory uint) {
	if uint(p.HistoryCount()) < maxHistory {
		p.History = append(p.History, p.Password)
	} else {
		remainHistry := p.History[:maxHistory]
		p.History = []string{p.Password}
		p.History = append(p.History, remainHistry...)
	}
}

// CheckPassword 判断password 是否正确
func (p *Password) CheckPassword(password string) error {
	err := bcrypt.CompareHashAndPassword([]byte(p.Password), []byte(password))
	if err != nil {
		return exception.NewUnauthorized("user or password not connrect")
	}
	return nil
}

总结

  • 已经被破解了的Hash
  • 速度较快的Hash,适于与内容摘要
  • 加盐Hash
  • 速度较慢的Hash,适用于密码保存
posted @ 2023-04-30 22:12  厚礼蝎  阅读(110)  评论(0编辑  收藏  举报