Java编码算法与哈希算法深入分析使用方法

目录
  • 一、编码算法
    • 1.什么是编码
    • 2.URL编码
    • 3.Base64编码
  • 二、哈希算法
    • 1.概述
    • 2.哈希碰撞
    • 3.常用哈希算法
      • ①.MD5
      • ②.SHA-1
      • ③.RipeMD-160
    • 4.哈希算法的用途
  • 三、小结

一、编码算法

1.什么是编码

ASCII 码就是一种编码,字母 A 的编码是十六进制的 0x41 ,字母 B 是 0x42 ,以此类推。

因为 ASCII 编码最多只能有 127 个字符,要想对更多的文字进行编码,就需要用占用 2个字节的 Unicode 。而中文的"中"字使用 Unicode 编码就是 0x4e2d ,使用 UTF8 则需要 3 个字节编码;因此,最简单的编码是直接给每个字符指定一个若干字节表示的整数,复杂一点的 编码就需要根据一个已有的编码推算出来。比如 UTF-8 编码,它是一种不定长编码, 但可以从给定字符的 Unicode 编码推算出来。

2.URL编码

URL 编码是浏览器发送数据给服务器时使用的编码,它通常附加在 URL 的参数部 分,例如: https://www.baidu.com/s?wd=%E4%B8%AD%E6%96%87

之所以需要 URL 编码,是因为出于兼容性考虑,很多服务器只识别 ASCII 字符。 但如果 URL 中包含中文、日文这些非 ASCII 字符怎么办?不要紧, URL 编码有一套 规则:

  • 如果字符是 A ~ Z , a ~ z , 0 ~ 9 以及 - 、 _ 、 . 、 * ,则保持不变;
  • 如果是其他字符,先转换为 UTF-8 编码,然后对每个字节以 %XX 表示。

例如:字符"中"的 UTF-8 编码是 0xe4b8ad ,因此,它的 URL 编码 是 %E4%B8%AD 。 URL 编码总是大写。

Java 标准库提供了一个 URLEncoder 类来对任意字符串进行 URL 编码:

import java.net.URLEncoder;
public class Main {
    public static void main(String[] args) {
        String encoded = URLEncoder.encode("中文!", "utf-8");
		System.out.println(encoded);
    }
}

上述代码的运行结果是 %E4%B8%AD%E6%96%87%21 ,"中"的 URL 编码 是 %E4%B8%AD ,"文"的URL编码是 %E6%96%87 , ! 虽然是 ASCII 字符,也要对其编 码为 %21 。

如果服务器收到 URL 编码的字符串,就可以对其进行解码,还原成原始字符串。 Java 标准库的 URLDecoder 就可以解码:

public class Main {
    public static void main(String[] args) {
        String decoded = URLDecoder.decode("%E4%B8%AD%E6%96%87%21", "utf-8");
		System.out.println(decoded);
    }
}

特别注意: URL 编码是编码算法,不是加密算法。 URL 编码的目的是把任意文本数据编码为 % 前缀 表示的文本,编码后的文本仅包含 A ~ Z , a ~ z , 0 ~ 9 , - , _ , . , * 和 % ,便于浏览 器和服务器处理。

3.Base64编码

URL 编码是对字符进行编码,表示成 %xx 的形式,而 Base64 编码是对二进制数 据进行编码,表示成文本格式。

Base64 编码可以把任意长度的二进制数据变为纯文本,并且纯文本内容中且只包 含指定字符内容: A ~ Z 、 a ~ z 、 0 ~ 9 、 + 、 / 、 = 。它的原理是把 3 字 节的二进制数据按 6bit 一组,用 4 个int整数表示,然后查表,把 int 整数用索引对 应到字符,得到编码后的字符串。

6 位整数的范围总是 0 ~ 63 ,所以,能用 64 个字符表示:字符 A ~ Z 对应索 引 0 ~ 25 ,字符 a ~ z 对应索引 26 ~ 51 ,字符 0 ~ 9 对应索引 52 ~ 61 ,最 后两个索引 62 、 63 分别用字符 + 和 / 表示。

