目前卷文化盛行,为了增强面试能力,开始了无边无际的学习,无边界不是重点,重点是要深入1万米。言归正传,本文打算做一个多线程学习的系列文章,沉淀自我。
文章目录
前言
本文主要是讲解加密解密的分类,基本概念,基本用法,使用场景,底层代码原理剖析。
- 加密算法名称:
- 简单说一下加密算法模式,不要把它与加密算法混淆起来,加密算法就是使用何种方式加密,比如:DES、AES、RSA,而加密算法模式,是用来描述加密算法(此处特指分组密码,不包括流密码)在加密时对明文分组的模式,它代表了不同的分组方式,如常见的:
- ECB模式:电子密码本模式
- CBC模式:密码分组连接模式
- CFB模式:密文反馈模式
- OFB模式:输出反馈模式
- CTR模式:计数器模式
- 支持的加密算法填充方式:
填充方式是在分组密码中,当明文长度不是分组长度的整数倍时,需要在最后一个分组中填充一些数据使其凑满一个分组的长度。
一、对称加密
- 对称加密算法
- 在对称加密算法中,数据发信方将明文(原始数据)和加密密钥一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去。
- 收信方收到密文后,若想解读原文,则需要使用加密用过的密钥及相同算法的逆算法对密文进行解密,才能使其恢复成可读明文。
- 在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥。
对称加密方法,如下
/** 默认的AES加密方式:AES/CBC/PKCS5Padding */
AES("AES"),
ARCFOUR("ARCFOUR"),
Blowfish("Blowfish"),
/** 默认的DES加密方式:DES/ECB/PKCS5Padding */
DES("DES"),
/** 3DES算法,默认实现为:DESede/CBC/PKCS5Padding */
DESede("DESede"),
RC2("RC2"),
PBEWithMD5AndDES("PBEWithMD5AndDES"),
PBEWithSHA1AndDESede("PBEWithSHA1AndDESede"),
PBEWithSHA1AndRC2_40("PBEWithSHA1AndRC2_40");
这里以AES为例,一般使用AES加密的比较多。
1.1AES
1.1.1基本概念
AES加密算法实现
- 高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法
- 对于Java中AES的默认模式是:AES/ECB/PKCS5Padding,如果使用CryptoJS,请调整为:padding: CryptoJS.pad.Pkcs7
相关概念说明:
- mode: 加密算法模式,是用来描述加密算法(此处特指分组密码,不包括流密码)在加密时对明文分组的模式,它代表了不同的分组方式。
- padding: 补码方式是在分组密码中,当明文长度不是分组长度的整数倍时,需要在最后一个分组中填充一些数据使其凑满一个分组的长度。
- iv: 在对明文分组加密时,会将明文分组与前一个密文分组进行XOR运算(即异或运算),但是加密第一个明文分组时不存在“前一个密文分组”,因此需要事先准备一个与分组长度相等的比特序列来代替,这个比特序列就是偏移量。也叫初始化向量。
- key:密钥,支持三种密钥长度:128、192、256位.key的字符串长度也就是16,24,32(使用hutool工具)。
1.1.2使用方法
- maven引入jar
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<hutool.version>5.3.8</hutool.version>
</properties>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>${hutool.version}</version>
</dependency>
- 代码如下
package com.valley.common.util;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
/**
* @author valley
* @date 2022/8/17
* @Description TODO
*/
public class SecureUtil {
/**
* 加密key
*/
private static String key = "rtywet123@#236701234565412345432";//key length: must be 16 or 24 or 32 bytes long
/**
* 加密iv
*/
private static String iv = "123sivDMP@#23670";//IV length: must be 16 bytes long
/**加密
* @param content
* @return
*/
public static String jiaMi(String content,String key,String iv) {
AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, key.getBytes(), iv.getBytes());
return aes.encryptHex(content);
}
/**
* AES加密
* @param content
* @return
*/
public static String jiaMi(String content) {
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key.getBytes());
return aes.encryptHex(content);
}
/**
* AES加密
* @param content
* @param key
* @return
*/
public static String jiaMi(String content,String key) {
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key.getBytes());
return aes.encryptHex(content);
}
/** AES解密
* @param encryptResultStr
* @return
*/
public static String jieMi(String encryptResultStr,String key,String iv){
AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, key.getBytes(), iv.getBytes());
//解密为字符串
return aes.decryptStr(encryptResultStr, CharsetUtil.CHARSET_UTF_8);
}
/** AES解密
*
* @param encryptResultStr
* @return
*/
public static String jieMi(String encryptResultStr){
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key.getBytes());
//解密为字符串
return aes.decryptStr(encryptResultStr, CharsetUtil.CHARSET_UTF_8);
}
public static void main(String[] args) {
String mw=jiaMi("我是牛人",key,iv);
System.out.println(mw);
System.out.println(jieMi(mw,key,iv));
}
}
二、不对称加密
-
非对称加密算法
-
1、签名:使用私钥加密,公钥解密。
-
用于让所有公钥所有者验证私钥所有者的身份并且用来防止私钥所有者发布的内容被篡改,但是不用来保证内容不被他人获得。
-
2、加密:用公钥加密,私钥解密。
-
用于向公钥所有者发布信息,这个信息可能被他人篡改,但是无法被他人获得。
不对称加密方法,如下
/** RSA算法 */
RSA("RSA"),
/** RSA算法,此算法用了默认补位方式为RSA/ECB/PKCS1Padding */
RSA_ECB_PKCS1("RSA/ECB/PKCS1Padding"),
/** RSA算法,此算法用了RSA/None/NoPadding */
RSA_None("RSA/None/NoPadding");
这里以RSA为例,一般不对称加密使用RSA加密的比较多。
2.1RSA
2.1.1基本概念
- RSA公钥/私钥/签名加密解密
- 罗纳德·李维斯特(Ron [R]ivest)、阿迪·萨莫尔(Adi [S]hamir)和伦纳德·阿德曼(Leonard [A]dleman)
- 由于非对称加密速度极其缓慢,一般文件不使用它来加密而是使用对称加密,非对称加密算法可以用来对对称加密的密钥加密,这样保证密钥的安全也就保证了数据的安全。
2.1.2使用方法
-
maven引入jar
参照1.1.2 -
代码如下
package com.valley.common.util;
import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.KeyUtil;
import cn.hutool.crypto.asymmetric.AsymmetricAlgorithm;
import cn.hutool.crypto.asymmetric.AsymmetricCrypto;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
//import java.util.Base64;
/**
* @author valley
* @date 2022/8/17
* @Description 非对称加密解密
*/
public class SecureUtil2 {
public static final String privateKeyStr="MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAIdI9YKPpzKyXohVhUQxMbvPcOMeekN0Is3nEhOWe/Yt8SWSV9I6wdhF32IY8pUDuagcJyylBqIpZEfdPFyFjG3lcwJCPdblwquapA/NO2YvoLi/6FMy8zPFUnekxVvO7f1kyu79MlfLcLKubXHTrFkciHYmBeRI5k4W/LzIXyZVAgMBAAECgYEAggVqCJiePkhWOLvRAHAzd2jw+ygQpAHbbX92zSKlcz5GdTDIfQIByiWlgzes9fEFn3+nWu4kiPg1LCiCWf30rz0roRmFCmZqT/IOmexgszHJ3W2oJaSFqOs97scSDdXsyxuVRVZ3eFdmx/dwoHv+0oDrzSaDtKmb/nv9WORNxUUCQQDv/fD/0Vt0qlCtRLp6k1O+zPaLwR7pGWm3rCoswlXaLv+lMafbwknmEk4bLcpi8a9IoTnix4KvidP1zv5u6cY3AkEAkE8Pjk/3mNuPOLLPh32EI24NSkTWCK+j7zI5qc365acTmMQ/DDf3oMZGkkdnsV7/nmIG09+bxDiTkoeMqyHx0wJBALGASLdsyQ/IyFLe+m2eedotDkzfUVVmjmJi2+6u7YCqMjqkN1q6oJ2h/FLt0zrLNrGpTX8FwQrEDhlIN7chAJECQGRrb9LDWPO4zZhR7dJGCL6y5XKqJhrTRou78p50JMTMl/l1jr4M8Dej4hYGtGScS9rGMiQb9YP9LswHoV6w91sCQEJ6p1fPWDyAlR4yME/mltGOEkHXeA6wme1jSy3LWD4GdVY+Ch0gr/buOW8f77DnP6kRcImcHmjmGAszAG3l8XM=";
public static final String publicKeyStr="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCHSPWCj6cysl6IVYVEMTG7z3DjHnpDdCLN5xITlnv2LfElklfSOsHYRd9iGPKVA7moHCcspQaiKWRH3TxchYxt5XMCQj3W5cKrmqQPzTtmL6C4v+hTMvMzxVJ3pMVbzu39ZMru/TJXy3Cyrm1x06xZHIh2JgXkSOZOFvy8yF8mVQIDAQAB";
/**公钥加密,私钥解密
* @param content
* @return
*/
public static byte[] jiaMi(byte[] content) {
RSA rsa =new RSA(privateKeyStr,publicKeyStr);
return rsa.encrypt(content, KeyType.PublicKey);
}
/**公钥加密,私钥解密
* @param content
* @return
*/
public static byte[] jieMi(byte[] content) {
RSA rsa =new RSA(privateKeyStr,publicKeyStr);
return rsa.decrypt(content, KeyType.PrivateKey);
}
public static void main(String[] args) {
// KeyPair pair = KeyUtil.generateKeyPair("RSA");
// byte[] privateKey = pair.getPrivate().getEncoded();
// byte[] publicKey = pair.getPublic().getEncoded();
// System.out.println(Base64.encode(privateKey));
// System.out.println(Base64.encode(publicKey));
String content="我是牛人";
byte[] con=jieMi(jiaMi(content.getBytes(StandardCharsets.UTF_8)));
System.out.println(new String(con));
}
}
三、摘要算法加密
- 消息摘要(Message Digest)又称为数字摘要(Digital Digest)。
- 它是一个唯一对应一个消息或文本的固定长度的值,它由一个单向Hash加密函数对消息进行作用而产生。如果消息在途中改变了,则接收者通过对收到消息的新产生的摘要与原摘要比较,就可知道消息是否被改变了。因此消息摘要保证了消息的完整性。消息摘要采用单向Hash 函数将需加密 的明文"摘要"成一串128bit的密文,这一串密文亦称为数字指纹(Finger Print),它有固定的长度,且不同的明文摘要成密文,其结果总是不同的,而同样的明文其摘要必定一致 。这样这串摘要便可成为验证明文是否是"真身"的"指纹"了。
- HASH函数的抗冲突性使得如果一段明文稍有变化,哪怕只更改该段落的一个字母,通过哈希算法作用后都将产生不同的值。而HASH算法的单向性使得要找到到哈希值相同的两个不 同的输入消息,在计算上是不可能的。所以数据的哈希值,即消息摘要,可以检验数据的完整性。哈希函数的这种对不同的输入能够生成不同的值的特性使得无法找到两个具有相同哈希值的输入。因此,如果两个文档经哈希转换后成为相同的值,就可以肯定它们是同一文档。 所以,当希望有效地比较两个数据块时,就可以比较它们的哈希值。例如,可以通过比较邮件发送前和发送后的哈希值来验证该邮件在传递时是否修改。
- 消息摘要算法
- 消息摘要算法的主要特征是加密过程不需要密钥,并且经过加密的数据无法被解密,只有输入相同的明文数据经过相同的消息摘要算法才能得到相同的密文。消息摘要算法不存在 密钥的管理与分发问题,适合于分布式网络相同上使用。由于其加密计算的工作量相当可观,所以以前的这种算法通常只用于数据量有限的情况下的加密,例如计算机的口令就是 用不可逆加密算法加密的。近年来,随着计算机相同性能的飞速改善,加密速度不再成为限制这种加密技术发展的桎梏,因而消息摘要算法应用的领域不断增加。
- 消息摘要算法的特点:
- 无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。
- 消息摘要看起来是“随机的”。这些比特看上去是胡乱的杂凑在一起的。
- 一般地,只要输入的消息不同,对其进行摘要以后产生的摘要消息也必不相同;但相同的输入必会产生相同的输出。
- 消息摘要函数是无陷门的单向函数,即只能进行正向的信息摘要,而无法从摘要中恢复出任何的消息,甚至根本就找不到任何与原信息相关的信息。
- 好的摘要算法,无法找到两条消息,是它们的摘要相同。
摘要算法,如下
MD2("MD2"),
MD5("MD5"),
SHA1("SHA-1"),
SHA256("SHA-256"),
SHA384("SHA-384"),
SHA512("SHA-512");
这里以MD5为例,一般使用摘要算法MD5加密的比较多。
3.1MD5
3.1.1基本概念
3.1.2使用方法
-
maven引入jar
参照1.1.2 -
代码如下
计算32位MD5摘要值,使用UTF-8编码,并转为16进制字符串。
package com.valley.common.util;
import cn.hutool.crypto.digest.DigestUtil;
/**
* @author valley
* @date 2022/8/18
* @Description TODO
*/
public class SecureUtil3 {
public static byte[] md5(String content){
return DigestUtil.md5(content);
}
public static String md5Hex(String content){
return DigestUtil.md5Hex(content);
}
public static String md5Hex16(String content){
return DigestUtil.md5Hex16(content);
}
public static void main(String[] args) {
String content = md5Hex("我是牛人");
System.out.println(content);
System.out.println(md5Hex16(content));
}
}
注意:以上所有的加密方式实现,都可以在hutool-crypoto包找到对应的实现。
四、简单加密
4.1byte数组与不同进制字符串互转
package com.wenshi.common.util;
import cn.hutool.core.util.HexUtil;
import java.math.BigInteger;
/**
* @author valley
* @date 2022/8/18
* @Description TODO
*/
public class HexUtils {
public static void main(String[] args) {
//测试字节数组转为十六进制字符串
byte[] byteDemo = new byte[]{1, 2, 3 };
//这里返回的字符串用空格分隔了,如果不需要,可以去方法中 修改21行的逻辑,去掉空格。用空格的好处:可以将十六进制的字符串,变成数组
System.out.println("字节数组{1,2,3}转换为十六进制字符串后为:"+ HexUtil.encodeHexStr(byteDemo));
//测试十六进制字符串转为字节数组
String hexstr = "010203";
//字符串中不要出现空格。否则解析出来的字符串可能不是想要的。例如你可以尝试 01 02 03与010203是不同的
byte[] byteTemp = HexUtil.decodeHex(hexstr);
System.out.println("十六进制字符串01 02 03 转换为二进制数组后的值:");
System.out.println("java默认的十进制显示");
for(byte x:byteTemp){
System.out.println(x+" ");
}
//二进制
System.out.println("二进制显示:");
for(byte x:byteTemp){
System.out.println(Integer.toBinaryString(x)+" ");
}
//十六进制
System.out.println("十六进制显示:");
for(byte x:byteTemp){
System.out.println(Integer.toHexString(x)+" ");
}
//测试十进制转16进制 2进制
System.out.println("十进制10转16进制为"+Integer.toHexString(10));
System.out.println("十进制10转二进制为"+Integer.toBinaryString(10));
//十进制的字符串 转 16 转2
//和上面一样,多了一个步骤,先把字符串解析为十进制
System.out.println("字符串10转16进制为"+Integer.toHexString(Integer.parseInt("10")));
System.out.println("字符串10转二进制为"+Integer.toBinaryString(Integer.parseInt("10")));
//二进制字符串 转 10进制 转 16进制
//思路:先把二进制转为 10进制字符串。然后参考十进制转 16进制
String b = "10000";
BigInteger srcb= new BigInteger(b,2);//转换为BigInteger类型
System.out.println("二进制字符串10000转为10进制后为:"+srcb);//转换为10进制并输出结果
System.out.println("二进制字符串10000转为16进制后为"+Integer.toHexString(Integer.parseInt(srcb.toString())));
//十六进制转 10 转2 同上的思路
String h = "10000";
BigInteger srch = new BigInteger(h, 16);
System.out.println("十六进制字符串10000转为10进制后为:"+srch.toString());//转换为10进制并输出结果
System.out.println("十六进制字符串10000转为2进制后为"+Integer.toBinaryString(Integer.parseInt(srch.toString())));
}
}
4.2BASE64加密解密
package com.valley.common.util;
import cn.hutool.core.codec.Base64;
/**
* @author valley
* @date 2022/8/18
* @Description TODO
*/
public class Base64Util {
public static String base64Encode(String content){
return Base64.encode(content);
}
public static String base64Decode(String content){
return Base64.decodeStr(content);
}
public static void main(String[] args) {
String content="我是牛人";
String encodeStr=base64Encode(content);
System.out.println(encodeStr);
System.out.println(base64Decode(encodeStr));
}
}
注意:AES RSA密文长度不固定,随着加密内容长度而动态变化;MD5秘文长度固定,或16字符或32字符。