JAVASE中的哈希算法简单实现

哈希算法概述:

哈希算法(Hash):

又称摘要算法(Digest),作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。
哈希算法最重要的特点就是:
相同的输入一定得到相同的输出
不同的输入大概率得到不同的输出

So,哈希算法的目的:为了验证原始数据是否被篡改

为何不同的输入得到的输出只是大概率不同,这便是接下来要讲述的哈希碰撞

哈希碰撞:

哈希碰撞是指:两个不同的输入得到了相同的输出

碰撞能不能避免?答案是不能。碰撞是一定会出现的,因为输出的字节长度是固定的,Java中String的hashCode()就是一个哈希算法,它的输入是任意字符串,输出是固定4字节整数,最多只有4294967296种输出,但输入的数据长度是不固定的,有无数种输入。所以,哈希算法是把一个无限的输入集合映射到一个有限的输出集合,必然会产生碰撞。
碰撞不可怕,我们要担心的不是碰撞,而是碰撞的概率,因为碰撞概率的高低关系到哈希算法的安全性。一个安全的哈希算法必须满足:
碰撞概率低
不能猜测输出
不能猜测输出是指:输入的任意一个bit的变化会造成输出完全不同,这样就很难从输出反推输入(只能依靠暴力穷举)

常用哈希算法:

哈希算法,根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全。
常用的哈希算法有:

算法

输出长度(位)

输出长度(字节)

MD5

128 bits

16 bytes

SHA-1

160 bits

20 bytes

RipeMD-160

160 bits

20 bytes

SHA-256

256 bits

32 bytes

SHA-512

512 bits

64 bytes

接下来,我们用代码实现常见的两种哈希算法:MD5与SHA-1

MD5:

Java标准库提供了常用的哈希算法,通过统一的接口进行调用。以MD5算法为例,看看如何对输入内容计算哈希

Ⅰ.加密文本信息

public class md5 {
    public static void main(String[] args) throws NoSuchAlgorithmException {
        //创建基于MD5算法的消息摘要对象
        MessageDigest md5 = MessageDigest.getInstance("md5");
        //更新原始数据
        md5.update("我本将心向明月".getBytes());

        //获取加密后的结果
        byte[] disest = md5.digest();
        System.out.println(Arrays.toString(disest));//加密后的结果
        System.out.println(HashTools.bytesToHex(disest));//将字节数组转换为16进制字符串
        System.out.println(disest.length);//加密结果的长度
    }
}
//Hash算法(信息摘要算法)工具
public class HashTools{
    private HashTools(){}
    public static String bytesToHex(byte[] bytes){
        StringBuilder ret = new StringBuilder();
        //将字节数组转换为16进制字符串
        public static String bytesToHex(byte[] bytes){
            StringBuilder ret = new StringBuilder();
            for (byte b : bytes){
                //将字节值转换为2位十六进制字符串
                ret.append(String.format("%02x",b));
            }
            return ret.toString();
        }
    }
}

使用MessageDigest时,我们首先根据哈希算法获取一个MessageDigest实例,然后,反复调用update(byte[])输入数据。当输入结束后,调用digest()方法获得byte[]数组表示的摘要,最后,把它转换为十六进制的字符串

Ⅱ.加密图片信息

//按照MD5算法对图片进行“加密”
public class ImageHash {
    public static void main(String[] args) throws IOException, NoSuchAlgorithmException {

        //图片原始字节内容
        byte[] images = Files.readAllBytes(Paths.get("d:\\text\\java.png"));
        
        //创建基于MD5算法的消息摘要对象
        MessageDigest md5 = MessageDigest.getInstance("md5");

        //原始字节内容(图片)
        md5.update(images);

        //获取加密摘要
        byte[] disest = md5.digest();
        System.out.println(Arrays.toString(disest));
        System.out.println(HashTools.bytesToHex(disest));
        System.out.println(disest.length);//MD5算法固定输出长度为16个字节

    }
}
//Hash算法(信息摘要算法)工具
public class HashTools{
    private HashTools(){}
    public static String bytesToHex(byte[] bytes){
        StringBuilder ret = new StringBuilder();
        //将字节数组转换为16进制字符串
        public static String bytesToHex(byte[] bytes){
            StringBuilder ret = new StringBuilder();
            for (byte b : bytes){
                //将字节值转换为2位十六进制字符串
                ret.append(String.format("%02x",b));
            }
            return ret.toString();
        }
    }

对比两个实现方法不难发现,Hash算法工具代码块一直重复出现,所有我们可以将这部分代码封装在HashTools方法中

//Hash算法工具类
public class HashTools {
    private HashTools(){}//构造方法私有
    private static MessageDigest disgest;//消息摘要对象

