加密之单向MD5,SHA,HMAC

1 Java加密概述

1.1 Java的安全体系架构介绍

Java中为安全框架提供类和接口。JDK 安全 APIJava编程语言的核心 API,位于 java.security 包(及其子包),以及sun.securityAPI包(及其子包)中。设计用于帮助开发人员在程序中同时使用低级和高级安全功能。
JDK 1.1 中第一次发布的 JDK 安全中引入了Java 加密体系结构(JCA),指的是用于访问和开发 Java 平台密码功能的构架。在 JDK 1.1 中,JCA 包括用于数字签名和报文摘要的 API。JDK 1.2 大大扩展了 Java 加密体系结构,它还对证书管理基础结构进行了升级以支持 X.509 v3 证书,并为划分细致、可配置性强、功能灵活、可扩展的访问控制引入了新的 Java 安全体系结构。
Java 加密体系结构包含 JDK 1.2 安全 API 中与密码有关的部分。为实现多重、可互操作的密码,它还提供了提供者体系结构。
Java 密码扩展 (JCE))扩展了 JCA API,包括用于加密、密钥交换和信息认证码(MAC)的 API。JCEJDK 密码共同提供了一个与平台无关的完整密码 API。不过现在融到jdk了

2 MD5加密

2.1 概述

与MD5与SHA密切相关的几个类的类图如下:
在这里插入图片描述
其中MessageDigestSpi为顶层抽象类,同一个包下的MessageDigestDigestBase为子抽象类。
在上面的类图中,使用了Delegate(委托)设计模式。这种模式的原理为类B(在此处为Delegage内部类)和类A(在此处为MessageDigestSpi类)是两个互相没有什么关系的类,B具有和A一模一样的方法和属性;并且调用B中的方法和属性就是调用A中同名的方法和属性。B好像就是一个受A授权委托的中介。第三方的代码不需要知道A及其子类的存在,也不需要和A及其子类发生直接的联系,通过B就可以直接使用A的功能,这样既能够使用到A的各种功能,又能够很好的将A及其子类保护起来了。

MD5SHA的相关代码都在MD5和SHA等类中,但是面向客户的MessageDigest抽象类不需要跟各个实现类打交道,只要通过委托类与其打交道即可

Message Digest Algorithm MD5(中文名为消息摘要算法第五版)为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。该算法的文件号为RFC 1321.Rivest开发出来,经MD2、MD3和MD4发展而来。
MD5用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一(又译摘要算法、哈希算法),主流编程语言普遍已有MD5实现。将数据(如汉字)运算为另一固定长度值,是杂凑算法的基础原理,MD5的前身有MD2、MD3和MD4
MD5的作用是让大容量信息在用数字签名软件签署私人密钥前被压缩成一种保密的格式(就是把一个任意长度的字节串变换成一定长的十六进制数字串)。

2.2 算法原理

MD5算法简要的叙述可以为:MD5512位分组来处理输入的信息,且每一分组又被划分为1632位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。
MD5算法中,首先需要对信息进行填充,使其位长对512求余的结果等于448。因此,信息的位长(Bits Length)将被扩展至N512+448,N为一个非负整数,N可以是零。填充的方法如下,在信息的后面填充一个1和无数个0,直到满足上面的条件时才停止用0对信息的填充。然后,在这个结果后面附加一个以64位二进制表示的填充前信息长度。经过这两步的处理,信息的位长=N512+448+64=(N+1)*512,即长度恰好是512的整数倍。这样做的原因是为满足后面处理中对信息长度的要求
MD5算法是单向不可逆算法

使用md5加密和验证大致流程如下:

2.3 实际操作

2.3.1 加密

加密代码

import java.security.MessageDigest;

