Golang封装加盐和加密次数的MD5函数

3,124 阅读3分钟

最近在使用Gin重构之前用SpringBoot写的后台程序,数据库中保存的密码是加了盐并经过两次hash的MD5值,之前SpringBoot是调用的Shiro进行两次MD5运算。查了一圈发现Golang的MD5工具库并没有提供加盐、加密次数的封装,并且也没有博客提及,所以这里记录一下自己封装的带加盐和加密次数的MD5函数

Golang中使用MD5库

Golang自带的crypto加密库中有MD5函数的实现,主要使用方式有两种,一种是调用write将要计算的byte数组提前写入block块中,调用sum时传入nil;二是直接将byte数组传入sum函数,如下所示:

package utils

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

// 方式一通过Write传参
func MD5(str string) string {
	b := []byte(str)
	h := md5.New()
	h.Write(b)
	return hex.EncodeToString(h.Sum(nil))
}

// 方式二通过Sum传参
func MD5_2(str string) string {
	b := []byte(str)
	h := md5.New()
	return hex.EncodeToString(h.Sum(b))
}

若要加盐的话,在第一种方式的基础上再把salt給write进去,如下所示:

func MD5_SALT(str string, salt string) string {
	b := []byte(str)
	s := []byte(salt)
	h := md5.New()
	h.Write(s) // 先写盐值
	h.Write(b)
	return hex.EncodeToString(h.Sum(nil))
}

错误的多次加密封装

查到MD5函数的加盐使用后,就想当然的以为多次加密应该就是循环加盐的过程,调用下面的封装后发现算出来的盐值不对,网上也没找到多次加密的文章,所以就去找了SpringBoot中的封装。

// 错误的加盐并多次加密
func MD5_SALT_MULT(str string, salt string, times int) string {
	b := []byte(str)
	s := []byte(salt)
	h := md5.New()
	var res []byte
	for i := 0; i < times; i++ {
		h.Write(s)
		h.Write(b)
		res = h.Sum(nil)
		b = res
	}
	return hex.EncodeToString(res)
}

Java加盐并多次加密的MD5函数

找了找以前的SpringBoot项目,发现当时调用了Shiro框架的MD5封装,只需要将盐值、加密次数传入SimpleHash函数就行了,当时并没思考后面是怎么运算的。

import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;

public class PasswordHelper {
    //随机字符串生成器,用于生成盐值
    private RandomNumberGenerator numberGenerator = new SecureRandomNumberGenerator();

    //哈希散列算法——md5
    public static final String ALGORITHM_NAME = "md5";

    //散列次数2
    public static final int HASH_ITERATION = 2;

    /**
     *  加密用户
     * @param user 用户,用户名(name)、密码(pwd)、盐\加密因子(salt)
     */
    public String encryptPassword(String password){
        if (password == null || "".equals(password))
            return;
        //生成加密因子,保存盐。
        String salt = numberGenerator.nextBytes().toHex();
        //加密密码 SimpleHash(算法名,密码,盐的byte,次数).toHex()
        String newPassword = new SimpleHash(ALGORITHM_NAME, password, salt, HASH_ITERATION).toHex();
        //更新密码
        return newPassword;
    }
}

通过 SimpleHash函数向下找具体的实现,SimpleHash的核心代码如下,这才发现原来salt只用了一次,并且每次运算后还要调用rest函数清空digest中的block块,知道了具体过程后就是把这段Java代码翻译成Golang代码即可

package org.apache.shiro.crypto.hash;

public class SimpleHash extends AbstractHash {
    protected byte[] hash(byte[] bytes, byte[] salt, int hashIterations) throws UnknownAlgorithmException {
        MessageDigest digest = this.getDigest(this.getAlgorithmName());
        if (salt != null) {
            digest.reset();
            digest.update(salt);
        }

        byte[] hashed = digest.digest(bytes);
        int iterations = hashIterations - 1;

        for(int i = 0; i < iterations; ++i) {
            digest.reset();
            hashed = digest.digest(hashed);
        }

        return hashed;
    }
}

用Golang实现加盐并多次加密的MD5函数

Java中的MessageDigest对象和Golang的hash结构体可以看作是等价的,都有rest函数,只不过Java通过调用digest函数运算hash值而Golang通过调用Sum函数运算,只需要将上面的Java代码进行简单的翻译就行了。最终封装如下:

package utils

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

func MD5V(str string, salt string, iteration int) string {
    b := []byte(str)
    s := []byte(salt)
	h := md5.New()
	h.Write(s) // 先传入盐值,之前因为顺序错了卡了很久
	h.Write(b)
        var res []byte
	res = h.Sum(nil)
	for i := 0; i < iteration-1; i++ {
		h.Reset()
		h.Write(res)
		res = h.Sum(nil)
	}
	return hex.EncodeToString(res)
}