    //按照MD5进行消息摘要计算
    public static String disgestByMD5(String source) throws NoSuchAlgorithmException {
        disgest = MessageDigest.getInstance("MD5");
        return handler(source);
    }

    //按照SHA1进行消息摘要计算
    public static String disgestBySHA1(String source) throws NoSuchAlgorithmException {
        disgest = MessageDigest.getInstance("SHA-1");
        return handler(source);
    }

    //通过信息摘要对象,处理加密内容
    private static String handler(String source){
        disgest.update(source.getBytes());//调用update输入数据
        byte[] bytes = disgest.digest();//调用digest()方法获得由byte[]数组表示的摘要
        String hash = bytesToHex(bytes);
        return hash;
    }
    //将字节数组转换为16进制字符串
    public static String bytesToHex(byte[] bytes){
        StringBuilder ret = new StringBuilder();
        for (byte b : bytes){
            //将字节值转换为2位十六进制字符串
            ret.append(String.format("%02x",b));
        }
        return ret.toString();
    }
}

SHA-1:

MD5与SHA-1算法原理相同,区别仅在于输出长度与字节大小的差异,我们可直接将SHA-1的实现方法封装在HashTools方法中(在Java中使用SHA-1,和MD5完全一样,只需要把算法名称改为"SHA-1")

public class hashdemo {
    public static void main(String[] args) throws NoSuchAlgorithmException {

        //创建基于MD5算法的消息摘要对象
        //MessageDigest md5 = MessageDigest.getInstance("md5");
        //创建基于SHA-1算法的消息摘要对象
        //MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
        
        
        //通过调用HashTools实现
        String md5 = HashTools.disgestByMD5("落花有意");
        String sha1 = HashTools.disgestBySHA1("流水无情");
        
        System.out.println(md5);
        System.out.println(sha1);
    }
}

哈希算法用途:

校验下载文件:

因为相同的输入永远会得到相同的输出,因此,如果输入被修改了,得到的输出就会不同。如何判断下载到本地的软件是原始的、未经篡改的文件?我们只需要自己计算一下本地文件的哈希值,再与官网公开的哈希值对比,如果相同,说明文件下载正确,否则,说明文件已被篡改。

存储用户密码:

哈希算法的另一个重要用途是存储用户口令。如果直接将用户的原始口令存放到数据库中,会产生极大的安全风险:
数据库管理员能够看到用户明文口令
数据库数据一旦泄漏,黑客即可获取用户明文口令

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

这样一来,数据库管理员看不到用户的原始口令。即使数据库泄漏,黑客也无法拿到用户的原始口令。想要拿到用户的原始口令,必须用暴力穷举的方法,一个口令一个口令地试,直到某个口令计算的MD5恰好等于指定值。
使用哈希口令时,还要注意防止彩虹表攻击
什么是彩虹表?上面讲到,如果只拿到MD5,从MD5反推明文口令,只能使用暴力穷举的方法。然而黑客并不笨,暴力穷举会消耗大量的算力和时间。但是,如果有一个预先计算好的常用口令和它们的MD5的对照表,这个表就是彩虹表。如果用户使用了常用口令,黑客从MD5一下就能反查到原始口令(这就是为什么不要使用常用密码,以及不要使用生日作为密码的原因)

当然,我们也可以采取特殊措施来抵御彩虹表攻击:对每个口令额外添加随机数,这个方法称之为加盐(salt): digest = md5(salt + inputPassword)

代码实现:

public class HashPassWord {
    public static void main(String[] args) throws NoSuchAlgorithmException {

        //原始密码
        String password = "我本将心向明月";
        //创建基于MD5算法的消息摘要对象
        MessageDigest md5 = MessageDigest.getInstance("md5");

        //产生随机盐值
        String salt = UUID.randomUUID().toString().substring(0,4);


        md5.update(password.getBytes());//原始密码
        md5.update(salt.getBytes());//盐值

        String disgest = HashTools.bytesToHex(md5.digest());//计算加密结果
        System.out.println(disgest);
    }
}

But,自己手动加"盐"还是过于麻烦且安全指数直线下降,所以在这里我们需要来了解一种更为安全的算法:Hmac

Hmac算法就是一种基于密钥的消息认证码算法,它的全称是Hash-based Message Authentication Code,是一种更安全的消息摘要算法。
Hmac算法总是和某种哈希算法配合起来用的。例如,我们使用MD5算法,对应的就是Hmac MD5算法,它相当于“加盐”的MD5:HmacMD5 ≈ md5(secure_random_key, input)
因此,HmacMD5可以看作带有一个安全的key的MD5。使用HmacMD5而不是用MD5加salt,有如下好处:
HmacMD5使用的key长度是64字节,更安全
Hmac是标准算法,同样适用于SHA-1等其他哈希算法
Hmac输出和原有的哈希算法长度一致
可见,Hmac本质上就是把key混入摘要的算法。验证此哈希时,除了原始的输入数据,还要提供key。为了保证安全,我们不会自己指定key,而是通过Java标准库的KeyGenerator生成一个安全的随机的key
下面是使用HmacMD5的参考代码:

public class hmac {
    public static void main(String[] args) {

        String index = "我本将心向明月";
        try {
            //获取HmacMD5密钥生成器,产生密钥
            KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");

            //生成密钥
            SecretKey key = keyGen.generateKey();
            System.out.println(Arrays.toString(key.getEncoded()));
            System.out.println(key.getEncoded().length);
            System.out.println(HashTools.bytesToHex(key.getEncoded()));

            //使用密钥,进行加密
            //获取HMac加密算法对象
            Mac mac = Mac.getInstance("HmacMD5");
            mac.init(key);//初始化密钥
            mac.update(index    .getBytes());//更新原始加密内容
            byte[] bytes = mac.doFinal();//加密处理,获取加密结果
            String result = HashTools.bytesToHex(bytes);//加密结果处理为16进制字符串
            System.out.println(result);
            System.out.println(bytes.length);
            System.out.println(result.length());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
    }
}

和MD5相比,使用HmacMD5的步骤是:
1通过名称HmacMD5获取KeyGenerator实例
2通过KeyGenerator创建一个SecretKey实例
3通过名称HmacMD5获取Mac实例
4用SecretKey初始化Mac实例
5对Mac实例反复调用update(byte[])输入数据
6调用Mac实例的doFinal()获取最终的哈希值

有了Hmac计算的哈希和SecretKey,我们想要验证怎么办?这时,SecretKey不能从KeyGenerator生成,而是从一个byte[]数组恢复:

public class DisHmac {
    public static void main(String[] args) {
        //原始密码
        String password = "我本将心向明月";

        //按照字节数组恢复Hmac密钥
        //字节密钥
        byte[] key = {-93, -65, -45, 40, 119, -32, -118, 83, 90, -122, -10, 54, -8, 84, -48, 75, 21, -93, 58, 28, 124, 107, -89, -64, 18, -4, 50, -18, -27, -118, -45, 114, 21, 45, 112, 123, 124, -85, -109, 93, 74, 99, -12, -36, -85, -93, -20, -12, -2, 33, 123, -53, -69, -66, 53, -76, -65, 54, -62, -77, 111, 30, -86, 80};
         //持有字符密钥时恢复密钥
        //String key = "a3bfd32877e08a535a86f636f854d04b15a33a1c7c6ba7c012fc32eee58ad372152d707b7cab935d4a63f4dcaba3ecf4fe217bcbbbbe35b4bf36c2b36f1eaa50";

        //保存密钥,长度为64字节
        //byte[] keyword = new byte[64];
        //for (int i = 0,k = 0;i<key.length();i+=2,k++){
            //String s = key.substring(i,i+2);
           //keyword[k] = (byte)Integer.parseInt(s,16);//转化为16进制byte值
        //}
        //System.out.println(Arrays.toString(keyword));//可观察到将密钥转化为字节数组
        try {
            //恢复密钥
            SecretKey secretKey = new SecretKeySpec(key,"HmacMD5");

            //创建Hmac加密算法对象
            Mac mac = Mac.getInstance("HmacMD5");
            mac.init(secretKey);//初始化密钥
            mac.update(password.getBytes());
            String result = HashTools.bytesToHex(mac.doFinal());

            //f5aa6840c061ffac15e69f2b0ee3b59c
            System.out.println(result);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
    }
}

Java标准库提供了一系列常用的哈希算法。但如果我们要用的某种算法,Java标准库没有提供时,抛开自己写一个这种不实际的想法,我们还可以找一个第三方库,BouncyCastle就是一个提供了很多哈希算法和加密算法的第三方开源库。它提供了Java标准库没有的一些算法,例如,RipeMD160哈希算法:

//使用第三方开源库提供的RipeMD160信息摘要算法实现
public class RipeMD160 {
    public static void main(String[] args) throws NoSuchAlgorithmException {
        //注册BouncyCastleBouncyCastleProvider通知类
        //将提供的消息类摘要算法注册至Security
        Security.addProvider(new BouncyCastleProvider());
        //获取RipeMD160算法的”信息摘要对象“(加密对象)
        MessageDigest ripeMd160 = MessageDigest.getInstance("RipeMD160");
        //更新原始数据
        ripeMd160.update("wbjxxmy".getBytes());

        //获取信息摘要(加密)
        byte[] result = ripeMd160.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);
    }
}

小结:

1.哈希算法可用于验证数据完整性,具有防篡改检测的功能
2.常用的哈希算法有MD5、SHA-1等
3.用哈希存储口令时要考虑彩虹表攻击

4.BouncyCastle是一个开源的第三方算法提供商,提供了很多Java标准库没有提供的哈希算法和加密算法

5.Hmac算法是一种标准的基于密钥的哈希算法,可以配合MD5、SHA-1等哈希算法,计算的摘要长度和原摘要算法长度相同。

