2021-09-27

MessageDigest(MD5)来实现数据加密

MessageDigest
MessageDigest 类为应用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法。信息摘要是安全的单向哈希函数,它接收任意大小的数据,输出固定长度的哈希值。

MessageDigest 对象开始被初始化。该对象通过使用 update 方法处理数据。任何时候都可以调用 reset 方法重置摘要。一旦所有需要更新的数据都已经被更新了,应该调用 digest 方法之一完成哈希计算。

对于给定数量的更新数据,digest 方法只能被调用一次。digest 被调用后,MessageDigest 对象被重新设置成其初始状态。

MD5算法得到的摘要是固定为 128 bit 的二进制串(一堆0和1,一共128个),为了更友好的表示摘要,一般都将 128位的二进制串 转为 32个16进制位 或 16个16进制位 如下:

原始数据:
1234567890

16位小写摘要
f82d132f9bb018ca

16位大写摘要
F82D132F9BB018CA

32位小写摘要
e807f1fcf82d132f9bb018ca6738a19f

32位大写摘要
E807F1FCF82D132F9BB018CA6738A19F

MD5算法得到的摘要固定长度位 128 bit 也就是32位,那么怎么还有16的摘要呢?
其实从上面的表格中可以看出,16位的摘要是取32位的摘要值的中间部分,即32位摘要中第8~24位的片段。

MessageDigest类计算MD5摘要的步骤
1、创建MessageDigest对象

public static MesageDigest getInstance(String algorithm);

算法名不区分大小写,所以下面的写法都是正确的:

MesageDigest getInstance("MD5");
MesageDigest getInstance("md5");
MesageDigest getInstance("mD5");

2、向MessageDigest传送要计算的数据
该步骤就是调用下面的某个方法来完成数据的传递

public void update(byte input);
public void update(byte[] input);
public void update(byte[] input, int offset, int len);

3、计算摘要
最后调用下面的某个方法来计算摘要。

public byte[] digest();
public byte[] digest(byte[] input);
public int digest(byte[] buf,int offset,int len);

4、将摘要转为16进制位的字符串
为了更友好的表示摘要,一般都将 128位的二进制串 转为 32个16进制位 或 16个16进制位 ,并以字符串的形式表示。
摘要一般以字符串的形式展示,所以在WEB应用中,用于表示密码的MD5摘要的数据库字段一般设置为String类型String password(虽然字段名字面意思表示账户密码,但实际上只是账户密码的MD5摘要)。

基于MessageDigest类的MD5工具类实现
1、MD5工具类完整代码

import java.security.MessageDigest;

public class MD5Util {

    private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};

    /**
     * 将1个字节(1 byte = 8 bit)转为 2个十六进制位
     * 1个16进制位 = 4个二进制位 (即4 bit)
     * 转换思路:最简单的办法就是先将byte转为10进制的int类型,然后将十进制数转十六进制
     */
    private static String byteToHexString(byte b) {
        // byte类型赋值给int变量时,java会自动将byte类型转int类型,从低位类型到高位类型自动转换
        int n = b;

        // 将十进制数转十六进制
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;

        // d1和d2通过访问数组变量的方式转成16进制字符串;比如 d1 为12 ,那么就转为"c"; 因为int类型不会有a,b,c,d,e,f等表示16进制的字符
        return hexDigits[d1] + hexDigits[d2];
    }

    /**
     * 将字节数组里每个字节转成2个16进制位的字符串后拼接起来
     */
    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++){
            resultSb.append(byteToHexString(b[i]));
        }
        return resultSb.toString();
    }

    /**
     * MD5算法,统一返回大写形式的摘要结果,默认固定长度是 128bit 即 32个16进制位
     * String origin :需要进行MD5计算的字符串
     * String charsetname :MD5算法的编码
     */
    private static String MD5_32(String origin, String charsetname) {
        String resultString = null;
        try {
            // 1,创建MessageDigest对象
            MessageDigest md = MessageDigest.getInstance("MD5");
            // 2,向MessageDigest传送要计算的数据;传入的数据需要转化为指定编码的字节数组
            md.update(origin.getBytes( charsetname ));
            // 3,计算摘要
            byte[] bytesResult = md.digest();

            // 第2步和第3步可以合并成下面一步
            // byte[] bytesResult = md.digest(origin.getBytes(charsetname));

            // 4,将字节数组转换为16进制位
            resultString = byteArrayToHexString( bytesResult );
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 统一返回大写形式的字符串摘要
        return resultString.toUpperCase();

    }

    /**
     * 获取 16位的MD5摘要,就是截取32位结果的中间部分
     */
    private static String MD5_16(String origin, String charsetname) {
        return MD5_32(origin, charsetname).substring(8,24);
    }

    public static void main(String[] args){
        String origin = "1234567890";

        // 默认MD5计算得到128 bit的摘要,即32个 16进制位
        String result_32 = MD5_32(origin, "utf-8");
        System.out.println(result_32);  // E807F1FCF82D132F9BB018CA6738A19F

        // 默认MD5计算得到即16个 16进制位
        String result_16 = MD5_16(origin, "utf-8");
        System.out.println(result_16);  // F82D132F9BB018CA
    }
}

