哈希加密算法

1.概述:在数据库存储数据时,如果我们直接将数据明文存放在数据库中,可能存在着非常大的安全问题。例如,我们把用户密码直接存放在数据库中,如果数据库被黑客盗取,黑客会直接得到正确的密码,随意登录账户。这时候,我们就应该将密码经过加密后再将加密的结果存入数据库,当用户输入密码后,我们只需要将输入的密码加密后与数据库中的数据进行对比,检查密码是否正确。

2.哈希加密算法

哈希算法是常见的加密算法,是当你输入任意内容时,经过哈希算法会返回一个固定长度的值。

哈希算法的特点:

        1.当输入内容一样时,输出内容一定一样。

        2.当输入内容不一样时,输出内容大概率不一样。

 这里为什么当输入内容不一样,而输出内容时大概率不一样,我们以String类的hashCode()方法为例,hashCode()方法就是一种哈希算法,字符串调用该方法会返回一个int类型的整数。

public static void main(String[] args) {
		String str1="通话";
		String str2="重地";
		System.out.println("通话的hashCode:"+str1.hashCode());
		System.out.println("重地的hashCode:"+str2.hashCode());
	}

 输出结果:

 

我们看到,虽然"通话"和"重地"这两个字符串的内容不一样,但它们返回的hashCode值时相等的,这是因为我们的输入内容时无限的,而经过哈希算法返回的值都是有具体的位数(即是有限的),相当于我们把无限份信(输入内容)放到有限个信箱(哈希算法返回的值)中,肯定会有若干个信是放到同一个信箱当中的。

而当输入内容不同时,经过哈希算法却得到了相同的值,我们将这种情况称作哈希碰撞

常见的哈希算法(hashCode()除外)
算法名称输出长度(位数)输出长度(字节数)
MD5128 bits16 bytes
SHA-1160 bits20 bytes
RipeMD-160160 bits20 bytes
SHA-256256 bits32 bytes
SHA-512512 bits

64 bytes

3.java实现哈希算法

在java标准库中实现了常见的哈希算法,我们需要调用统一的接口来使用哈希算法来对数据进行加密。以MD5为例:

//MD5加密算法(消息摘要)
public class Demo06 {
	public static void main(String[] args) throws NoSuchAlgorithmException {
		//创建MD5消息摘要对象    通过字符串指定具体算法对象
		MessageDigest md5=MessageDigest.getInstance("MD5");
		
		//更新原始数据
		md5.update("测试数据".getBytes());
		
		//获取加密后字节数组
		byte[] md5Ret=md5.digest();
		System.out.println("加密后(字节数组结果):"+Arrays.toString(md5Ret));
		
		System.out.println("加密后(16进制字符串):"+HashUtils.byteToHexString(md5Ret));
		//不管输入什么内容,加密后的长度永远固定
		System.out.println(md5Ret.length);
	}
}

HashUtils工具类(将得到的字节数组转换为十六进制的字符串):

//将字节数组转换为16进制的字符串
	public static String byteToHexString(byte[] buf) {
		StringBuilder rt=new StringBuilder();
		for(byte b:buf) {
			//将一个字节值转换成2位字符,不够时用0补充  %02x
			rt.append(String.format("%02x", b));
		}
		return rt.toString();
	}

运行结果: 

 首先,我们需要使用MessageDigest.getInstance("算法名称")来创建具体算法的MessageDigest对象,再调用update()方法将需要加密数据的字节值传进去,然后再调用digest()方法执行加密,并将加密后的字节数组返回。因为加密数据为字节数组类型,所以加密的数据可以是字符串,图片,视频等。如果需要使用其它哈希算法,只需要改变在创建MessageDigest对象时指定的算法名称,特别的是,java标准库并没有提供RipeMD-160算法的实现,我们需要导入第三方类库bcprov-jdk15on-1.70,下载地址:https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on

然后在程序中注册后,就可以正常使用了。

//注册provider类, 将第三方类库加入标准库中
Security.addProvider(new BouncyCastleProvider());

 4.盐值和Hmac算法

因为哈希算法当输入内容一致时,每次运行的结果不会改变,所以根据加密结果可能会推算出原始数据,为了提高数据安全性,我们在每次加密数据的时候,在数据的前面或后面加上一个盐值,盐值由你自己决定,盐值的长度越长,安全性越好,在数据库中也要存储盐值。以盐值为一个随机数为例:

public static void main(String[] args) throws NoSuchAlgorithmException {
		
		String password="9269899464";
		
		//盐值(随机数)
		String salt=UUID.randomUUID().toString().substring(1,5);
		
		MessageDigest sha1=MessageDigest.getInstance("SHA-1");
		
		sha1.update(password.getBytes());
		
		sha1.update(salt.getBytes());
		
		String digestRet=HashUtils.byteToHexString(sha1.digest());
		
		System.out.println(digestRet);
	}

