0


常见哈希算法以及Hmac算法,BouncyCastle总结

常见哈希算法总结

(1)什么是哈希算法?

哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输出数据进行计算,得到一个固定长度的输出摘要。哈希算法的目的;为了验证原始数据是否被篡改.

哈希算法最重要的特点是:

相同的输入一定得到相同的输出;

不同的输入大概率得到不同的输出.

    Java字符串的hashCode()就是一个哈希算法,它的输入是任意字符串,输出是固定的4字节int整数:
"hello".hashCode(); // 0x5e918d2
"hello, java".hashCode(); // 0x7a9d88e8
"hello, bob".hashCode(); // 0xa0dbae2f

(2)哈希碰撞

概念:两个不同的输入得到了相同的输出。

"AaAaAa".hashCode(); // 0x7460e8c0
"BBAaBB".hashCode(); // 0x7460e8c0

"通话".hashCode(); // 0x11ff03
"重地".hashCode(); // 0x11ff03

碰撞能不能避免?答案是不能。碰撞是一定会出现的,因为输出的字节长度是固定的,String的hashCode()输出是4字节整数,最多只有4294967296种输出,但输入的数据长度是不固定的,有无数种输入。所以,哈希算法是把一种无限的输入集合映射到一个有限的输出集合,必然会产生碰撞。

碰撞概率的高低关系到哈希算法的安全性。一个安全的哈希算法必须满足:

碰撞概率低;

不能猜测输出。

不能猜测输出是指:输出的任意一个bit的变化会造成输出完全不同,这样就很难从输出反推输入(只能依靠暴力穷举).

假设一种哈希算法有如下规律:

hashA("java001") = "123456"
hashA("java002") = "123457"
hashA("java003") = "123458"

那么很容易从输出123459反推输入,这种哈希算法就不安全。安全的哈希算法从输出是看不出任何规律的。

hashB("java001") = "123456"
hashB("java002") = "580271"
hashB("java003") = ???

常见哈希算法

常见的哈希算法有:根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全。
算法输出长度(位)输出长度(字节)MD5128bits16bytesSHA-1160bits20bytesRipeMD-160160bits20bytesSHA-256256bits32bytesSHA-512512bits64bytes
Java标准库提供了常用的哈希算法,并且有一套统一的接口。我们以MD5算法为例,看看如何对输入计算哈希:

public class main{
    public static void main(String[] args){
        //创建一个MessageDigest实例;
        MessageDigest md=MessageDigest.getInstance("MD5");
        //反复调用updata输入数据:
        md.update("hello".getBytes("UTF-8"));
        md.updata("world".getBytes("UTF-8"));
        
        byte[] results=md.digest();
        StringBuilder sb=new StringBuilder();
        for(byte bite:results){
            sb.append(String.format("%02x",bite));
        }
        
        System.out.println(sb.toString());
    }
}
    使用MessageDigest时,我们首先根据哈希算法获取一个MessageDigest实例,然后,反复调用update(byte[])输入数据.当输入结束后,调用digest()方法获得byte[]数组表示的摘要,最后,把它转换成十六进制的字符串。

