Java基础29(编码算法 哈希算法 MD5 SHA—1 HMac 算法 堆成加密算法)
目录
一、编码算法
1. 常见编码
2. URL编码
3. Base64编码
4. 小结
二、哈希算法
1. 哈希碰撞
2. 常用哈希算法
MD5算法
SHA-1算法
自定义HashTools工具类
3. 哈希算法的用途
校验下载文件
存储用户密码
4. 小结
三、Hmac算法
小结:
四、对称加密算法
1. 使用AES加密
ECB模式
一、编码算法
ASCII码就是一种编码,字母A的编码是十六进制的0x41,字母B是0x42,以此类推:
因为ASCII编码最多只能有127个字符,要想对更多的文字进行编码,就需要用占用2个字节的Unicode或者3个字节的UTF-8。例如:中文的"中"字使用Unicode编码就是0x4e2d,UTF-8编码是0xe4b8ad。
字母
ASCII编码
A
0x41
B
0x42
C
0x43
D
0x44
…
…
因此,最简单的编码是直接给每个字符指定一个若干字节表示的整数,复杂一点的编码就需要根据一个已有的编码推算出来。
汉字
Unicode编码
UTF-8编码
中
0x4e2d
0xe4b8ad
文
0x6587
0xe69687
编
0x7f16
0xe7bc96
码
0x7801
0xe7a081
1. 常见编码
ASCII码:ASCII码中只存储英文字符,有127个字符,占一个字节
Unicode码:可以存储中文,占两个字节
UTF-8:实际上是从Unicode码中推算出来的一种编码方式
URL编码:是浏览器发送数据时的一种编码,通常附加在URL的参数部分。之所以需要,是因为很多服务器只能识别ASCII字符,对字符进行编码,URL它独有一套自己的编码规则。在java的类库中提供了URLEncoder类,来对任意字符串进行URL编码。
如果服务器收到URL编码,会对它自动进行解码
Base64编码:对二进制数据进行编码,转换成文本格式,这样在很多文本中就可以处理进制数据。
Base64编明是种编码算法。不是加密算法。
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编码://URLEncoder,url编码操作 //URLDecoder类,url解码操作 public class Demo01 {public static void main(String[] args) throws UnsupportedEncodingException {// 编码String keyWorld = "中文";String encodeString = URLEncoder.encode(keyWorld, "utf-8");System.out.println("编码:" + encodeString);//编码:%E4%B8%AD%E6%96%87} }
如果服务器收到URL编码的字符串,就可以对其进行解码,还原成原始字符串。Java标准库的URLDecoder就可以解码:
// 解码String decodeString = URLDecoder.decode(encodeString, "utf-8");System.out.println(decodeString);//中文
要特别注意:URL编码是编码算法,不是加密算法。URL编码的目的是把任意文本数据编码为%前缀表示的文本,编码后的文本仅包含A~Z,a~z,0~9,-,_,.,*和%,便于浏览器和服务器处理。
//URL编码注意: public class Demo02 {public static void main(String[] args) throws UnsupportedEncodingException {//编码只对中文内容进行处理 String keyWorld = "特斯拉";String encodeString = URLEncoder.encode(keyWorld, "utf-8");String urlString = "https://www.baidu.com/s?wd=";System.out.println(urlString + encodeString);//https://www.baidu.com/s?wd=%E7%89%B9%E6%96%AF%E6%8B%89//解码操作,url可以对整个链接进行解码String decodeString = URLDecoder.decode(urlString + encodeString, "utf-8");System.out.println(decodeString);//https://www.baidu.com/s?wd=特斯拉} }
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,分别对应十进制57、11、34、45,通过索引计算结果为5Lit4
在Java中,二进制数据就是byte[]数组。Java标准库提供了Base64来对byte[]数组进行编解码:
//Base64编码 //3字节信息一组,24-6位 4字符 public class Demo03 {public static void main(String[] args) {String string = "中";byte[] bytes = string.getBytes();// JDK1.8后才提供String encodeString = Base64.getEncoder().encodeToString(bytes);System.out.println(encodeString);// 5Lit// 解码byte[] decodeBytes = Base64.getDecoder().decode(encodeString);System.out.println(Arrays.toString(decodeBytes));// [-28, -72, -83]System.out.println(new String(decodeBytes));// 中} }
将图片使用base64转成字符串并保存到a.txt中:
//将图片使用base64转成字符串并保存到a.txt中 //将a.txt中的信息使用base64解码,还原成图片写出 public class Demo04 {public static void main(String[] args) throws IOException {// 将一个图片进行编码,文件进行保存byte[] bytes = Files.readAllBytes(Paths.get("C:\\Users\\张柯堂\\Desktop\\头像.png"));String str = Base64.getEncoder().encodeToString(bytes);Files.write(Paths.get("a.txt"), str.getBytes());System.out.println("写出图片字符信息结束");// 解码// 读入信息byte[] readBytes = Files.readAllBytes(Paths.get("a.txt"));byte[] decodeBytes = Base64.getDecoder().decode(readBytes);Files.write(Paths.get("C:\\Users\\张柯堂\\Desktop\\copy.png"), decodeBytes);System.out.println("写出图片结束");} }
因为标准的Base64编码会出现+、/和=,所以不适合把Base64编码后的字符串放到URL中。一种针对URL的Base64编码可以在URL中使用的Base64编码,它仅仅是把+变成-,/变成_:
public class Demo05 {public static void main(String[] args) {//+ /// 原始字节内byte[] input = new byte[] {2,-114,127,0};//使用两种编码方式对input转换/-->_ +-->-String str = Base64.getEncoder().encodeToString(input);String str1 = Base64.getUrlEncoder().encodeToString(input);System.out.println(str);//Ao5/AA==System.out.println(str1);//Ao5_AA==//解码操作byte[] byte1 =Base64.getDecoder().decode(str);byte[] byte2 =Base64.getUrlDecoder().decode(str1);//输出解码后的内容System.out.println(Arrays.toString(byte1));//[2, -114, 127, 0]System.out.println(Arrays.toString(byte2));//[2, -114, 127, 0]} }
4. 小结
- URL编码和Base64编码都是编码算法,它们不是加密算法;
- URL编码的目的是把任意文本数据编码为%前缀表示的文本,便于浏览器和服务器处理;
- Base64编码的目的是把任意二进制数据编码为文本,但编码后数据量会增加1/3。
二、哈希算法
哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。
哈希算法最重要的特点就是:
- 相同的输入一定得到相同的输出;
- 不同的输入大概率得到不同的输出。
所以,哈希算法的目的:为了验证原始数据是否被篡改。
两个相同的字符串永远会计算出相同的hashCode,否则基于hashCode定位的HashMap就无法正常工作。这也是为什么当我们自定义一个class时,覆写equals()方法时我们必须正确覆写hashCode()方法。
1. 哈希碰撞
哈希碰撞是指:两个不同的输入得到了相同的输出。
碰撞能不能避免?答案是不能。碰撞是一定会出现的,因为输出的字节长度是固定的,String的hashCode()输出是4字节整数,最多只有4294967296种输出,但输入的数据长度是不固定的,有无数种输入。所以,哈希算法是把一个无限的输入集合映射到一个有限的输出集合,必然会产生碰撞。
碰撞不可怕,我们担心的不是碰撞,而是碰撞的概率,因为碰撞概率的高低关系到哈希算法的安全性。一个安全的哈希算法必须满足:
- 碰撞概率低;
- 不能猜测输出。
不能猜测输出是指:输入的任意一个bit的变化会造成输出完全不同,这样就很难从输出反推输入(只能依靠暴力穷举)。
2. 常用哈希算法
哈希算法,根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全。
常用的哈希算法有:
算法
输出长度(位)
输出长度(字节)
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算法
Java标准库提供了常用的哈希算法,通过统一的接口进行调用。 以MD5算法为例,看看如何对输入内容计算哈希:public class Demo01 {public static void main(String[] args) throws NoSuchAlgorithmException {//获取消息摘要对象MessageDigest md = MessageDigest.getInstance("MD5");//1.使用Md5进行消息加密 // byte[]str ="还想继续考研".getBytes(); // byte[]b=md.digest(str); // System.out.println("加密前的信息:"+Arrays.toString(str));//加密前的信息:[-24, -65, -104, -26, -125, -77, -25, -69, -89, -25, -69, -83, -24, -128, -125, -25, -96, -108] // System.out.println("加密后的信息:"+Arrays.toString(b));//加密后的信息:[-119, -28, -47, 120, 114, -79, 7, -1, 59, -98, -69, 65, -37, -111, -87, -120] // System.out.println("加密信息的长度:"+b.length);//加密信息的长度:16// // 2.大量的数据进行加密md.update("还是想考研".getBytes());md.update("zkt".getBytes());md.update("c罗".getBytes());//3.加密byte[] result = md.digest();System.out.println("加密后的信息"+Arrays.toString(result));//加密后的信息[-52, 50, -102, -69, -95, 11, -59, -6, 68, -125, 74, -45, 6, 26, 108, 75]System.out.println("加密信息的长度"+result.length);//加密信息的长度16System.out.println("转16进制的字符串"+byteToString(result));//转16进制的字符串cc329abba10bc5fa44834ad3061a6c4b}public static String byteToString(byte[] b) {StringBuilder sb = new StringBuilder();for (byte c : b) {// 1.将10字节数字转成16进制的字符串sb.append(String.format("%02x", c));}return sb.toString();} }
使用MessageDigest时,我们首先根据哈希算法获取一个MessageDigest实例,然后,反复调用update(byte[])输入数据。当输入结束后,调用digest()方法获得byte[]数组表示的摘要,最后,把它转换为十六进制的字符串。
使用MD5算法来对图片进行加密
//使用MD5算法来对图片进行加密 public class Demo02 {public static void main(String[] args) throws IOException, NoSuchAlgorithmException {byte[] image =Files.readAllBytes(Paths.get("C:\\Users\\张柯堂\\Desktop\\头像.png"));//获取消息摘要对象MessageDigest md5 = MessageDigest.getInstance("MD5");//加密byte[] bytes = md5.digest(image);System.out.println(Demo01.byteToString(bytes));//21f50de91db2ce9a80ccfd2f916d13a0} }
SHA-1算法
SHA-1也是一种哈希算法,它的输出是160 bits,即20字节。SHA-1是由美国国家安全局开发的,SHA算法实际上是一个系列,包括SHA-0(已废弃)、SHA-1、SHA-256、SHA-512等。
在Java中使用SHA-1,和MD5完全一样,只需要把算法名称改为"SHA-1":public class Demo05 {public static void main(String[] args) throws NoSuchAlgorithmException {// 1.需要加密的信息byte[] passwold = "123456".getBytes();// 2.创建信息摘要对象MessageDigest sha_1 = MessageDigest.getInstance("SHA-1");// 3.update()sha_1.update(passwold);// 4.加密--16进制的字符串byte[] result = sha_1.digest();System.out.println("加密后的信息:" + Arrays.toString(result));System.out.println("加密后的长度:" + result.length);System.out.println(Demo01.byteToString(result));}}
自定义HashTools工具类
HashTools类:
public class HashTools {//创建消息摘要对象作为静态变量private static MessageDigest mesDig;//md5加密算法加密信息public static String messageMd5(byte[] bytes) throws NoSuchAlgorithmException {mesDig = MessageDigest.getInstance("MD5");return handle(bytes);}//sha-1加密算法加密信息public static String messageSha_1(byte[] bytes) throws NoSuchAlgorithmException {mesDig = MessageDigest.getInstance("SHA-1");return handle(bytes);}//进行加密操作public static String handle(byte[] bytes) {mesDig.update(bytes);byte[] result = mesDig.digest();return byteToString(result);}//将字节数组转16进制的字符串public static String byteToString(byte[] b) {StringBuilder sb = new StringBuilder();for (byte c : b) {// 1.将10字节数字转成16进制的字符串//2.追加到StringBuilder类中sb.append(String.format("%02x", c));}return sb.toString();}//str是个16进制的字符串,每两个字符表示一个字节信息//将16进制的字符串转10进制的字节数组public static byte[] stringTobyte(String str) {byte[] result = new byte[str.length() / 2];for (int i = 0, j = 0; i < str.length(); i = i + 2, j++) {//int num = Integer.parseInt(str.substring(i, i + 2), 16);Byte num = Byte.parseByte(str.substring(i, i + 2), 16);result[j] = (byte)num;}return result;} }
public class Demo06 {public static void main(String[] args) throws NoSuchAlgorithmException {byte[] passWorldString = "123456".getBytes();//MD5String resultMd5=HashTools.messageMd5(passWorldString);//SHA-1String reulstSha1=HashTools.messageSha_1(passWorldString);System.out.println(resultMd5.length());//32System.out.println(reulstSha1.length());//40}}
3. 哈希算法的用途
校验下载文件
因为相同的输入永远会得到相同的输出,因此,如果输入被修改了,得到的输出就会不同。我们在网站上下载软件的时候,经常看到下载页显示的MD5哈希值:如何判断下载到本地的软件是原始的、未经篡改的文件?我们只需要自己计算一下本地文件的哈希值,再与官网公开的哈希值对比,如果相同,说明文件下载正确,否则,说明文件已被篡改。
存储用户密码
哈希算法的另一个重要用途是存储用户口令。如果直接将用户的原始口令存放到数据库中,会产生极大的安全风险:
- 数据库管理员能够看到用户明文口令;
- 数据库数据一旦泄漏,黑客即可获取用户明文口令。
不存储用户的原始口令,那么如何对用户进行认证?方法是存储用户口令的哈希,例如,MD5。在用户输入原始口令后,系统计算用户输入的原始口令的MD5并与数据库存储的MD5对比,如果一致,说明口令正确,否则,口令错误。
username
password
bob
123456789
alice
sdfsdfsdf
tim
justdoit
因此,数据库存储用户名和口令的表内容应该像下面这样:这样一来,数据库管理员看不到用户的原始口令。即使数据库泄漏,黑客也无法拿到用户的原始口令。想要拿到用户的原始口令,必须用暴力穷举的方法,一个口令一个口令地试,直到某个口令计算的MD5恰好等于指定值。
username
password
bob
25f9e794323b453885f5181f1b624d0b
alice
73a90acaae2b1ccc0e969709665bc62f
tim
19f9f30bd097d4c066d758fb01b75032
使用哈希口令时,还要注意防止彩虹表攻击。
什么是彩虹表呢?上面讲到了,如果只拿到MD5,从MD5反推明文口令,只能使用暴力穷举的方法。然而黑客并不笨,暴力穷举会消耗大量的算力和时间。但是,如果有一个预先计算好的常用口令和它们的MD5的对照表,这个表就是彩虹表。如果用户使用了常用口令,黑客从MD5一下就能反查到原始口令:这就是为什么不要使用常用密码,以及不要使用生日作为密码的原因。
常用口令
MD5
hello123
f30aa7a662c728b7407c54ae6bfd27d1
12345678
25d55ad283aa400af464c76d713c07ad
passw0rd
bed128365216c019988915ed3add75fb
19700101
570da6d5277a646f6552b8832012f5dc
…
…
wbjxxmy
11d7a82f45f6a176fd9d5c100ccab40a
当然,我们也可以采取特殊措施来抵御彩虹表攻击:对每个口令额外添加随机数,这个方法称之为加盐(salt):
digest = md5(salt + inputPassword)
经过加盐处理的数据库表,内容如下:
username
salt
password
bob
H1r0a
a5022319ff4c56955e22a74abcc2c210
alice
7$p2w
e5de688c99e961ed6e560b972dab8b6a
tim
z5Sk9
1eee304b92dc0d105904e7ab58fd2f64
//通过随机盐值,低于彩虹表攻击 public class Demo03 {public static void main(String[] args) throws NoSuchAlgorithmException {byte[] passworld = "123456".getBytes();// 获取消息摘要对象MessageDigest md5 = MessageDigest.getInstance("MD5");// 原始密码信息添加进去md5.update(passworld);// 产生随机的盐值,并添加进去,进行加盐操作String uuidString = UUID.randomUUID().toString().substring(0, 6);md5.update(uuidString.getBytes());System.out.println(uuidString);//73242dbyte[] result = md5.digest();System.out.println("加密后的字节数组:" + Arrays.toString(result));//加密后的字节数组:[57, 38, 44, -69, -126, -103, 126, 24, 76, -101, -53, 105, 68, -84, -21, -104]System.out.println("加密后的字符串:" + Demo01.byteToString(result));//加密后的字符串:39262cbb82997e184c9bcb6944aceb98} }
4. 小结
- 哈希算法可用于验证数据完整性,具有防篡改检测的功能;
- 常用的哈希算法有MD5、SHA-1等;
- 用哈希存储口令时要考虑彩虹表攻击
三、Hmac算法
在前面讲到哈希算法时,我们说,存储用户的哈希口令时,要加盐存储,目的就在于抵御彩虹表攻击。我们回顾一下哈希算法:digest = hash(input)
正是因为相同的输入会产生相同的输出,我们加盐的目的就在于,使得输入有所变化:
digest = hash(salt + input)
这个salt可以看作是一个额外的“认证码”,同样的输入,不同的认证码,会产生不同的输出。因此,要验证输出的哈希,必须同时提供“认证码”。
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。
//Hmac算法-链接md5 public class Demo06 {public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException { // 1.通过名称HmacMD5获取KeyGenerator实例;KeyGenerator key = KeyGenerator.getInstance("HmacMD5"); // 2.通过KeyGenerator创建一个SecretKey实例;SecretKey secretKey = key.generateKey();System.out.println("密钥数组:"+Arrays.toString(secretKey.getEncoded()));System.out.println("密钥长度:"+secretKey.getEncoded().length);System.out.println("密钥字符串:"+HashTools.byteToString(secretKey.getEncoded()));// 3.通过名称HmacMD5获取Mac实例;Mac macMd5 = Mac.getInstance("HmacMD5"); // 4.用SecretKey初始化Mac实例;macMd5.init(secretKey); // 5.对Mac实例反复调用update(byte[])输入数据;byte[] bytes = "hxjxky".getBytes();macMd5.update(bytes); // 6.调用Mac实例的doFinal()获取最终的哈希值。byte[] result = macMd5.doFinal();System.out.println("密钥数组:"+Arrays.toString(result));System.out.println("密钥长度:"+result.length);System.out.println("密钥字符串:"+HashTools.byteToString(result));} }运行结果: 密钥数组:[-36, -58, -62, -96, 52, -96, -28, -43, 29, -20, -73, 126, 110, -120, 26, -2, -16, -79, 111, 48, -111, 119, -104, -76, -71, -61, 48, 1, 88, 45, 85, -112, -21, -94, -19, -104, 17, 13, 34, 23, 79, 13, 4, 96, 32, -105, -108, -10, 121, -47, -85, 49, 8, -108, 22, 116, 39, -126, 17, 56, 15, -84, 114, -94] 密钥长度:64 密钥字符串:dcc6c2a034a0e4d51decb77e6e881afef0b16f30917798b4b9c33001582d5590eba2ed98110d22174f0d0460209794f679d1ab3108941674278211380fac72a2 密钥数组:[-111, -50, 96, 58, -120, -34, -57, -74, 113, 124, -113, -70, -91, 39, 126, 58] 密钥长度:16 密钥字符串:91ce603a88dec7b6717c8fbaa5277e3a
和MD5相比,使用HmacMD5的步骤是:
- 通过名称HmacMD5获取KeyGenerator实例;
- 通过KeyGenerator创建一个SecretKey实例;
- 通过名称HmacMD5获取Mac实例;
- 用SecretKey初始化Mac实例;
- 对Mac实例反复调用update(byte[])输入数据;
- 调用Mac实例的doFinal()获取最终的哈希值。
我们可以用Hmac算法取代原有的自定义的加盐算法,因此,存储用户名和口令的数据库结构如下:
有了Hmac计算的哈希和SecretKey,我们想要验证怎么办?这时,SecretKey不能从KeyGenerator生成,而是从一个byte[]数组恢复:
username
secret_key (64 bytes)
password
bob
a8c06e05f92e...5e16
7e0387872a57c85ef6dddbaa12f376de
alice
e6a343693985...f4be
c1f929ac2552642b302e739bc0cdbaac
tim
f27a973dfdc0...6003
af57651c3a8a73303515804d4af43790
//密钥数组:[53, 11, 1, 43, -63, -58, -22, -65, 99, 41, 78, 76, 99, -84, 99, 66, -115, 106, 28, -14, -52, 45, 32, 64, 29, 118, 18, 105, 28, 14, 26, 63, -84, -34, -41, 8, 116, -78, 13, 124, -16, 60, -64, -124, -2, -55, 25, -84, 104, -56, -84, 39, -68, -27, 13, 127, -117, -102, 31, 75, 42, 24, 37, -127]//密钥长度:64 //密钥字符串:350b012bc1c6eabf63294e4c63ac63428d6a1cf2cc2d20401d7612691c0e1a3facded70874b20d7cf03cc084fec919ac68c8ac27bce50d7f8b9a1f4b2a182581 //密钥数组:[-73, 87, 121, 49, 11, -15, 82, 65, -80, 124, -110, 73, -119, -28, -32, -42] //密钥长度:16 //密钥字符串:b75779310bf15241b07c924989e4e0d6public class Demo07 {public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException { // byte[] keybytes = { 53, 11, 1, 43, -63, -58, -22, -65, 99, 41, 78, 76, 99, -84, 99, 66, -115, 106, 28, -14, -52, // 45, 32, 64, 29, 118, 18, 105, 28, 14, 26, 63, -84, -34, -41, 8, 116, -78, 13, 124, -16, 60, -64, -124, // -2, -55, 25, -84, 104, -56, -84, 39, -68, -27, 13, 127, -117, -102, 31, 75, 42, 24, 37, -127 };String string = "350b012bc1c6eabf63294e4c63ac63428d6a1cf2cc2d20401d7612691c0e1a3facded70874b20d7cf03cc084fec919ac68c8ac27bce50d7f8b9a1f4b2a182581";byte[] keybytes = HashTools.stringTobyte(string);// 恢复密钥:参数1:密钥数组 参数2:算法方式SecretKey key = new SecretKeySpec(keybytes, "HmacMD5");Mac mac = Mac.getInstance("HmacMD5");mac.init(key);mac.update("hxjxky".getBytes());byte[] result = mac.doFinal();System.out.println(HashTools.byteToString(result));//b75779310bf15241b07c924989e4e0d6} }
恢复SecretKey的语句就是new SecretKeySpec(keybytes, "HmacMD5")。
小结:
Hmac算法是一种标准的基于密钥的哈希算法,可以配合MD5、SHA-1等哈希算法,计算的摘要长度和原摘要算法长度相同。
四、对称加密算法
对称加密算法就是传统的用一个秘钥进行加密和解密。例如,我们常用的WinZIP和WinRAR对压缩包的加密和解密,就是使用对称加密算法:
从程序的角度看,所谓加密,就是这样一个函数,它接收密码和明文,然后输出密文:secret = encrypt(key, message);
而解密则相反,它接收密码和密文,然后输出明文:plain = decrypt(key, secret);
在软件开发中,常用的对称加密算法有:
算法
密钥长度
工作模式
填充模式
DES
56/64
ECB/CBC/PCBC/CTR/...
NoPadding/PKCS5Padding/...
AES
128/192/256
ECB/CBC/PCBC/CTR/...
NoPadding/PKCS5Padding/PKCS7Padding/...
IDEA
128
ECB
PKCS5Padding/PKCS7Padding/...
密钥长度直接决定加密强度,而工作模式和填充模式可以看成是对称加密算法的参数和格式选择。Java标准库提供的算法实现并不包括所有的工作模式和所有填充模式。
最后,值得注意的是,DES算法由于密钥过短,可以在短时间内被暴力破解,所以现在已经不安全了。
1. 使用AES加密
AES算法是目前应用最广泛的加密算法。比较常见的工作模式是ECB和CBC。
ECB模式
ECB模式是最简单的AES加密模式,它需要一个固定长度的密钥,固定的明文会生成固定的密文。
我们先用ECB模式加密并解密://AES+ECB //1. 根据算法名称/工作模式/填充模式获取Cipher实例; //2. 根据算法名称初始化一个SecretKey实例,密钥必须是指定长度; //3. 使用SerectKey初始化Cipher实例,并设置加密或解密模式; //4. 传入明文或密文,获得密文或明文。 public class Demo01 {public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {// 1.message keybyte[] key = "1234567890abcdef".getBytes();String message = "我本将心像明月";// 加密操作byte[] encodeByte = encodeMessage(key, message.getBytes());System.out.println("加密后的信息:" + HashTools.byteToString(encodeByte));// 解密操作byte[] decodeByte = decodeMessage(key, encodeByte);System.out.println("解密后的信息:" + new String(decodeByte));System.out.println("加密前的字节数组:" + Arrays.toString(message.getBytes()));System.out.println("解密后的信息字节数组:" + Arrays.toString(decodeByte));}public static byte[] encodeMessage(byte[] key, byte[] message) throws NoSuchAlgorithmException,NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {// 1. 根据算法名称/工作模式/填充模式获取Cipher实例;Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");// 2. 根据算法名称初始化一个SecretKey实例,密钥必须是指定长度;SecretKey secretKey = new SecretKeySpec(key, "AES");// 3. 使用SerectKey初始化Cipher实例,并设置加密或解密模式;cipher.init(Cipher.ENCRYPT_MODE, secretKey);// 4. 传入明文或密文,获得密文或明文。return cipher.doFinal(message);}public static byte[] decodeMessage(byte[] key, byte[] encodeMessage) throws NoSuchAlgorithmException,NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {// 创建密码对象,需要传入算法/工作模式/填充模式Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");// 根据key的字节内容,"恢复"秘钥对象SecretKey keySpec = new SecretKeySpec(key, "AES");// 初始化秘钥:设置解密模式DECRYPT_MODEcipher.init(Cipher.DECRYPT_MODE, keySpec);// 根据原始内容(字节),进行解密+cipher.update(encodeMessage);byte[] result = cipher.doFinal();return result;}}
public class Demo02 {public static void main(String[] args) throws Exception {// 原文:String message = "我本将心像明月";System.out.println("Message(原始信息): " + message);// 128位密钥 = 16 bytes Key:byte[] key = "1234567890abcdef".getBytes();// 加密:byte[] data = message.getBytes();byte[] encrypted = encrypt(key, data);System.out.println("Encrypted(加密内容): " + Base64.getEncoder().encodeToString(encrypted));// 解密:byte[] decrypted = decrypt(key, encrypted);System.out.println("Decrypted(解密内容): " + new String(decrypted));}// 加密:public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {// 创建密码对象,需要传入算法/工作模式/填充模式Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");// 根据key的字节内容,"恢复"秘钥对象SecretKey keySpec = new SecretKeySpec(key, "AES");// 初始化秘钥:设置加密模式ENCRYPT_MODEcipher.init(Cipher.ENCRYPT_MODE, keySpec);// 根据原始内容(字节),进行加密return cipher.doFinal(input);}// 解密:public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {// 创建密码对象,需要传入算法/工作模式/填充模式Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");// 根据key的字节内容,"恢复"秘钥对象SecretKey keySpec = new SecretKeySpec(key, "AES");// 初始化秘钥:设置解密模式DECRYPT_MODEcipher.init(Cipher.DECRYPT_MODE, keySpec);// 根据原始内容(字节),进行解密return cipher.doFinal(input);} }
Java标准库提供的对称加密接口非常简单,使用时按以下步骤编写代码:
- 根据算法名称/工作模式/填充模式获取Cipher实例;
- 根据算法名称初始化一个SecretKey实例,密钥必须是指定长度;
- 使用SerectKey初始化Cipher实例,并设置加密或解密模式;
- 传入明文或密文,获得密文或明文。
相关文章:

Java基础29(编码算法 哈希算法 MD5 SHA—1 HMac 算法 堆成加密算法)
目录 一、编码算法 1. 常见编码 2. URL编码 3. Base64编码 4. 小结 二、哈希算法 1. 哈希碰撞 2. 常用哈希算法 MD5算法 SHA-1算法 自定义HashTools工具类 3. 哈希算法的用途 校验下载文件 存储用户密码 4. 小结 三、Hmac算法 小结: 四、对称加密…...

人脸识别——OpenCV
人脸识别 创建窗口创建按钮设置字体定义标签用于显示图片选择并显示图片检测图片中的人脸退出程序返回主界面 创建窗口 导入tkinter库,创建窗口,设置窗口标题和窗口大小。 import tkinter as tkwin tk.Tk() win.title("人脸识别") win.geom…...
深入探索容器:什么是容器及其在现代软件开发中的作用
深入探索容器:什么是容器及其在现代软件开发中的作用 引言 在今天的软件开发和运维领域,容器技术已经成为了一个不可或缺的工具。从初创企业到大型企业,从Web应用到微服务架构,容器都在发挥着其独特的作用。那么,什么…...

STM32-- GPIO->EXTI->NVIC中断
一、NVIC简介 什么是 NVIC ? NVIC 即嵌套向量中断控制器,全称 Nested vectored interrupt controller 。它 是内核的器件,所以它的更多描述可以看内核有关的资料。M3/M4/M7 内核都是支持 256 个中断,其中包含了 16 个系统中…...

【介绍下WebStorm开发插件】
🎥博主:程序员不想YY啊 💫CSDN优质创作者,CSDN实力新星,CSDN博客专家 🤗点赞🎈收藏⭐再看💫养成习惯 ✨希望本文对您有所裨益,如有不足之处,欢迎在评论区提出…...

推荐丨一键申请SSL证书,让网站实现HTTPS访问!
申请HTTPS证书可以简化为以下几个直接步骤,以便您能快速理解和操作: 1. 确定证书类型: - 单域名证书:适用于一个特定域名。 - 通配符证书:适用于同一主域名下的所有子域名。 - 多域名证书:覆盖多个不同的域…...

交叉导轨在医疗设备上的作用!
随着医疗器械行业的需求逐步增长,交叉导轨给医疗器械行业带来了广阔的发展前景。作为重要的精密传动元件,交叉导轨具有寿命长、高精度、高刚性、高耐腐蚀性和高稳定性等优点,满足精密仪器上对产品的高要求使用场景。 在医疗设备领域中交叉导轨…...

【云原生】Docker Compose 使用详解
目录 一、前言 二、Docker Compose 介绍 2.1 Docker Compose概述 2.2 Docker Compose特点 2.3 Docker Compose使用场景 三、Docker Compose 搭建 3.1 安装docker环境 3.2 Docker Compose安装方式一 3.2.1 下载最新版/如果不是最新可替换最新版本 3.2.2 设置权限 3.2.…...

通过LabVIEW提升生产设备自动化水平
现代制造业对生产设备的自动化水平提出了越来越高的要求。使用LabVIEW这一强大的图形化编程环境,可以显著提升生产设备的自动化程度,改善生产效率和产品质量。本文将详细分析如何通过LabVIEW改善生产设备的自动化水平,并提供具体的实施策略与…...

面试题vue+uniapp(个人理解-面试口头答述)未编辑完整....
1.vue2和vue3的区别(vue3与vue2的区别(你不知道细节全在这)_vue2和vue3区别-CSDN博客)参考 Vue3 在组合式(Composition )API,中使用生命周期钩子时需要先引入,而 Vue2 在选项API&am…...

PPP-B2b精密产品使用注意事项及分析
1、因为在使用PPP-B2b进行定轨的时候,发的精密轨道产品是B3频点的,需要改正的卫星质心(Com)与SP3精密星历对比。 2、PPP-B2b产品吸收了电离层误差,因此电离层提取方面与IGS电离层完全无法对其。 3、由于PPP-B2b产品精…...

C语言(结构体)
Hi~!这里是奋斗的小羊,很荣幸各位能阅读我的文章,诚请评论指点,欢迎欢迎~~ 💥个人主页:小羊在奋斗 💥所属专栏:C语言 本系列文章为个人学习笔记,在这里撰写成文一…...
Python filter()用法:深入解析与实战应用
Python filter()用法:深入解析与实战应用 在Python编程中,filter() 函数是一个内置的高阶函数,它用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。该函数在数据处理和筛选时非常有用࿰…...

k8s集群的存储卷、pvc和pv
目录 简介 简介 PV 全称叫做 Persistent Volume,持久化存储卷。它是用来描述或者说用来定义一个存储卷的,这个通常都是由运维工程师来定义。 PVC 的全称是 Persistent Volume Claim,是持久化存储的请求。它是用来描述希望使用什么样的或者说…...
二分搜索树深度优先遍历
二分搜索树深度优先遍历 二分搜索树(Binary Search Tree,简称BST)是一种特殊的二叉树,它具有以下特性:对于树中的任意节点,其左子树中的所有元素都小于该节点的值,其右子树中的所有元素都大于该…...
ImportError: cannot import name ‘packaging‘ from ‘pkg_resources‘‘
参考自: [Bug]: ImportError: cannot import name packaging from pkg_resources (/usr/local/lib/python3.10/dist-packages/pkg_resources/__init__.py) Issue #15863 AUTOMATIC1111/stable-diffusion-webui GitHub ImportError: cannot import name packaging from pkg…...

灯塔歌曲音乐下载官网
灯塔歌曲音乐下载官网网址:www.dengtamp3.com 灯塔音乐下载上线以“用心服务,认真负责”为核心价值。 我们的团队是一个青春的团队,朝气蓬勃。我们采用最新的服务模式,以网为媒为广大客户提供服务,我们坚持以“用心&a…...

数据结构的归并排序(c语言版)
一.归并排序的基本概念 1.基本概念 归并排序是一种高效的排序算法,它采用了分治的思想。它的基本过程如下: 将待排序的数组分割成两个子数组,直到子数组只有一个元素为止。然后将这些子数组两两归并,得到有序的子数组。不断重复第二步,直到最终得到有序的整个数组。 2.核心…...
ubuntu使用Docker笔记
一、参考资料 1、B站视频 尚硅谷Docker实战教程 2、有心人整理的笔记 Docker笔记(周阳版) 3、菜鸟教程 Docker 教程 以下是本人的折腾实践。 二、Docker的安装 2.1、使用清华源安装docker,清华源官方教程。 本人是在ubuntu20.04下安装的…...
PHP编程入门:揭开Web开发的神秘面纱
PHP编程入门:揭开Web开发的神秘面纱 在数字化时代,PHP作为一种广泛使用的服务器端脚本语言,为Web开发领域注入了强大的活力。无论你是编程新手还是有一定经验的开发者,掌握PHP编程都将为你开启一扇通往Web开发新世界的大门。接下…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...

蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...