private static String encodeByMD5(String originString) {
        if (originString != null) {
            try {
                //创建具有指定算法名称的信息摘要
                MessageDigest md = MessageDigest.getInstance("MD5");
                //使用指定的字节数组对摘要进行最后更新,然后完成摘要计算
                byte[] results = md.digest(originString.getBytes());
                //此处是把字节转为十六进制字符
                String resultString = byteArrayToHexString(results);
                return resultString.toUpperCase();
                //此处是 用base64处理
                //return new BASE64Encoder().encode(results);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        return null;
    }

3.3.2 MD5处理后转大写十六进制

字节转十六进制或

使用自己自定义的转十六进制算法

private static String byteArrayToHexString(byte[] b) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++) {
            System.out.println(b[i]+"==========="+i);
            resultSb.append(byteToHexString(b[i]));
        }
        
        return resultSb.toString();
    }
private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
 private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n = 256 + n;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }

使用Integer自带的转十六进制

private static String byteArrayToHexString(byte[] b) {
        StringBuffer resultSb = new StringBuffer();
       
        resultSb.append(byteToHex(b));
        return resultSb.toString();
    }
public static String byteToHex(byte[] inbuf) {
        int i;
        String byteStr;
        StringBuilder strBuf = new StringBuilder();
        for (i = 0; i < inbuf.length; i++) {
        //此处的 & 0x00ff是为了 消除负数影响
            byteStr = Integer.toHexString(inbuf[i] & 0x00ff);
            if (byteStr.length() < 2) {
                strBuf.append('0');
            }
            strBuf.append(byteStr);
        }
        return new String(strBuf);
    }

点击此处了解Integer转换16进制需要 & 0x00ff 的作用

或者使用 字符串格式化处理返回十六进制,其中,%02X 是格式化字符串的模式,其中 % 表示占位符的开始,02 表示最小宽度为 2 位,不足时用零填充,X 表示以大写字母的十六进制形式输出

private static String hexToStr(byte[] hex){
        StringBuilder sb = new StringBuilder();
        for (byte b:hex){
            sb.append(String.format("%02X",b));
        }
        return sb.toString();
    }

2.3.3 MD5验密

//password是加密后字符串
// inputstring 是输入字符串
public static boolean validatePassword(String password, String inputString) {
        if (password.equals(encodeByMD5(inputString))) {
            return true;
        } else {
            return false;
        }
    }

3 SHA加密

3.1 概述

SHA是一种数据加密算法,该算法经过加密专家多年来的发展和改进已日益完善,现在已成为公认的最安全的散列算法之一,并被广泛使用。该算法的思想是接收一段明文,然后以一种不可逆的方式将它转换成一段(通常更小)密文,也可以简单的理解为取一串输入码(称为预映射或信息),并把它们转化为长度较短、位数固定的输出序列即散列值(也称为信息摘要或信息认证代码)的过程。散列函数值可以说是对明文的一种指纹或是摘要所以对散列值的数字签名就可以视为对此明文的数字签名。

安全散列算法SHA(Secure Hash Algorithm,SHA)是美国国家标准技术研究所发布的国家标准FIPS PUB 180。其中规定了SHA-1,SHA-224,SHA-256,SHA-384,和SHA-512这几种单向散列算法。SHA-1,SHA-224和SHA-256适用于长度不超过$2{64}$二进制位的消息。SHA-384和SHA-512适用于长度不超过$2$二进制位的消息。

3.2 原理