案例:读取一张图片调用digest()方法获得byte[]数组表示的摘要,最后,把它转换成十六进制的字符串。

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class Test08 {
    public static void main(String[] args) {
        try {
            byte[] imageByteArray=Files.readAllBytes(Paths.get("c:\\test\\img\\cz.jpg"));
            MessageDigest digest=MessageDigest.getInstance("MD5");
            digest.update(imageByteArray);
            byte[] resultByteArray=digest.digest();
            StringBuilder result=new StringBuilder();
            for(byte bite:resultByteArray) {
                result.append(String.format("%02x", bite));
            }
            System.out.println(result);
            System.out.println(result.length());
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

SHA-1

    SHA-1也是一种哈希算法,它的输出是160 bits,即20字节。SHA-`1是由美国国家安全局开发的,SHA算法实际上是一个系列,包括SHA-0(已废弃),SHA-1,SHA-256,SHA-512等。

    在Java中使用SHA-1,和MD5完全一样,只需要把算法名称改为"SHA-1":
import java.security.MessageDigest;

public class main {
    public static void main(String[] args)  {
        // 创建一个MessageDigest实例:
        MessageDigest md = MessageDigest.getInstance("SHA-1");
       
        // 反复调用update输入数据:
        md.update("Hello".getBytes("UTF-8"));
        md.update("World".getBytes("UTF-8"));
        
        // 20 bytes: db8ac1c259eb89d4a131b253bacfca5f319d54f2
        byte[] results = md.digest(); 

        StringBuilder sb = new StringBuilder();
        for(byte bite : results) {
            sb.append(String.format("%02x", bite));
        }
        
        System.out.println(sb.toString());
    }
}

哈希算法的用途

    检验下载文件

因为相同的输入永远会得到相同的输出,因此,如果输入被修改了,得到的输出就会不同。

     如何判断下载到本地的软件是原始的,未经篡改的文件?我们只需要自己计算一下本地文件的哈希值,再与官网公开的哈希值对比,如果相同,说明文件下载正确,否则,说明文件已被篡改。

存储用户密码

    哈希算法的另一个重要用途是存储用户口令。如果直接将用户的原始口令存储到数据库中,会产生极大的安全风险:

    数据库管理员能够看到用户明文口令;

    数据库数据一旦泄露,黑客即可获取用户明文口令。

usernamepasswordbob123456789alicesdfsdfsdftimjustdoit
如何对用户进认证?存储用户口令的哈希,系统计算用户输入的原始口令的MD5并与数据库存储的MD5对比,如果一致,说明口令正确,否则,口令错误。

这样,数据库管理员看不到用户的原始口令。即使数据库泄露,黑客也无法拿到用户的原始口令。想要拿到用户的原始口令,必须使用暴力穷举的方法,一个口令一个口令的试,直到某个口令计算的MD5恰好等于指定值。

使用哈希口令时,还要注意防止彩虹表攻击。什么是彩虹表:如果一个预先计算好的常用口令和他们的MD5的对照表,这个表就是彩虹表。如果用户使用了常用口令,黑客从MD5一下就能反查到原始口令:
常用口令MD5hello123f30aa7a662c728b7407c54ae6bfd27d1...........
当然,我们也可以采取特殊措施来抵御彩虹表攻击;对每个口令额外添加随机数,这个方法称之为加盐(salt):

    digest=md5(salt+inputPassword)

案例:

public class Test09 {
    public static void main(String[] args) {
        String password="wbjxxmy";
        String salt=UUID.randomUUID().toString().substring(0, 5);
        System.out.println(salt);
        
        try {
            //获取SHA-1算法的工具对象
            MessageDigest digest=MessageDigest.getInstance("SHA-1");
            digest.update(password.getBytes());
            digest.update(salt.getBytes());
            
            byte[] resultByteArray=digest.digest();
            
            System.out.println(Arrays.toString(resultByteArray));
            System.out.println(resultByteArray.length);
            
            
            StringBuilder result=new StringBuilder();
            for(byte b:resultByteArray) {
                result.append(String.format("%02x", b));
            }
            System.out.println(result);
            System.out.println(result.length()); 
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

Hmac算法

    Hmac算法总是和某种哈希算法配合起来用的。例如:我们使用MD5算法,对应的就是Hmac MD5算法,它相当于加盐的MD5

好处:

HmacMD5使用的key长度是64字节,更安全;

Hmac是标准算法,同样适用于SHA-1等其他哈希算法;

Hmac输出和原有的哈希算法长度一致。

可见,Hmac本质上就是把key混入摘要的算法。验证此哈希时,除了原始的输入数据,还要提供key.为了保证安全,我们不会指定key,而是通过Java标准库的KeyGenerator生成一个安全的随机key.

//获取HmacMD5密钥生成器
KeyGenerator keyGen=KeyGenerator.getInstance("HmacMD5");
//产生密钥
SecretKey secreKey=keyGen.generateKey();
//打印随机生成的密钥:
byte[] keyArray=sereKey.getEncoded();
StringBuilder key=StringBuilder();
for(byte bite:keyArray){
    key.append(String.format("%02x",bite));
}
System.out.println(key);
//使用HmacMD5加密
Mac  mac=Mac.getInstance("HmacMD5");
mac.init(secreKey);//初始化
mac.update("HelloWorld".getBytes("UTF-8"));
byte[] resultArray=mac.doFinal();

StringBuilder result=new StringBuilder();
for(byte bite:resultArray){
    result.append(String.format("%02x",bite));
}
System.out.println(result);

和MD5相比,使用HmacMD5的步骤是:

1.通过名称HmacMD5获取KeyGenerator实例;

2.通过KeyGenerator创建一个SecretKey实例;

3.通过HmacMD5获取Mac实例;

4.用SecretKey初始化Mac实例;

5.对Mac实例反复调用updata(byte[])输入数据;

6.调用Mac实例的doFinal()获得最终的哈希值

    有了Hmac计算的哈希和SecretKey,我们想要验证怎么办?SecretKey不能从KeyGenerator生成,而是从一个byte[]数组恢复。
byte[] keyArray = {70, 31, 31, 113, -75, 45, 5, 112, -32, -32, 57, 59, -77, -52, -22, -67, -115, -15, -32, -57, 94, -78, 58, -115, 76, -104, 41, -120, -21, 28, 123, -13, -79, -17, 18, 63, -45, -14, -43, -33, -126, -115, 76, -87, -123, -16, -109, -127, -113, -114, 19, 96, 69, 73, -2, -75, -66, -88, -10, -9, -14, 104, -97, -69};
        
SecretKey secreKey = new SecretKeySpec(keyArray, "HmacMD5");
Mac mac = Mac.getInstance("HmacMD5");
mac.init(secreKey); // 初始化key
mac.update("HelloWorld".getBytes("UTF-8"));
byte[] resultArray = mac.doFinal();

StringBuilder result = new StringBuilder();
for(byte bite:resultArray) {
    result.append(String.format("%02x", bite));
}
System.out.println(result);

BouncyCastle

提供了很多哈希算法和加密算法的第三方开源库。它提供了Java标准库没有的一些算法,例如:RipeMD160算法。

用法

1.添加jar包至classpath

java标准库的java.seurity包提供了一种标准机制,允许第三方提供无缝接入。我们使用BouncyCastle提供的RipeMD160算法,需先把BouncyCastle注册一下

public class Main {
    public static void main(String[] args) throws Exception {
        // 注册BouncyCastle提供的通知类对象BouncyCastleProvider
        Security.addProvider(new BouncyCastleProvider());

        // 获取RipeMD160算法的"消息摘要对象"(加密对象)
        MessageDigest md = MessageDigest.getInstance("RipeMD160");

        // 更新原始数据
        md.update("HelloWorld".getBytes());

        // 获取消息摘要(加密)
        byte[] result = md.digest();

        // 消息摘要的字节长度和内容
        System.out.println(result.length); // 160位=20字节
        System.out.println(Arrays.toString(result));

        // 16进制内容字符串
        String hex = new BigInteger(1,result).toString(16);
        System.out.println(hex.length()); // 20字节=40个字符
        System.out.println(hex);
    }
}

本文转载自: https://blog.csdn.net/m0_46502752/article/details/125921600
版权归原作者 掉头发的胡程序员 所有, 如有侵权,请联系我们删除。

“常见哈希算法以及Hmac算法,BouncyCastle总结”的评论:

还没有评论