浅谈哈希算法

目录

一、概念

二、哈希碰撞

三、常见哈希算法

        1.MD5算法

        1.1 加密普通字符串完整代码

         1.2 加密图片

        2.SHA-1算法

        3.RipeMD-160算法

    四、哈希算法用途

        1.校验下载文件

        2.存储用户密码

五、总结


一、概念

        首先,哈希算法,又叫摘要算法。它的作用是对任意一组输入数据进行计算,得到一个固定长度的输出摘要。

        哈希算法相同的输入一定能得到相同的输出,它的目的就是为了验证原始数据是否被篡改

        java字符串的hashCode()方法就是一个哈希算法,其输入是任意字符串,输出是固定的4字节int整数。

        所有的哈希算法是单向传递的,不可逆。

二、哈希碰撞

        对于相同的字符串一定可以得到相同的哈希值,但有时候不同的字符串也可以得到相同的哈希值,就比如“通话”和“重地”。

        这就有问题了,那么具体我们该怎么样区分?或者说解决该问题呢?

        存在哈希碰撞的情况,我们就不能局限于利用哈希编码值来区分了,前面提到过,hashCode()方法本质上不管你输入多大的数据,它的输出长度是不变的(4字节整数),所以难免有碰撞。

        这就对哈希算法有要求了,一个安全的哈希算法需满足:

                (1)碰撞概率低

                (2)不能猜测输出

        为什么说不能猜测输出呢?

        是因为哈希算法是对输入内容进行加密的,是为了提高在网络传输中的信息安全性的,如果能轻易被反推出输出结果,那它的安全性是无法保证的。

三、常见哈希算法

        哈希碰撞的概率要想达到尽可能的低,那么哈希算法的输出长度就要尽可能的长,长度越长重复的概率就越小,也就越安全。

        

算法输出长度(位)输出长度(字节)
MD5128 bits18bytes
SHA-1160 bits20 bytes
RipeMD-160160 bits20 bytes
SHA-256256 bits32 bytes
SHA-512512 bits64 bytes

        首先,我们要了解一个类MessageDigest,MessageDigest为消息摘要对象,通过调用MessageDigest的getInstance(String algorithm)创建消息摘要对象。

        根据传入算法名称不同,来实现不同哈希算法对象的生成。

        以MD5算法为例:

//创建消息摘要对象MessageDigest
		MessageDigest md5=MessageDigest.getInstance("MD5");//传入不同算法名称以便实现不同加密
	

        MessageDigest提供了update()算法:更新原始数据

                                            digest()算法:获取加密后结果,得到的是一个字节数组。

//更新原始数据
		md5.update("天王盖地虎,宝塔镇河妖".getBytes());//输入
		
//获取加密后结果
	//不论什么内容,加密后得到的永远是一个定长(16)数组
		byte[] b=md5.digest();//输出
        System.out.println("加密后结果:"+Arrays.toString(b));

 

我们想要看到的不仅是加密后输出的字节数组,于是我们定义了一个HashTools工具类,

//Hash算法(消息摘要算法)的工具类
public class HashTools {
    //私有构造方法
	private HashTools() {
		
	}
    
    //将字节数组转换成16进制字符串
	public static String ByteToHex(byte[] bytes) {
        //创建可变字符串对象
		StringBuilder res=new StringBuilder();
		for(byte b:bytes) {//遍历字节数组
            //转换方式,按1个字节占2个字符串位转换
			res.append(String.format("%02x", b));
		}
		return res.toString();//返回字符串
	}
}
    

调用这个工具类的ByteToHex()方法,可以将加密后的字节数组转换成16进制的字符串。

        于是,可以输出

System.out.println("加密(16进制)结果:"+HashTools.ByteToHex(b));

        每次调用update()方法时,输入内容一样加密结果是一样的。

        1.MD5算法

        1.1 加密普通字符串完整代码

public class MD5Demo {
	public static void main(String[] args) throws NoSuchAlgorithmException {
		
		
		//创建消息摘要对象MessageDigest
		MessageDigest md5=MessageDigest.getInstance("MD5");//传入不同算法名称以便实现不同加密
		//更新原始数据
		md5.update("天王盖地虎,宝塔镇河妖".getBytes());//输入
		
		//获取加密后结果
		//不论什么内容,加密后得到的永远是一个定长(16)数组
		byte[] b=md5.digest();//输出
		System.out.println("加密后结果:"+Arrays.toString(b));
		System.out.println("加密(16进制)结果:"+HashTools.ByteToHex(b));
		System.out.println("加密后长度:"+b.length);
		
		//输入内容一样,加密结果一样
		md5.update("天王盖地虎".getBytes());
		md5.update(",宝塔镇河妖".getBytes());
		byte[] b1=md5.digest();
		System.out.println("十六进制加密结果:"+HashTools.ByteToHex(b1));
	}

}

         1.2 加密图片