没有调用update方法?
在很多时候,我们经常看到类似下面的写法:

byte[] bytesResult = md.digest(origin.getBytes(charsetname));

在这种写法中并没有代码显式地调用update方法,但跟下面的写法是一样的,实际上也调用了update方法:

// 2,向MessageDigest传送要计算的数据;传入的数据需要转化为指定编码的字节数组

md.update(origin.getBytes( charsetname ));

// 3,计算摘要

byte[] bytesResult = md.digest();

查看该digest(byte[] input)方法的源码,可以看到该方法其实就是将第2步和第3步封装成一个函数而已。

public byte[] digest(byte[] input) {
    update(input);
    return digest();
}

另一种封装的工具类

package com.company;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5Tool {
    private static ThreadLocal<MD5Tool> md5ToolThreadLocal = new ThreadLocal<>();

    private MD5Tool() {
    }

    /**
     * 获取一个MD5工具实例
     */
    public static MD5Tool getInstance() {
        if (md5ToolThreadLocal.get() == null) {
            md5ToolThreadLocal.set(new MD5Tool());
        }
        return md5ToolThreadLocal.get();
    }

    /**
     * 通过md5进行加密
     *
     * @param source 要加密的数据
     * @return
     * @throws NoSuchAlgorithmException
     */
    public String getMd5(String source) throws NoSuchAlgorithmException {
        //1.获取MessageDigest对象
        MessageDigest digest = MessageDigest.getInstance("md5");

        //2.执行加密操作
        byte[] bytes = source.getBytes();

        //在MD5算法这,得到的目标字节数组的特点:长度固定为16
        byte[] targetBytes = digest.digest(bytes);

        //3.声明字符数组
        char[] characters = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

        //4.遍历targetBytes
        StringBuilder builder = new StringBuilder();
        for (byte b : targetBytes) {
            //5.取出b的高四位的值
            //先把高四位通过右移操作拽到低四位
            int high = (b >> 4) & 15;

            //6.取出b的低四位的值
            int low = b & 15;

            //7.以high为下标从characters中取出对应的十六进制字符
            char highChar = characters[high];

            //8.以low为下标从characters中取出对应的十六进制字符
            char lowChar = characters[low];

            builder.append(highChar).append(lowChar);
        }

        return builder.toString();
    }

}

项目中真实用到的密码加密
采用的是Base64+MD5加密

public String encode(String passwd, String username)
{

    Encoder encoder=Base64.getEncoder();
    MessageDigest md5 = null;
    try
    {
        md5 = MessageDigest.getInstance("MD5");
    }
    catch (NoSuchAlgorithmException e)
    {
        System.out.println("encode()加密失败");//一般使用logger.error("encode()加密失败", e);
    }
    try
    {
        if (md5 != null)
        {
            passwd = encoder.encodeToString(md5.digest((passwd + username).getBytes("utf-8")));
        }
    }
    catch (UnsupportedEncodingException e)
    {
        System.out.println("encode()加密失败");//一般使用logger.error("encode()加密失败", e);
    }

    return passwd;
}

之后将密码保存到数据库中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值