一、编码算法
主要是为了在网络间更方便的传输数据/本地存储字节数组而产生
1、base64
由A-Z、a-z、0-9、+、/共64个字符组成,去掉i、I、o、O、+、/即base58
注意:base64以三个字节为一组,如果最后一组不足三个字节,则使用=号补充
案例:
package com.yypttest.Utils;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Base64Test {
//指定字符集
private static final String UTF8 = StandardCharsets.UTF_8.name();
//使用jdk原生来实现base64
@Test
public void test1() throws Exception {
String str = "base64测试";
//getEncoder:编码
//jdk1.8之后才能使用
String encode = Base64.getEncoder().encodeToString(str.getBytes(UTF8));
System.out.println("encodeStr: "+encode);
byte[] decode = Base64.getDecoder().decode(encode.getBytes(UTF8));
System.out.println("decoder: "+new String(decode,UTF8));
}
//使用commons-codec来实现base64
@Test
public void test2() throws Exception {
String str = "使用commons-codec来实现base64";
//getEncoder:编码
//jdk1.8之后才能使用
String encodeStr = org.apache.commons.codec.binary.Base64.encodeBase64String(str.getBytes(UTF8));
System.out.println("encodeStr: "+encodeStr);
byte[] decodeStr = org.apache.commons.codec.binary.Base64.decodeBase64(encodeStr.getBytes(UTF8));
System.out.println("decoder: "+new String(decodeStr,UTF8));
}
}
2、URL编码
application/x-www-from-urlencoded
前端默认使用这种方式编码传递到后端
测试案例:
package com.yypttest.Utils;
import org.junit.Test;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class UrlTest {
//指定字符集
private static final String UTF8 = StandardCharsets.UTF_8.name();
@Test
public void test1() throws Exception{
String str = "测试applicationxwwfurlened";
String encode = URLEncoder.encode(str,UTF8);
System.out.println("编码后:"+encode);
String decode = URLDecoder.decode(encode, UTF8);
System.out.println("解码后:"+decode);
}
}
只对中文进行编码
二、摘要算法
1、定义
又叫Hash算法、散列函数、数字摘要、消息摘要。它是一种单向算法,用户可以通过hash算法对目标信息生成一段特定长度的唯一hash值,但不能通过这个hash值重新获得目标信息。
2、应用场景
- 密码、信息完整性校验、数字签名
3、常见算法
- MD5:Message-Digest Algorithm,结果占128位==>16个byte
- SHA(Secure Hash Algorithm):安全散列算法
- sha-256
- 其他如:sha-0,sha-1,sha512
- MAC(Message Authentication Code):消息认证码,是一种带有密钥的hash函数
- 其他如:MD2、MD4、HAVAL
4、案例
1、使用jdk原生api实现md5,字节数组与16进制字符串之间的转换
package com.yypttest.Utils;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
public class MD5Test {
//使用jdk原生api实现md5
//指定字符集
private static final String UTF8 = StandardCharsets.UTF_8.name();
@Test
public void test1() throws Exception{
String str = "使用jdk原生api实现md5";
String algorithm = "MD5";
//获取消息摘要算法对象
MessageDigest md = MessageDigest.getInstance(algorithm);
//获取原始内容的自己数组
byte[] bytes = str.getBytes(UTF8);
//获取到摘要结果,这里的结果还是字节数
byte[] digest = md.digest(bytes);
// 当digest比较大的时候,循环的进行update()
// md.update(bytes);
// md.digest();
/**
* MD5:Message-Digest Algorithm,结果占128位==>16个byte
* 每个字节占8位2进制数,每四位二进制可转换为一个十六进制数,所以这里16个字节就可以用32位十六进制数表示。
* 把每一个字节转为16进制字符,最终再来拼接起来这些16进制字符
*/
String hexStr = convertBytes2HexStr(digest);
System.out.println(hexStr);
}
/*
* 把字节数组转为16进制字符串,如果一个字节转为16进制字符后不足两位,则前面补0
* */
private String convertBytes2HexStr(byte[] digest) {
StringBuilder buffer = new StringBuilder();
for (byte b:digest){
//获取b的补码后8位
String hex = Integer.toHexString(((int)b)&0xff);
if (hex.length()==1){
hex="0"+hex;
}
buffer.append(hex);
}
return buffer.toString();
}
}
/**
* 把16进制字符串(这里的字符串一定是偶数,因为在convertBytes2HexStr中已经处理过了)转为字节数组
* @param hexStr 16进制字符串
* @return 字节数组
*/
public static byte[] convertHex2Btyes(String hexStr){
//一个字符可以转为2个16进制字符
int length = hexStr.length()/2;
//16进制的个数
byte[] result = new byte[length];
for (int i = 0; i < length; i++) {
//假如:hexStr=abcd
//Integer.parseInt:将指定的进制数转成10进制数(参数1:指定字符,参数2:字符的是什么进制数)
//获取每个字节的高4位二进制数,也就是第一位16进制数,substring指定字符串范围截取字符串,含头不含尾(]
int high4 = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
//获取每个字节的低4位二进制数,也就是第二位16进制数,substring指定字符串范围截取字符串,含头不含尾(]
int low4 = Integer.parseInt(hexStr.substring(i * 2+1, i * 2 + 2), 16);
result[i] = (byte) (high4*16+low4);
}
return result;
}
@Test
public void test() throws DecoderException {
String hexStr = "abcd";
byte[] hex2Btyes = convertHex2Btyes(hexStr);
System.out.println(Arrays.toString(hex2Btyes));
//使用codec
System.out.println(Arrays.toString(Hex.decodeHex(hexStr)));
}
2、使用commons-codec实现md5
//import org.apache.commons.codec.digest.*;
//使用commons-codec实现md5
@Test
public void test2() throws Exception{
String str = "使用commons-codec实现md5";
System.out.println(DigestUtils.md5DigestAsHex(str.getBytes(UTF8)));
}
3、使用commons-codec实现sha256
package com.yypttest.Utils;
import org.junit.Test;
import org.apache.commons.codec.digest.*;
import static com.alibaba.fastjson.util.IOUtils.UTF8;
public class Sha256 {
@Test
public void test1(){
String str = "使用commons-codec实现sha256";
System.out.println(DigestUtils.sha1Hex(str.getBytes(UTF8)));
}
}
4、MAC(Message Authentication Code):消息认证码
mac摘要和digest算法(MD5,sha)不同的地方就是加了盐
package com.yypttest.Utils;
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
import org.junit.Test;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
public class MacTest {
//指定字符集
private static final String UTF8 = StandardCharsets.UTF_8.name();
/**
* 获取mac的消息摘要
* @param originalContent 原始内容
* @param key mac算法的key
* @param algorithm 算法名称,如HmacMD5
* @return
*/
public static String doMacDigest(String originalContent,String key,String algorithm){
try {
//获取消息摘要算法对象
Mac mac = Mac.getInstance(algorithm);
//获取key对象并初始化mac
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(UTF8), algorithm);
//初始化
mac.init(secretKeySpec);
//获取原始内容的字节数组
byte[] bytes = originalContent.getBytes(UTF8);
//获取到摘要结果
byte[] doFinal = mac.doFinal(bytes);
//把每一个字节转换位16进制字符,最终在拼接成16进制
MD5Test md5Test = new MD5Test();
return md5Test.convertBytes2HexStr(doFinal);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
@Test
public void test1() throws Exception{
String str = "使用jdk原始api实现mac";
//指定密钥,mac摘要和digest算法(MD5,sha)不同的地方就是加了盐
String key = "123";
String algorithm = "HmacMD5";
String macMD5Digest = doMacDigest(str, key, algorithm);
System.out.println("macMD5Digest: "+macMD5Digest);
String str1 = "使用jdk原始api实现mac";
//指定密钥,mac摘要和digest算法(MD5,sha)不同的地方就是加了盐
String key1 = "123";
String algorithm1 = "HmacSHA256";
String macSHA265Digest = doMacDigest(str1, key1, algorithm1);
System.out.println("macSHA265Digest: "+macSHA265Digest);
String str2 = "使用jdk原始api实现mac";
//指定密钥,mac摘要和digest算法(MD5,sha)不同的地方就是加了盐
String key2 = "123";
String algorithm2 = "HmacSHA512";
String macSHA512Digest = doMacDigest(str2, key2, algorithm2);
System.out.println("macSHA512Digest: "+macSHA512Digest);
}
//使用codec实现mac
@Test
public void test2() throws UnsupportedEncodingException {
String str2 = "使用codec实现mac";
String key2 = "123";
String macMD5 = new HmacUtils(HmacAlgorithms.HMAC_MD5, key2.getBytes(UTF8)).hmacHex(str2.getBytes(UTF8));
String macSHA256 = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, key2.getBytes(UTF8)).hmacHex(str2.getBytes(UTF8));
String macSHA512 = new HmacUtils(HmacAlgorithms.HMAC_SHA_512, key2.getBytes(UTF8)).hmacHex(str2.getBytes(UTF8));
System.out.println("macMD5: "+macMD5);
System.out.println("macSHA256: "+macSHA256);
System.out.println("macSHA512: "+macSHA512);
}
}
三、对称加密
1、定义
也叫单密钥加密,所谓单密钥,指的是加密和解密的过程使用相同的密钥,相比非对称加密,因只有一把钥匙,因而速度更快,更适合加解密大文件
2、常见算法
- DES:data encryption standard,已经过时
- AES:advanced encryption standard,替代des
- 其他如:3DES、Blowfish、IDEA、RC4、RC5、RC6
3、分类
- 分组加密,又叫快加密
- 序列加密
4、块加密常用的加密模式
- ECB
定义: electronic code book,电码本模式,将整个明文分成若干段相同的小段,然后对每一小段进行加密。特点: 每段之间互不依赖,可以并行处理;同样的明文总是生成同样的密文
- CBC
定义: cipher block chaining,密文分组链模式,所谓链,即密文分组之间像链条一样相与连接在一起。先将明文切分成若干小段,然后每一小段与上一段的密文段(第一个块因没有上一个密文段,使用的是IV进行运算后,再与密钥进行加密
特点: 串行处理,同样的明文每次生成的密文不一样。
5、测试用例
DES测试用例
package com.yypttest.Utils;
import org.apache.commons.codec.binary.Base64;
import org.junit.Test;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
public class DesTest {
//算法类型
private static final String ALGORITHM = "DES";
//密钥 DES的秘钥默认是8位秘钥
private static final String KEY = "12345677";
//字符集
private static final String UTF8 = StandardCharsets.UTF_8.name();
/**
* 加密
* @param text 待加密的内容
* @return
*/
public String encrypt(String text) throws Exception{
//获取实例
Cipher instance = Cipher.getInstance(ALGORITHM);
/*
创建加解密的规则
*/
SecretKey secretKey = new SecretKeySpec(KEY.getBytes(UTF8), ALGORITHM);
/*
初始化
第一个参数:加解密模式(加密模式)
第二个参数:规则
*/
instance.init(Cipher.ENCRYPT_MODE,secretKey);
//获取加密数组
byte[] encodedBytes = instance.doFinal(text.getBytes(UTF8));
//展示字节数组,1:使用base64,2:使用转成16进制字符串
//这里使用base64
return Base64.encodeBase64String(encodedBytes);
}
/**
* 解密
* @param text 加密后的字符串
* @return
* @throws Exception
*/
public String decrypt(String text) throws Exception{
//获取实例
Cipher instance = Cipher.getInstance(ALGORITHM);
/*
创建加解密的规则
*/
SecretKey secretKey = new SecretKeySpec(KEY.getBytes(UTF8), ALGORITHM);
/*
初始化
第一个参数:加解密模式(解密模式)
第二个参数:规则
*/
instance.init(Cipher.DECRYPT_MODE,secretKey);
byte[] decryptedBytes = instance.doFinal(Base64.decodeBase64(text.getBytes(UTF8)));
return new String(decryptedBytes,UTF8);
}
@Test
public void test()throws Exception{
String str = "对称加密DES模式测试";
String encrypt = encrypt(str);
String decrypt = decrypt(encrypt);
System.out.println("加密后的结果:"+encrypt);
System.out.println("解码后的结果:"+decrypt);
}
}
AES测试用例
package com.yypttest.Utils;
import org.apache.commons.codec.binary.Base64;
import org.junit.Test;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
public class AesTest {
//算法类型
private static final String ALGORITHM = "AES";
//密钥,AES的秘钥规定是16、24、32位秘钥
private static final String KEY = "12345678qwerasdf";
//字符集
private static final String UTF8 = StandardCharsets.UTF_8.name();
/**
* 加密
* @param text 待加密的内容
* @return
*/
public String encrypt(String text) throws Exception{
//获取实例
Cipher instance = Cipher.getInstance(ALGORITHM);
/*
创建加解密的规则
*/
SecretKey secretKey = new SecretKeySpec(KEY.getBytes(UTF8), ALGORITHM);
/*
初始化
第一个参数:加解密模式(加密模式)
第二个参数:规则
*/
instance.init(Cipher.ENCRYPT_MODE,secretKey);
//获取加密数组
byte[] encodedBytes = instance.doFinal(text.getBytes(UTF8));
//展示字节数组,1:使用base64,2:使用转成16进制字符串
//这里使用base64
return MD5Test.convertBytes2HexStr(encodedBytes);
}
/**
* 解密
* @param text 加密后的字符串
* @return
* @throws Exception
*/
public String decrypt(String text) throws Exception{
byte[] bytes = MD5Test.convertHex2Btyes(text);
//获取实例
Cipher instance = Cipher.getInstance(ALGORITHM);
/*
创建加解密的规则
*/
SecretKey secretKey = new SecretKeySpec(KEY.getBytes(UTF8), ALGORITHM);
/*
初始化
第一个参数:加解密模式(解密模式)
第二个参数:规则
*/
instance.init(Cipher.DECRYPT_MODE,secretKey);
byte[] decryptedBytes = instance.doFinal(bytes);
return new String(decryptedBytes,UTF8);
}
@Test
public void test()throws Exception{
String str = "对称加密AES模式,采用16进制编码测试";
String encrypt = encrypt(str);
String decrypt = decrypt(encrypt);
System.out.println("加密后的结果:"+encrypt);
System.out.println("解码后的结果:"+decrypt);
}
}
解决AES和DES的key默认固定了长度问题
package com.yypttest.Utils;
import org.junit.Test;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
public class AesTest {
//算法类型
private static final String ALGORITHM = "AES";
//密钥
private static final String KEY = "12345678qsdf";
//字符集
private static final String UTF8 = StandardCharsets.UTF_8.name();
/**
* 加密
* @param text 待加密的内容
* @return
*/
public String encrypt(String text) throws Exception{
//获取实例
Cipher instance = Cipher.getInstance(ALGORITHM);
/*
解决aes和des默认固定key的字符长度问题
*/
//创建keyGenerator,可以根据传入的key,生成一个指定长度的key
KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
//初始化SecureRandom,指定生成key的算法
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
//指定原始传入的key
secureRandom.setSeed(KEY.getBytes(UTF8));
//指定生成新的key的长度
keyGenerator.init(128,secureRandom);
//通过keyGenerator生成原始key
SecretKey secretKey = keyGenerator.generateKey();
//获取到新密钥的字节数组
byte[] encoded = secretKey.getEncoded();
/*
创建加解密的规则
*/
SecretKey secretKeySpec = new SecretKeySpec(encoded, ALGORITHM);
/*
初始化
第一个参数:加解密模式(加密模式)
第二个参数:规则
*/
instance.init(Cipher.ENCRYPT_MODE,secretKeySpec);
//获取加密数组
byte[] encodedBytes = instance.doFinal(text.getBytes(UTF8));
//展示字节数组,1:使用base64,2:使用转成16进制字符串
//这里使用base64
return MD5Test.convertBytes2HexStr(encodedBytes);
}
/**
* 解密
* @param text 加密后的字符串
* @return
* @throws Exception
*/
public String decrypt(String text) throws Exception{
byte[] bytes = MD5Test.convertHex2Btyes(text);
//获取实例
Cipher instance = Cipher.getInstance(ALGORITHM);
/*
解决aes和des默认固定key的字符长度问题
*/
//创建keyGenerator,可以根据传入的key,生成一个指定长度的key
KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
//初始化SecureRandom,指定生成key的算法
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
//指定原始传入的key
secureRandom.setSeed(KEY.getBytes(UTF8));
//指定生成新的key的长度
keyGenerator.init(128,secureRandom);
//通过keyGenerator生成原始key
SecretKey secretKey = keyGenerator.generateKey();
//获取到新密钥的字节数组
byte[] encoded = secretKey.getEncoded();
/*
创建加解密的规则
*/
SecretKey secretKeySpec = new SecretKeySpec(encoded, ALGORITHM);
/*
初始化
第一个参数:加解密模式(解密模式)
第二个参数:规则
*/
instance.init(Cipher.DECRYPT_MODE,secretKeySpec);
byte[] decryptedBytes = instance.doFinal(bytes);
return new String(decryptedBytes,UTF8);
}
@Test
public void test()throws Exception{
String str = "对称加密AES模式,采用16进制编码测试,key可以任意长度";
String encrypt = encrypt(str);
String decrypt = decrypt(encrypt);
System.out.println("加密后的结果:"+encrypt);
System.out.println("解码后的结果:"+decrypt);
}
}
6、块加密常用的填充模式
为什么要有?对于固定的加密算法,每个块有固定大小(BlockSize),比如8个byte,明文分块后,加密前需要保证对最后一个块的大小为8个byte,如果不够则使用特定数据进行填充。
- NoPadding:不自动填充
des时要求原文必须是8个字节的整数倍,aes时是16个字节的整数倍 - PKCS5Padding(限制了块大小为8个byte的PKCS7Padding)/PKCS7Padding
PKCS: Public-Key Cryptography standards,公钥密码学标准
测试用例
package com.yypttest.Utils;
import org.junit.Test;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
/**
* @Author Jiangjinlong
* @Date 2023/3/14 11:12
* @PackageName:com.yypttest.Utils
* @ClassName: AesTestTwo
* @Description: 测试加密模式和填充模式
*/
public class AesTestTwo {
/*
AES加密默认的加密默认就是:ECB,默认的填充模式就是:PKCS5Padding
*/
/*
AES使用CBC加密模式时需要传入IV
*/
//算法类型
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String ALGORITHM_TYPE = "AES";
//密钥
private static final String KEY = "12345678qsdf";
//定义IV,默认规定16个字节
private static final String IV = "12345678qsdfasdf";
//字符集
private static final String UTF8 = StandardCharsets.UTF_8.name();
/**
* 加密
* @param text 待加密的内容
* @return
*/
public String encrypt(String text) throws Exception{
//获取实例
Cipher instance = Cipher.getInstance(ALGORITHM);
/*
解决aes和des默认固定key的字符长度问题
*/
//创建keyGenerator,可以根据传入的key,生成一个指定长度的key
KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM_TYPE);
//初始化SecureRandom,指定生成key的算法
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
//指定原始传入的key
secureRandom.setSeed(KEY.getBytes(UTF8));
//指定生成新的key的长度
keyGenerator.init(128,secureRandom);
//通过keyGenerator生成原始key
SecretKey secretKey = keyGenerator.generateKey();
//获取到新密钥的字节数组
byte[] encoded = secretKey.getEncoded();
/*
创建加解密的规则
*/
SecretKey secretKeySpec = new SecretKeySpec(encoded, ALGORITHM_TYPE);
IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes(UTF8));
/*
初始化
第一个参数:加解密模式(加密模式)
第二个参数:规则
第三个参数:使用CBC加密模式时需要多传一个ivParameterSpec向量
*/
instance.init(Cipher.ENCRYPT_MODE,secretKeySpec,ivParameterSpec);
//获取加密数组
byte[] encodedBytes = instance.doFinal(text.getBytes(UTF8));
//展示字节数组,1:使用base64,2:使用转成16进制字符串
//这里使用base64
return MD5Test.convertBytes2HexStr(encodedBytes);
}
/**
* 解密
* @param text 加密后的字符串
* @return
* @throws Exception
*/
public String decrypt(String text) throws Exception{
byte[] bytes = MD5Test.convertHex2Btyes(text);
//获取实例
Cipher instance = Cipher.getInstance(ALGORITHM);
/*
解决aes和des默认固定key的字符长度问题
*/
//创建keyGenerator,可以根据传入的key,生成一个指定长度的key
KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM_TYPE);
//初始化SecureRandom,指定生成key的算法
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
//指定原始传入的key
secureRandom.setSeed(KEY.getBytes(UTF8));
//指定生成新的key的长度
keyGenerator.init(128,secureRandom);
//通过keyGenerator生成原始key
SecretKey secretKey = keyGenerator.generateKey();
//获取到新密钥的字节数组
byte[] encoded = secretKey.getEncoded();
/*
创建加解密的规则
*/
SecretKey secretKeySpec = new SecretKeySpec(encoded, ALGORITHM_TYPE);
IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes(UTF8));
/*
初始化
第一个参数:加解密模式(解密模式)
第二个参数:规则
第三个参数:使用CBC加密模式时需要多传一个ivParameterSpec向量
*/
instance.init(Cipher.DECRYPT_MODE,secretKeySpec,ivParameterSpec);
byte[] decryptedBytes = instance.doFinal(bytes);
return new String(decryptedBytes,UTF8);
}
@Test
public void test()throws Exception{
String str = "对称加密AES模式,采用16进制编码测试";
String encrypt = encrypt(str);
String decrypt = decrypt(encrypt);
System.out.println("加密后的结果:"+encrypt);
System.out.println("解码后的结果:"+decrypt);
}
}
四、非对称加密
1、定义
加密和解密使用的是两个不同的密钥 (public key 和 private key)。公钥可以给任何人,私钥总是自己保留。
2、为什么会出现?
对称加解密使用相同的秘钥,但对不同的原始内容加密会采用不同的秘钥,导致秘钥数量巨大,难以维护。
3、常见算法
- RSA
- 其他如: ECC,Diffie-Hellman,El Gamal,DSA
4 应用场景
-
加解密
可以使用公钥加密,对应的就是私钥解密;也可以使用私钥加密,对应的公钥解密
测试用例
package com.yypttest.Utils; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; import org.junit.Test; import javax.crypto.Cipher; import java.io.ByteArrayOutputStream; import java.io.File; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; /** * @Author JiangJinlong * @Date 2023/3/14 13:22 * @PackageName:com.yypttest.Utils * @ClassName: RsaTest * @Description: TODO */ public class RsaTest { private static final String ALGORITHM = "RSA"; private static final String UTF8 = StandardCharsets.UTF_8.name(); private static final String publicKeyPath = "E:\\Work_file\\YyptTest\\src\\main\\resources\\rsa.pub"; private static final String privateKeyPath = "E:\\Work_file\\YyptTest\\src\\main\\resources\\rsa.pri"; /** * rsa单次最大加密的文明大小 */ private static final int MAX_ENCRYPT_BLOCK = 117; /** * rsa单次最大解密的密文大小 */ private static final int MAX_DECRYPT_BLOCK = 128; //获取公私钥文件位置,怀疑路径不能含中文 /* private static final String publicKeyPath; private static final String privateKeyPath; static { ClassLoader cl = RsaTest.class.getClassLoader(); publicKeyPath = cl.getResource("rsa.pub").getPath(); privateKeyPath = cl.getResource("rsa.pri").getPath(); System.out.println("公钥路径:"+publicKeyPath); System.out.println("私钥路径:"+privateKeyPath); }*/ /** * 生成经过base64编码后的密钥对(公钥和私钥)并存储在文件中 */ private void writeKey2File() throws Exception{ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM); keyPairGenerator.initialize(1024); //通过keyPair生成器生成keyPair KeyPair keyPair = keyPairGenerator.generateKeyPair(); /* 公钥 */ PublicKey publicKey = keyPair.getPublic(); //把对象转成字节数组 byte[] encoded = publicKey.getEncoded(); String publicKeyBase64Stri = Base64.encodeBase64String(encoded); /* 私钥 */ PrivateKey privateKey = keyPair.getPrivate(); //把对象转成字节数组 byte[] privateKeyEncoded = privateKey.getEncoded(); String privateKeyBase64Stri = Base64.encodeBase64String(privateKeyEncoded); //分别把公钥字符串和私钥字符串写入文件 FileUtils.writeStringToFile(new File(publicKeyPath),publicKeyBase64Stri,UTF8); FileUtils.writeStringToFile(new File(privateKeyPath),privateKeyBase64Stri,UTF8); } /** * 从生成好的公钥文件rsa.pub(这里的公钥文件是通过Base64编码之后存储的数据)获取公钥对象 * @return */ private PublicKey getPulickey()throws Exception{ //读取通过Base64编码之后的公钥文件 String publicKeyBase64Str = FileUtils.readFileToString(new File(publicKeyPath), UTF8); //base64解码,获取字节数组 byte[] decodeBase64 = Base64.decodeBase64(publicKeyBase64Str); //传入算法名字”RAS“ KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM); //传入key的规则,对于公钥的规则就是x509,将公钥的字符串传入公钥规则里面 X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(decodeBase64); return keyFactory.generatePublic(x509EncodedKeySpec); } /** * 从生成好的私钥文件rsa.pri(这里的公钥文件是通过Base64编码之后存储的数据)获取私钥对象 * @return */ private PrivateKey getPrivateKey()throws Exception{ //读取通过Base64编码之后的公钥文件 String privateKeyBase64Str = FileUtils.readFileToString(new File(privateKeyPath), UTF8); //base64解码,获取字节数组 byte[] decodeBase64 = Base64.decodeBase64(privateKeyBase64Str); //传入算法名字”RAS“ KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM); //传入key的规则,对于私钥的规则就是PKCS8,将公钥的字符串传入公钥规则里面 PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(decodeBase64); return keyFactory.generatePrivate(pkcs8EncodedKeySpec); } /** * 加密 * @param originalCont 原始内容 * @param key 公钥或者私钥 * @return base64编码后的加密内容 * @throws Exception */ public String encrypt(String originalCont, Key key) throws Exception{ Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE,key); byte[] bytes = doCodec(cipher,originalCont.getBytes(UTF8),MAX_ENCRYPT_BLOCK); return Base64.encodeBase64String(bytes); } /** * 解密 * @param encryptedStr 加密后内容 * @param key 公钥或者私钥 * @return 原始内容 * @throws Exception */ public String decrypt(String encryptedStr, Key key) throws Exception{ byte[] decodeBase64 = Base64.decodeBase64(encryptedStr); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE,key); byte[] decryptedBytes = doCodec(cipher,decodeBase64,MAX_DECRYPT_BLOCK); return new String(decryptedBytes); } /** * 执行加密或者解密 * @param cipher * @param decodeBase64 * @param maxDecryptBlock * @return */ private byte[] doCodec(Cipher cipher, byte[] decodeBase64, int maxDecryptBlock) throws Exception{ //获取字节长度 int inputLen = decodeBase64.length; //迁移量 int offset = 0; //本次完成加密之后的数组 byte[] cache; //循环的次数 int i= 0; // ByteArrayOutputStream baos = new ByteArrayOutputStream(); while ((inputLen-offset)>0){ if ((inputLen - offset)>maxDecryptBlock){ //第三个参数,要处理的长度 cache = cipher.doFinal(decodeBase64,offset,maxDecryptBlock); }else { cache = cipher.doFinal(decodeBase64,offset,inputLen-offset); } baos.write(cache,0,cache.length); i++; offset = i*maxDecryptBlock; } //加密或者解密的结果 byte[] bytes = baos.toByteArray(); baos.close(); return bytes; } /* 生成公钥私钥,密钥对每次生成的都是不一样的,所以我们执行一次之后保存起来,其他时候就不需要在执行了 */ @Test public void testWriteKey() throws Exception{ writeKey2File(); } @Test public void testRsa()throws Exception{ String str = "测试公钥加密————私钥解密"; //测试公钥加密————私钥解密 System.out.println("公钥加密结果:"+encrypt(str, getPulickey())); System.out.println("私钥解密结果:"+decrypt(encrypt(str, getPulickey()), getPrivateKey())); // 测试私钥加密————公钥解密 String str1 = "测试私钥加密————公钥解密"; System.out.println("私钥加密结果:"+encrypt(str1, getPrivateKey())); System.out.println("公钥解密结果:"+decrypt(encrypt(str1, getPrivateKey()), getPulickey())); } }
-
数字签名
发送方A: 原始内容-.->通过摘要算法获取原始内容的摘要str1–>把hash用发送方的私钥加密–>数字签名。
接收方B: 用发送方的公钥验证签名并解密—>对原始内容进行摘要算法str2–.>比较str1==str2(保证未被篡改)
注意:不是用公钥加密hash,如果用公钥,别人没你的私钥,怎么验证呢?因为签名是先进行摘要再进行rsa,所以在摘要算法一定的情况下,签名后得到的字符串长度总是一样的
package com.yypttest.Utils; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; import org.junit.Test; import javax.crypto.Cipher; import java.io.ByteArrayOutputStream; import java.io.File; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; /** * @Author JiangJinlong * @Date 2023/3/14 13:22 * @PackageName:com.yypttest.Utils * @ClassName: RsaTest * @Description: TODO */ public class SignatureTest { private static final String SIGNATURE_ALGORITHM = "sha256withrsa"; @Test public void testRsa()throws Exception{ String str = "测试数字签名SignatureTest"; byte[] strBytes = str.getBytes(); String sign = sign(strBytes); System.out.println("签名:"+sign); //校验 boolean verify = verify(strBytes, sign); System.out.println("验证结果:"+verify); } /** * 对消息使用私钥生成数字签名 * @param data 原始数据 * @return * @throws Exception */ private static String sign(byte[] data)throws Exception{ //获取私钥 PrivateKey privateKey = new RsaTest().getPrivateKey(); //用指定的算法初始化签名对象,先进行摘要再进行加密 Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); signature.initSign(privateKey); signature.update(data); return MD5Test.convertBytes2HexStr(signature.sign()); } /** * 校验数字签名 * @param data 原始数据 * @param sign 数字签名 * @return * @throws Exception */ private static boolean verify(byte[] data,String sign) throws Exception{ Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); signature.initVerify(new RsaTest().getPulickey()); signature.update(data); return signature.verify(MD5Test.convertHex2Btyes(sign)); } }
-
数字信封
对称加密的秘钥分发不安全–>发送方用接收方的公钥进行加密–>接受方用私钥再解开
注意:为什么会有分发秘钥呢? 不是约定好了吗? 结合我们前面的讲解,秘钥会着每个零件的数期一起发送给接收方。 -
数字证书
- Ca
certificate authority,使用pkipublic key nfrastructure)技术的机构,也可自己内部搭建,如使用ejbca。 - ca根证书
Q:B除了存储A的数字证书对应的ca公钥,假设还有N个人给B发信息,难道B都要保存一份他们的数字证书的CA公钥吗?
A: 不需要,CA认证中心给可以给B一份”根证书”,里面存储的CA公钥可以验证所有CA分中心颁发的数字证书
- Ca