//将图片读入字节数组
		byte[] image=Files.readAllBytes(Paths.get("C:\\Users\\寻玉萌\\Pictures\\哆啦a梦.jpg"));
		
		//创建加密对象
		MessageDigest md5=MessageDigest.getInstance("MD5");
		
		//输入图片的字节数组
		md5.update(image);
		
		byte[] md5Image=md5.digest();
		System.out.println("加密后结果:"+Arrays.toString(md5Image));
		//16位字节数组按一字节占两位字符串长度输出,所以16进制加密后为32位
		System.out.println("16进制加密后结果:"+HashTools.ByteToHex(md5Image));
		System.out.println("加密后长度:"+md5Image.length);

        加密图片,唯一不同就是更新数据内容,因为update()方法传入的参数时字节数组,,将本地图片利用Files工具类的readAllBytes()方法读入更方便。

        2.SHA-1算法

        区别仅在创建消息摘要对象时传入SHA-1算法名称而已,这里不多赘述。

        3.RipeMD-160算法

        因为MessageDigest对象是通过重写getInstance()方法来达到创建不同算法对象的目的,本地的MessageDigest类中没有重写相对应的RipeMD-160的getInstance()方法,所以这里须引入第三方开源库的jar包方可使用。

        

        在创建消息摘要对象前,需要将提供的消息摘要算法RipeMD-160注册至Security,再进行后续操作。

public class Recipe160Demo {
	public static void main(String[] args) throws NoSuchAlgorithmException {
		//注册BouncyCastleBouncyCastleProvider通知类
		//将提供的消息摘要算法注册至Security
			
		Security.addProvider(new BouncyCastleProvider());
		
		//创建消息摘要对象
		MessageDigest ripemd160=MessageDigest.getInstance("RipeMD160");
		//更新原始数据内容
		ripemd160.update("噜啦噜啦嘞绿".getBytes());
		byte[] b=ripemd160.digest();//获取加密结果
		System.out.println("RipeMD160算法加密后:"+HashTools.ByteToHex(b));
		
		
	}
}

   

    四、哈希算法用途

        1.校验下载文件

                我们下载文件时,网站上都会显示MD5哈希值

                

                校验文件是否被篡改,只需要计算本地文件的哈希值与之对比即可判断。

        2.存储用户密码

                试想一下,假如数据库中存储的直接是用户的密码,那么数据库一旦泄露,那么直接将导致数据的丢失,存在严重的安全风险。

                所以我们可以将直接存储用户的密码改为存储其对应的哈希值,利用对应的哈希算法计算出哈希值存入数据库,用户在进行认证时,系统根据用户输入计算哈希值,再与数据库中对应哈希值对比即可。

        就像下面这样如下:

usernamepassword
aaa473a442f625606ebbe008b3379852d2fda7409fd
bob2426f963654673331e8c9f5fb704c9466f8dbc98

        这样一来,安全性得到保证。

                还有值得注意的是,使用哈希口令是的彩虹表攻击。

                简单来说,彩虹表就是总结出一定规律的常用密码和它们对应的MD5对照表,如果用户使用了常见的密码,黑客便可根据MD5反推出原始密码。

                我们可以借助"加盐"操作来抵御彩虹表攻击。

                加盐机制无非就是增加了密码的随机性,我们让加密内容由密码(password)+随机数字(salt)组成。

                        每次update(password)以后,再update(salt),这样每次生成的加密结果都不同,谁也无计可施。

        具体实现如下:

public class MD5AndSaltDemo {
	public static void main(String[] args) throws NoSuchAlgorithmException {
		//密码
		String password="wbjxxmy";
		//盐值
		String salt=UUID.randomUUID().toString().substring(0, 4);
		
		//创建消息摘要对象
		MessageDigest sha1=MessageDigest.getInstance("SHA-1");
		sha1.update(password.getBytes());
		sha1.update(salt.getBytes());
		
		//获取加密结果
		byte[] b=sha1.digest();
		System.out.println("加密后结果为:"+Arrays.toString(b));
		System.out.println("16进制加密后结果:"+HashTools.ByteToHex(b));
		
	}

}

五、总结

        1.哈希算法用于验证数据完整性,仅支持单向传递(加密),不可逆。

        2.常用的哈希算法MD5、SHA-1,RipeMD-160算法。

        3.用户哈希存储口令时要考虑彩虹表攻击。

                

        

        

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值