                                                                                          向上攀爬的痛苦,终会在登顶时烟消云散

                                       ——ZQY

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
OpenCV是一个开源的计算机视觉库,可用于处理图像和视频数据。它提供了多种功能和算法,例如图像相似度检测和图像哈希。 图像相似度检测是通过比较两个图像之间的差异程度来判断它们的相似度。OpenCV有几种方法可以实现图像相似度检测,例如结构相似性指数(SSIM)和均方误差(MSE)。这些方法可以帮助我们了解两个图像之间的相似性,从而用于识别和匹配图像。 图像哈希是一种将图像转换为固定长度哈希码的技术。在OpenCV,可以使用感知哈希算法(pHash)或块哈希算法(BlockHash)来实现图像哈希哈希码可以帮助我们快速比较图像的相似性,而不需要直接比较图像本身。例如,我们可以使用哈希码来查找数据库是否存在相似的图像。 Java是一种常用的编程语言,它也可以与OpenCV一起使用来实现图像相似度检测和图像哈希。在Java,可以使用OpenCV的Java接口来调用相关的函数和方法。通过结合Java和OpenCV,我们可以编写代码来实现图像相似度检测和图像哈希的功能,并且能够进行更复杂的图像处理和分析。 综上所述,OpenCV可以帮助我们实现图像相似度检测和图像哈希的功能,而Java可以与OpenCV一起使用来编写相应的代码。通过利用这些功能,我们可以在图像处理和计算机视觉领域进行更高效和准确的图像分析和处理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟0917

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值