Hmac算法: Hmac算法是基于密钥的算法,密钥就相当于上面所说的盐值,当需要校验数据的准确性时,只有正确的数据和正确的密钥才能得到正确的加密结果。

Hmac算法通常与哈希算法配合使用,Hmac算法首先需要通过KeyGenerator生成密钥,然后将密钥加入加密过程,生成密钥后,也需要将密钥的内容存放在数据库中。

public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
		//1.生成密钥
		KeyGenerator keyGenerator=KeyGenerator.getInstance("HmacMD5");
		//生成密钥对象
		SecretKey key=keyGenerator.generateKey();
		
        //获取密钥字节数组
		System.out.println("密钥字节数组:"+Arrays.toString(key.getEncoded()));
		
		System.out.println("密钥长度:"+key.getEncoded().length);
		
		System.out.println("密钥字符串:"+HashUtils.byteToHexString(key.getEncoded()));
		
		//2.使用密钥加密
		Mac mac=Mac.getInstance("HmacMD5");
		
		//初始化密钥
		mac.init(key);
		
		//加入需要加密的内容
		mac.update("测试数据".getBytes());
		
		//进行加密操作,并返回加密结果
		byte[] buf=mac.doFinal();
		
		System.out.println("加密结果(字节长度):"+buf.length);
		
		System.out.println("加密字符结果:"+HashUtils.byteToHexString(buf));

}

运行结果:

 从运行结果中可以看出,Hmac算法生成的密钥为64字节,但哈希算法使用的是MD5算法,所以加密之后的结果为16字节。

5.恢复密钥验证数据

在我们校验数据时,需要用相同的密钥和数据进行加密,然后与数据库中存放的加密结果进行比对才能得到数据的准确性,而往往数据库中存放的是密钥的内容(字节数组或字符串),在加密过程中用到的是SecretKey密钥对象。

public static void main(String[] args) {
		
		String password="测试结果";
		byte[] keyBytes= {-127, 74, 88, 80, 27, 89, -14, 51, -126, 34, 61, -41, 115, -69, 104, -79, -54, 92, -94, 116, 29, 107, 90, 121, 106, -50, 26, -79, -49, 32, 46, -115, -95, 25, -62, -26, 112, 73, -128, 96, 18, 38, -64, 53, -59, 93, -13, -17, 34, -104, 9, 123, -108, 31, -5, 100, 121, -78, -23, -115, -121, -90, -87, -113};
		
		//根据密钥字节数组,创建出密钥对象
		SecretKey key=new SecretKeySpec(keyBytes, "HmacMD5");
		
		//d43c0ede402ec34270a48ec0f94bf5f8
		try {
			Mac  mac=Mac.getInstance("HmacMD5");
			
			mac.init(key);
			
			mac.update(password.getBytes());
			
			String ret=HashUtils.byteToHexString(mac.doFinal());
			System.out.println(ret);
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (IllegalStateException e) {
			e.printStackTrace();
		}
	}

运行结果: 

 

可以看到,我们根据密钥的字节数组创建出密钥对象,然后进行加密得到的结果与我们在上面代码的加密结果是一致的。

我们根据字节数组可以直接创建出密钥对象,当我们数据库中存放的是密钥字符串时,就需要将字符串转换为对应的字节数组,然后创建出密钥对象,因为我们在字节数组转字符串时,是将一个字节转换成了两个十六进制的字符,所以在字符串转字节数组时,应该每两个十六进制字符转换成一个字节。

        String keyStr="2430f918eb2a72f219e889bf4d8e2a523239397dae0431c7813ac771345c5d34caf897c4be2a37a1156d88e4416af82862ae689e207c9753169eabdce4dcae2d";
		
		byte[] keyBytes=new byte[64];
		
        //将密钥字符串每两个字符转换成一个字节
		for(int i=0,k=0;i<keyStr.length();i+=2,k++) {
            //每次截取两个字符
			String s=keyStr.substring(i,i+2);
			
			keyBytes[k]=(byte)Integer.parseInt(s, 16);
		}
		
		SecretKey key=new SecretKeySpec(keyBytes, "HmacMD5");
		
		try {
			Mac mac=Mac.getInstance("HmacMD5");
			
			mac.init(key);
			
			mac.update("测试结果".getBytes());
			
			String ret=HashUtils.byteToHexString(mac.doFinal());
			
			System.out.println(ret);

执行以上代码,可以得到和上面结果一致的加密字符串,我们只需要对比用密钥进行加密得到的字符串与数据库中存放的字符串是否一致,就可以得出数据的正确与否。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值