举个例子: 3 个 byte 数据分别是 e4 、 b8 、 ad ,按 6bit 分组得到 39 、 0b 、 22 和 2d :

┌───────────────┬───────────────┬───────────────┐
│      e4       │      b8       │      ad       │
└───────────────┴───────────────┴───────────────┘
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│1│1│1│0│0│1│0│0│1│0│1│1│1│0│0│0│1│0│1│0│1│1│0│1│
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
┌───────────┬───────────┬───────────┬───────────┐
│    39     │    0b     │    22     │    2d     │
└───────────┴───────────┴───────────┴───────────┘

在 Java 中,二进制数据就是 byte[] 数组。 Java 标准库提供了 Base64 来对 byte[] 数组进行编解码:

public class Main {
    public static void main(String[] args) {
        byte[] input = new byte[] { (byte) 0xe4, (byte) 0xb8, (byte) 0xad };
        String b64encoded = Base64.getEncoder().encodeToString(input);
        System.out.println(b64encoded);
    }
}

编码后得到字符串结果: 5Lit4 。要对这个字符使用 Base64 解码,仍然用 Base64 这个类:

public class Main {
    public static void main(String[] args) {
        byte[] output = Base64.getDecoder().decode("5Lit");
        System.out.println(Arrays.toString(output)); // [-28, -72, -83]
    }
}

因为标准的 Base64 编码会出现 + 、 / 和 = ,所以不适合把 Base64 编码后的字符串放到 URL 中。 一种针对 URL 的 Base64 编码可以在 URL 中使用的 Base64 编码,它仅仅是把 + 变成 - , / 变成 _ :

public class Main {
    public static void main(String[] args) {
        // 原始字节内容
		byte[] input = new byte[] { 0x01, 0x02, 0x7f, 0x00 };
		// 分别使用两种方式进行编码
		String b64Encode = Base64.getEncoder().encodeToString(input);
        String b64UrlEncoded = Base64.getUrlEncoder().encodeToString(input);
        // 结果完全一致
        System.out.println(b64Encode);
        System.out.println(b64UrlEncoded);
        // 分别使用两种方式进行重新解码
        byte[] output1 = Base64.getDecoder().decode(b64Encode);
        System.out.println(Arrays.toString(output1));
        byte[] output2 = Base64.getUrlDecoder().decode(b64UrlEncoded);
        System.out.println(Arrays.toString(output2));
    }
}
  • Base64 编码的目的是把二进制数据变成文本格式,这样在很多文本中就可以处理二进 制数据。例如,电子邮件协议就是文本协议,如果要在电子邮件中添加一个二进制文 件,就可以用 Base64 编码,然后以文本的形式传送。
  • Base64 编码的缺点是传输效率会降低,因为它把原始数据的长度增加了1/3。和 URL 编码一样, Base64 编码是一种编码算法,不是加密算法。
  • 如果把 Base64 的 64 个字符编码表换成 32 个、 48 个或者 58 个,就可以使 用 Base32 编码, Base48 编码和 Base58 编码。字符越少,编码的效率就会越低。

二、哈希算法

1.概述

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

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

  • 相同的输入一定得到相同的输出;
  • 不同的输入大概率得到不同的输出。

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

"hello".hashCode(); // 0x5e918d2
"hello, java".hashCode(); // 0x7a9d88e8
"hello, bob".hashCode(); // 0xa0dbae2f

两个相同的字符串永远会计算出相同的 hashCode ,否则基于 hashCode 定位的 HashMap 就无法正常工 作。这也是为什么当我们自定义一个 class 时,覆写 equals() 方法时我们必须正确覆写 hashCode() 方法。

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") = ???

3.常用哈希算法

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

①.MD5