SHA-1是一种数据加密算法,该算法的思想是接收一段明文,然后以一种不可逆的方式将它转换成一段(通常更小)密文,也可以简单的理解为取一串输入码(称为预映射或信息),并把它们转化为长度较短、位数固定的输出序列即散列值(也称为信息摘要或信息认证代码)的过程。
单向散列函数的安全性在于其产生散列值的操作过程具有较强的单向性。如果在输入序列中嵌入密码,那么任何人在不知道密码的情况下都不能产生正确的散列值,从而保证了其安全性。
SHA将输入流按照每块512位64个字节)进行分块,并产生20个字节的被称为信息认证代码或信息摘要的输出。
该算法输入报文的长度不限,产生的输出是一个160位的报文摘要。输入是按512 位的分组进行处理的。SHA-1是不可逆的、防冲突,并具有良好的雪崩效应。
通过散列算法可实现数字签名实现,数字签名的原理是将要传送的明文通过一种函数运算(Hash)转换成报文摘要(不同的明文对应不同的报文摘要),报文摘要加密后与明文一起传送给接受方,接受方将接受的明文产生新的报文摘要与发送方的发来报文摘要解密比较,比较结果一致表示明文未被改动,如果不一致表示明文已被篡改。
MAC(信息认证代码)就是一个散列结果,其中部分输入信息是密码,只有知道这个密码的参与者才能再次计算和验证MAC码的合法性

3.3 实际操作

public static String shaEncode(String inStr) throws Exception {
        MessageDigest sha = null;
        try {
            sha = MessageDigest.getInstance("SHA");
        } catch (Exception e) {
            e.printStackTrace();
        }
        byte[] byteArray = inStr.getBytes("UTF-8");
        byte[] shaBytes = sha.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < shaBytes.length; i++) {
            int val = ((int) shaBytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }


    public static void main(String args[]) throws Exception {
        String str = new String("amigoxiexiexingxing");
        System.out.println("原始:" + str);
        System.out.println("SHA后:" + shaEncode(str));
    }

3.4 SHA和MD5比较

因为二者均由MD4导出,SHA-1MD5彼此很相似。相应的,他们的强度和其他特性也是相似,但还有以下几点不同:

  1. 对强行攻击的安全性:最显著和最重要的区别是SHA-1摘要比MD5摘要长32位。使用强行技术,产生任何一个报文使其摘要等于给定报摘要的难度对MD5是2^128数量级的操作,而对SHA-1则是2^160数量级的操作。这样,SHA-1对强行攻击有更大的强度。
  2. 对密码分析的安全性:由于MD5的设计,易受密码分析的攻击,SHA-1显得不易受这样的攻击。
  3. 速度:在相同的硬件上,SHA-1的运行速度比MD5

4 HMAC

4.1 定义

HMAC(Hash Message Authentication Code,散列消息鉴别码,基于密钥的Hash算法的认证协议。消息鉴别码实现鉴别的原理是,用公开函数密钥产生一个固定长度的值作为认证标识,用这个标识鉴别消息的完整性。使用一个密钥生成一个固定大小的小数据块,即MAC,并将其加入到消息中,然后传输。接收方利用与发送方共享的密钥进行鉴别认证等

4.2 实际操作

初始化HMAC密钥,生成随机密钥,可选算法:HmacMD5,HmacSHA1,HmacSHA256,HmacSHA384,HmacSHA512

public static final String KEY_MAC = "HmacMD5";  
public static String initMacKey() throws Exception { 
//得到一个 指定算法密钥的密钥生成器 
    KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC);  
  //生成一个密钥
    SecretKey secretKey = keyGenerator.generateKey();  
    return new BASE64Encoder().encode(secretKey.getEncoded());  
}  

HMAC加密

public static final String KEY_MAC = "HmacMD5";  
public static byte[] encryptHMAC(byte[] data, String key) throws Exception {  
  
    SecretKey secretKey = new SecretKeySpec(new BASE64Decoder().decodeBuffer(key), KEY_MAC);  
    Mac mac = Mac.getInstance(secretKey.getAlgorithm());  
    mac.init(secretKey);  
    return mac.doFinal(data);  
  }  

使用时,由于每次生成随机密钥 会导致加密结果不一致,可以使用固定密钥而不是每次都变动的密钥

public static void main(String[] args) throws Exception {
        String s = initMacKey();
       
        byte[] bytes = encryptHMAC("123456".getBytes(), "123456");
        System.out.println(new BASE64Encoder().encode(bytes));

    }
posted @ 2022-01-09 17:03  上善若泪  阅读(377)  评论(0编辑  收藏  举报