import java.security.MessageDigest;
public class main {
	public static void main(String[] args)  {
		// 创建一个MessageDigest实例:
        MessageDigest md = MessageDigest.getInstance("MD5");
        // 反复调用update输入数据:
        md.update("Hello".getBytes("UTF-8"));
        md.update("World".getBytes("UTF-8"));
        // 16 bytes: 68e109f0f40ca72a15e05cc22786f8e6
        byte[] results = md.digest();
        StringBuilder sb = new StringBuilder();
        for(byte bite : results) {
        	sb.append(String.format("%02x", bite));
        }
        System.out.println(sb.toString());
	}
}

运行上述代码,可以得到输入HelloWorld 的 MD5 是 68e109f0f40ca72a15e05cc22786f8e6

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

②.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());
	}
}

类似的,计算 SHA-256 ,我们需要传入名称" SHA-256 ",计算 SHA-512 ,我们需要传入名称" SHA-512 "。

③.RipeMD-160

BouncyCastle是一个提供了很多哈希算法和加密算法的第三方开源库。它提供了 Java 标准库没 有的一些算法,例如, RipeMD160 哈希算法。 RIPEMD160 是一种基于 Merkle-Damgård 结构的加密哈希函数,它是比特币标准之一。 RIPEMD-160 是 RIPEMD 算法的增强版本, RIPEMD-160 算法可以产生出 160 位的的哈希摘要。

用法:

  • 首先,我们必须把 BouncyCastle 提供的 bcprov-jdk15on-1.70.jar 添加至 classpath 。
  • 其次,Java标准库的 java.security 包提供了一种标准机制,允许第三方提供商无缝接入。我们要使用 Bouncy Castle 提供的 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);
    }
}

4.哈希算法的用途

校验下载文件

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

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

存储用户密码

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

三、小结

  • URL 编码和 Base64 编码都是编码算法,它们不是加密算法;
  • URL 编码的目的是把任意文本数据编码为 % 前缀表示的文本,便于浏览器和服务器处理;
  • Base64 编码的目的是把任意二进制数据编码为文本,但编码后数据量会增加 1/3 。
  • 哈希算法可用于验证数据完整性,具有防篡改检测的功能;
  • 常用的哈希算法有 MD5 、SHA-1 等;
  • 用哈希存储口令时要考虑彩虹表攻击。

到此这篇关于Java编码算法与哈希算法深入分析使用方法的文章就介绍到这了,更多相关Java编码算法与哈希算法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java 数据结构与算法系列精讲之哈希算法实现

    概述 从今天开始, 小白我将带大家开启 Java 数据结构 & 算法的新篇章. 获取哈希值 hashCode()方法可以返回一个对象的哈希值. 需要注意的是, 我们需要对值进行装箱, 才能调用 hashCode()方法. 例子: public static void main(String[] args) { // 小数 Integer a = 1; System.out.println(a.hashCode()); // 负数 Integer b = -1; System.out.printl

  • java哈希算法HashMap经典面试题目汇总解析

    目录 1.HashMap的数据结构? 2.HashMap的工作原理? 3.当两个对象的hashCode相同会发生什么? 4.你知道hash的实现吗?为什么要这样实现? 5.为什么要用异或运算符? 6.HashMap的table的容量如何确定? 7.HashMap中put方法的过程? 8.数组扩容的过程? 9.为什么不一直使用红黑树? 10.说说你对红黑树的见解? 11.jdk8中对HashMap做了哪些改变? 12.HashMap,LinkedHashMap,TreeMap有什么区别? 13.H

  • 一篇文章读懂Java哈希与一致性哈希算法

    目录 哈希 Hash 算法介绍 分布式存储场景 场景描述: 实现思路: 缺点: 一致性Hash算法 节点增加场景 节点减少场景 节点分布不均匀 虚拟节点 增加节点 节点减少 总结 哈希 Hash 算法介绍 哈希算法也叫散列算法, 不过英文单词都是 Hash, 简单一句话概括, 就是可以把任意长度的输入信息通过算法变换成固定长度的输出信息, 输出信息也就是哈希值, 通常哈希值的格式是16进制或者是10进制, 比如下面的使用 md5 哈希算法的示例 md5("123456") =>

  • 详解Java分布式系统中一致性哈希算法

    业务场景 近年来B2C.O2O等商业概念的提出和移动端的发展,使得分布式系统流行了起来.分布式系统相对于单系统,解决了流量大.系统高可用和高容错等问题.功能强大也意味着实现起来需要更多技术的支持.例如系统访问层的负载均衡,缓存层的多实例主从复制备份,数据层的分库分表等. 我们以负载均衡为例,常见的负载均衡方法有很多,但是它们的优缺点也都很明显: 随机访问策略.系统随机访问,缺点:可能造成服务器负载压力不均衡,俗话讲就是撑的撑死,饿的饿死. 轮询策略.请求均匀分配,如果服务器有性能差异,则无法实现

  • Java 数据结构哈希算法之哈希桶方式解决哈希冲突

    一. 实现形式一(键值对只能为整数) 我们可以先实现一个比较简单的哈希表,使用java中解决哈希冲突的方法,即哈希桶(开散列)方式实现,其中注意: 可以使用内部类方式定义节点 负载因子默认为0.75 因为我们使用的是哈希桶方式解决哈希冲突,所以在我们扩容成功之后,原来桶中的数据得重新哈希计算出新的位置,不然就和原来桶中的数据的位置不一样了 相关代码如下 public class HashBucket { static class Node {//使用内部类方式定义节点 public int ke

  • Java编码算法与哈希算法深入分析使用方法

    目录 一.编码算法 1.什么是编码 2.URL编码 3.Base64编码 二.哈希算法 1.概述 2.哈希碰撞 3.常用哈希算法 ①.MD5 ②.SHA-1 ③.RipeMD-160 4.哈希算法的用途 三.小结 一.编码算法 1.什么是编码 ASCII 码就是一种编码,字母 A 的编码是十六进制的 0x41 ,字母 B 是 0x42 ,以此类推. 因为 ASCII 编码最多只能有 127 个字符,要想对更多的文字进行编码,就需要用占用 2个字节的 Unicode .而中文的"中"字使

  • 用Python实现通过哈希算法检测图片重复的教程

    Iconfinder 是一个图标搜索引擎,为设计师.开发者和其他创意工作者提供精美图标,目前托管超过 34 万枚图标,是全球最大的付费图标库.用户也可以在 Iconfinder 的交易板块上传出售原创作品.每个月都有成千上万的图标上传到Iconfinder,同时也伴随而来大量的盗版图.Iconfinder 工程师 Silviu Tantos 在本文中提出一个新颖巧妙的图像查重技术,以杜绝盗版. 我们将在未来几周之内推出一个检测上传图标是否重复的功能.例如,如果用户下载了一个图标然后又试图通过上传

  • java数据结构和算法中哈希表知识点详解

    树的结构说得差不多了,现在我们来说说一种数据结构叫做哈希表(hash table),哈希表有是干什么用的呢?我们知道树的操作的时间复杂度通常为O(logN),那有没有更快的数据结构?当然有,那就是哈希表: 1.哈希表简介 哈希表(hash table)是一种数据结构,提供很快速的插入和查找操作(有的时候甚至删除操作也是),时间复杂度为O(1),对比时间复杂度就可以知道哈希表比树的效率快得多,并且哈希表的实现也相对容易,然而没有任何一种数据结构是完美的,哈希表也是:哈希表最大的缺陷就是基于数组,因

  • java实现的RC4加密解密算法示例

    本文实例讲述了java实现的RC4加密解密算法.分享给大家供大家参考,具体如下: 有一个项目,需要解析一个用户提供的rc4加密后的文件,特意搜索整理了一个Java 版本的RC4加解密算法. public static String HloveyRC4(String aInput,String aKey) { int[] iS = new int[256]; byte[] iK = new byte[256]; for (int i=0;i<256;i++) iS[i]=i; int j = 1;

  • Java实现的RSA加密解密算法示例

    本文实例讲述了Java实现的RSA加密解密算法.分享给大家供大家参考,具体如下: import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Image; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.

随机推荐