1.1 古典密码学
如凯撒密码、滚筒密码
1.2 近代密码学
如德国Enigma机,被图灵破解
1.3 现代密码学
不是加密和解密,为了在网络间更方便的传输数据而产生
由 A-Z、a-z、0-9、+、/ 共64个字符组成,去掉 i I o O + / 即base58
注意:base64以三个字节为一组,如果最后一组不足3个字节,则使用=号补充。
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
* jdk base64测试
*
* @author zs
* @date 2022-09-02
*/
public class jdkBase64Test {
@Test
public void test01() {
String str = "小白一起学编程";
//编码
String encodeStr = Base64.getEncoder().encodeToString(str.getBytes( StandardCharsets.UTF_8));
System.out.println(encodeStr);
//解码
String decodeStr = new String(Base64.getDecoder().decode(encodeStr.getBytes(StandardCharsets.UTF_8)),StandardCharsets.UTF_8);
System.out.println(decodeStr);
}
}
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
<version>1.15version>
dependency>
import org.apache.commons.codec.binary.Base64;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
/**
* 编解码器base64测试
*
* @author zs
* @date 2022-09-03
*/
public class codecBase64Test {
@Test
public void test01() {
String str = "小白一起学编程";
//编码
String encodeStr = Base64.encodeBase64String(str.getBytes(StandardCharsets.UTF_8));
System.out.println(encodeStr);
//解码
String decodeStr = new String(Base64.decodeBase64(encodeStr.getBytes(StandardCharsets.UTF_8)),StandardCharsets.UTF_8);
System.out.println(decodeStr);
}
}
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class URLTest {
@Test
public void test01() throws UnsupportedEncodingException {
String str = "小白一起学编程";
//编码
String encode = URLEncoder.encode(str, StandardCharsets.UTF_8.name());
System.out.println(encode);
//解码
String decode = URLDecoder.decode(encode, StandardCharsets.UTF_8.name());
System.out.println(decode);
}
}
又叫Hash算法、散列函数、数字摘要、消息摘要。它是一种单向算法,用户可以通过hash算法对目标信息生成一段特定长度的唯一hash值,但不能通过这个hash值重新获得目标信息
密码、信息完整性校验、数字签名
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* jdk md5测试
*
* @author zs
* @date 2022-09-03
*/
public class JdkMd5Test {
@Test
public void test01() throws NoSuchAlgorithmException {
String str = "小白一起学编程";
String algorithm = "MD5";
// 获取消息摘要算法对象
MessageDigest md = MessageDigest.getInstance(algorithm);
// 获取原始内容的字节数组
byte[] originalBytes = str.getBytes(StandardCharsets.UTF_8);
// 获取摘要结果
byte[] digestBytes = md.digest(originalBytes);
//当originalBytes比较大的时候,循环的进行update()
// md.update(originalBytes);
// md.digest();
//md5占128位->16字节->1字节=8位 可以转为一个16进制 --> 16个16进制的数
//将每个字节转为16进制字符,最终拼接起来即可
String hexStr = convertBytes2HexStr(digestBytes);
System.out.println("hexStr:" + hexStr);
}
private String convertBytes2HexStr(byte[] digestBytes) {
StringBuilder sb = new StringBuilder();
//循环16个字节
for (byte b : digestBytes) {
//获取b的补码的后8位
String hex = Integer.toHexString(((int)b)&0xff);
// 15 --> Integer.toHexString(15&0xff) --> 0f
// 16 --> Integer.toHexString(16&0xff)
if (hex.length() == 1) {
hex = "0" +hex;
}
sb.append(hex);
}
return sb.toString();
}
}
//hexStr:2d4c6bc71a670cce33f75b9691488742
简单封装
public class HexUtils {
/**
* 把字节数组转为16进制字符串,如果一个字节转为16进制字符后不足两位,则在前面补0
*
* @param digestBytes 字节数组
* @return {@link String}
*/
public static String convertBytes2HexStr(byte[] digestBytes) {
StringBuilder sb = new StringBuilder();
//循环16个字节
for (byte b : digestBytes) {
//获取b的补码的后8位
String hex = Integer.toHexString(((int)b)&0xff);
// 15 --> Integer.toHexString(15&0xff) --> 0f
// 16 --> Integer.toHexString(16&0xff)
if (hex.length() == 1) {
hex = "0" +hex;
}
sb.append(hex);
}
return sb.toString();
}
}
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public class MessageDigestUtils {
/**
* 执行消息摘要
*
* @param originalContent 原创字符串
* @param algorithm 算法名称
* @return {@link String} 摘要内容
*/
public static String doDigest(String originalContent,String algorithm) {
try {
// 获取消息摘要算法对象
MessageDigest md = MessageDigest.getInstance(algorithm);
// 获取原始内容的字节数组
byte[] originalBytes = originalContent.getBytes(StandardCharsets.UTF_8);
// 获取摘要结果
byte[] digestBytes = md.digest(originalBytes);
//当originalBytes比较大的时候,循环的进行update()
// md.update(originalBytes);
// md.digest();
//md5占128位->16字节->1字节=8位 可以转为一个16进制 --> 16个16进制的数
//将每个字节转为16进制字符,最终拼接起来即可
return HexUtils.convertBytes2HexStr(digestBytes);
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
测试
/**
* jdk md5测试
*
* @author zs
* @date 2022-09-03
*/
public class JdkMd5Test {
@Test
public void test01() throws NoSuchAlgorithmException {
String str = "小白一起学编程";
String algorithm = "MD5";
String hexStr = MessageDigestUtils.doDigest(str, algorithm);
System.out.println("hexStr:" + hexStr);
}
}
//hexStr:2d4c6bc71a670cce33f75b9691488742
codec中byte数组转hex字符串方法
@Test
public void test02(){
byte[] bytes = new byte[]{15,16};
System.out.println(Hex.encodeHexString(bytes));
}
MD5
@Test
public void test04(){
String str = "小白一起学编程";
System.out.println(org.apache.commons.codec.digest.DigestUtils.md5Hex(str));
}
@Test
public void test03(){
String str = "小白一起学编程";
System.out.println(DigestUtils.md5DigestAsHex(str.getBytes(StandardCharsets.UTF_8)));
}
@Test
public void test01() throws NoSuchAlgorithmException {
String str = "小白一起学编程";
String algorithm = "SHA-256";
String hexStr = MessageDigestUtils.doDigest(str, algorithm);
System.out.println("hexStr:" + hexStr);
}
@Test
public void test04(){
String str = "小白一起学编程";
System.out.println(DigestUtils.sha256Hex(str));
}
@Test
public void test01() throws NoSuchAlgorithmException {
String str = "小白一起学编程";
String algorithm = "SHA-512";
String hexStr = MessageDigestUtils.doDigest(str, algorithm);
System.out.println("hexStr:" + hexStr);
}
@Test
public void test04(){
String str = "小白一起学编程";
System.out.println(DigestUtils.sha512Hex(str));
}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
public class MessageDigestUtils {
/**
* 执行消息摘要
*
* @param originalContent 原创字符串
* @param algorithm 算法名称
* @return {@link String} 摘要内容
*/
public static String doMacDigest(String originalContent,String key,String algorithm) {
try {
// 获取消息摘要算法对象
Mac mac = Mac.getInstance(algorithm);
// 获取key对象并初始化mac
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8),algorithm);
mac.init(secretKey);
// 获取原始内容的字节数组
byte[] originalBytes = originalContent.getBytes(StandardCharsets.UTF_8);
// 获取到摘要结果
byte[] digestBytes = mac.doFinal(originalBytes);
// 把每一个字节转为16进制字符,最终再拼接起来这些16进制字符
return HexUtils.convertBytes2HexStr(digestBytes);
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
public class HexUtils {
/**
* 把字节数组转为16进制字符串,如果一个字节转为16进制字符后不足两位,则在前面补0
*
* @param digestBytes 字节数组
* @return {@link String}
*/
public static String convertBytes2HexStr(byte[] digestBytes) {
StringBuilder sb = new StringBuilder();
//循环16个字节
for (byte b : digestBytes) {
//获取b的补码的后8位
String hex = Integer.toHexString(((int)b)&0xff);
// 15 --> Integer.toHexString(15&0xff) --> 0f
// 16 --> Integer.toHexString(16&0xff)
if (hex.length() == 1) {
hex = "0" +hex;
}
sb.append(hex);
}
return sb.toString();
}
}
import org.junit.Test;
import java.security.NoSuchAlgorithmException;
public class JdkMd5Test {
/**
* jdk HmacMd5 实现方式
*
* @throws NoSuchAlgorithmException 没有这样算法异常
*/
@Test
public void test01() throws NoSuchAlgorithmException {
String str = "小白一起学编程";
String algorithm = "HmacMD5";
// 指定秘钥,mac摘要和digest算法(md5,sha)不同的地方就是加了盐
String key = "123";
String hexStr = MessageDigestUtils.doMacDigest(str,key, algorithm);
System.out.println("hexStr:" + hexStr);
}
}
@Test
public void test02() throws NoSuchAlgorithmException {
String str = "小白一起学编程";
String algorithm = "HmacSHA256";
// 指定秘钥,mac摘要和digest算法(md5,sha)不同的地方就是加了盐
String key = "123";
String hexStr = MessageDigestUtils.doMacDigest(str,key, algorithm);
System.out.println("hexStr:" + hexStr);
}
/**
* jdk HmacSHA512 实现方式
*
* @throws NoSuchAlgorithmException 没有这样算法异常
*/
@Test
public void test03() throws NoSuchAlgorithmException {
String str = "小白一起学编程";
String algorithm = "HmacSHA512";
// 指定秘钥,mac摘要和digest算法(md5,sha)不同的地方就是加了盐
String key = "123";
String hexStr = MessageDigestUtils.doMacDigest(str,key, algorithm);
System.out.println("hexStr:" + hexStr);
}
@Test
public void test04() throws NoSuchAlgorithmException {
String str = "小白一起学编程";
String key = "123";
//HmacUtils.hmacMd5Hex(); 过时
String hmacMD5HexStr = new HmacUtils(HmacAlgorithms.HMAC_MD5, key.getBytes(StandardCharsets.UTF_8))
.hmacHex(str.getBytes(StandardCharsets.UTF_8));
String hmacSHA256HexStr = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, key.getBytes(StandardCharsets.UTF_8))
.hmacHex(str.getBytes(StandardCharsets.UTF_8));
String hmacSHA512HexStr = new HmacUtils(HmacAlgorithms.HMAC_SHA_512, key.getBytes(StandardCharsets.UTF_8))
.hmacHex(str.getBytes(StandardCharsets.UTF_8));
System.out.println(hmacMD5HexStr);
System.out.println(hmacSHA256HexStr);
System.out.println(hmacSHA512HexStr);
}
定义:
单秘钥加密。所谓单秘钥,指的是加密和解密的过程中使用相同的秘钥,相比非对称加密,因只有一把秘钥,因而速度更快,更适合加密大文件。
DES加密,只允许密钥是8个字节的。
AES加密,密钥必须是16个字节的.
也就是说 key=“12345678”可以,key="123456789"就会报错。
import org.apache.commons.net.util.Base64;
import org.junit.Test;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
public class DesTest {
private static final String ALGORITHM = "DES";
private static final String KEY = "12345678";
/**
* 加密
*
* @param text 待加密的内容
* @return {@link String}
* @throws Exception 异常
*/
private String encrypt(String text) throws Exception {
// 获取实例
Cipher cipher = Cipher.getInstance(ALGORITHM);
/*
创建加解密的规则
*/
SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);
//指定加密模式还是解密模式,秘钥对象
/*
1. 加解密模式 2.秘钥规则
*/
cipher.init(Cipher.ENCRYPT_MODE,secretKey);
byte[] encodedBytes = cipher.doFinal(text.getBytes(StandardCharsets.UTF_8));
/*
加密的字节数组如何展示?
第一种:Base64
第二种:转成16进制字符串
*/
return Base64.encodeBase64String(encodedBytes);
}
/**
* 解密
*
* @param text 待解密的内容
* @return {@link String}
* @throws Exception 异常
*/
private String decrypt(String text) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE,secretKey);
byte[] decryptedBytes = cipher.doFinal(Base64.decodeBase64(text.getBytes(StandardCharsets.UTF_8)));
return new String(decryptedBytes,StandardCharsets.UTF_8);
}
@Test
public void test() throws Exception {
String encode = encrypt("小白一起学编程");
System.out.println(encode);
String decode = decrypt(encode);
System.out.println(decode);
}
}
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
public class CipherUtils {
private static final String ALGORITHM = "AES";
/**
* 得到cipher对象
*
* @param type 加解密模式
* @param seed 秘钥key
* @return {@link Cipher}
*/
public static Cipher getCipher(int type,String seed) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec secretKey = new SecretKeySpec(seed.getBytes(StandardCharsets.UTF_8), ALGORITHM);
cipher.init(type,secretKey);
return cipher;
}
}
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.junit.Test;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
public class AesTest {
@Test
public void encrypt() throws Exception {
//加密
String text = "小白一起学编程";
Cipher cipher1 = CipherUtils.getCipher(Cipher.ENCRYPT_MODE, "12345678");
byte[] encodedBytes = cipher1.doFinal(text.getBytes(StandardCharsets.UTF_8));
String baseStr = Base64.encodeBase64String(encodedBytes);
String hexStr = Hex.encodeHexString(encodedBytes);
System.out.println(baseStr);
System.out.println(hexStr);
//解密
Cipher cipher2 = CipherUtils.getCipher(Cipher.DECRYPT_MODE, "12345678");
byte[] decodeBytes1 = cipher2.doFinal(Base64.decodeBase64(baseStr.getBytes(StandardCharsets.UTF_8)));
byte[] decodeBytes2 = cipher2.doFinal(Hex.decodeHex(hexStr));
String decodeStr1 = new String(decodeBytes1, StandardCharsets.UTF_8);
String decodeStr2 = new String(decodeBytes2, StandardCharsets.UTF_8);
System.out.println(decodeStr1);
System.out.println(decodeStr2);
}
}
/**
* 得到cipher对象
*
* @param type 加解密模式
* @param seed 秘钥key
* @return {@link Cipher}
*/
public static Cipher getCipherIgnoreKeyLength(int type,String seed) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
// 创建keyGenerator对象,可以根据传入的key生成一个指定长度的key
KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
// 初始化secureRandom,并指定生成指定长度key的算法
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(seed.getBytes(StandardCharsets.UTF_8));
keyGenerator.init(128,secureRandom);
// 通过keyGenerator生成新的秘钥
SecretKey secretKey = keyGenerator.generateKey();
byte[] encoded = secretKey.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(encoded, ALGORITHM);
cipher.init(type,secretKey);
return cipher;
}
@Test
public void test02() throws Exception {
//加密
String text = "小白一起学编程";
Cipher cipher1 = CipherUtils.getCipherIgnoreKeyLength(Cipher.ENCRYPT_MODE, "0123456789abcdef12341234");
byte[] encodedBytes = cipher1.doFinal(text.getBytes(StandardCharsets.UTF_8));
String baseStr = Base64.encodeBase64String(encodedBytes);
String hexStr = Hex.encodeHexString(encodedBytes);
System.out.println(baseStr);
System.out.println(hexStr);
//解密
Cipher cipher2 = CipherUtils.getCipherIgnoreKeyLength(Cipher.DECRYPT_MODE, "0123456789abcdef12341234");
byte[] decodeBytes1 = cipher2.doFinal(Base64.decodeBase64(baseStr.getBytes(StandardCharsets.UTF_8)));
byte[] decodeBytes2 = cipher2.doFinal(Hex.decodeHex(hexStr));
String decodeStr1 = new String(decodeBytes1, StandardCharsets.UTF_8);
String decodeStr2 = new String(decodeBytes2, StandardCharsets.UTF_8);
System.out.println(decodeStr1);
System.out.println(decodeStr2);
}
ECB (默认加密模式)
定义:electronic code book, 电码本模式,将整个明文分成若干段相同的小段,然后对每一小段进行加密。
特点:每段之间互不依赖,可以并行处理,同样的明文总是生成同样的密文
CBC
定义:cipher block chaining,密文分组链模式,所谓链,即密文分组之间像链条一样相互连接在一起。先将明文切分成若干小段,然后每一小段与上一段的密文段(第一个块因没有上一个密文段,使用的是IV)进行运算后,再与密钥进行加密
特点:串行处理;同样的明文每次生成的密文不一样。
private static final String ALGORITHM = "AES";
private static final String ALGORITHM_TYPE_CBC = "AES/CBC/PKCS5Padding";
private static final String ALGORITHM_TYPE_ECB = "AES/ECB/PKCS5Padding";
/**
* 得到cipher对象
*
* @param type 加解密模式
* @param seed 秘钥key
* @return {@link Cipher}
*/
public static Cipher getCipherCBC(int type,String seed) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM_TYPE_CBC);
SecretKeySpec secretKey = new SecretKeySpec(seed.getBytes(StandardCharsets.UTF_8), ALGORITHM);
//iv向量
IvParameterSpec ivParameterSpec = new IvParameterSpec("1234123412341236".getBytes(StandardCharsets.UTF_8));
cipher.init(type,secretKey,ivParameterSpec);
return cipher;
}
/**
* 得到cipher对象
*
* @param type 加解密模式
* @param seed 秘钥key
* @return {@link Cipher}
*/
public static Cipher getCipherECB(int type,String seed) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM_TYPE_ECB);
SecretKeySpec secretKey = new SecretKeySpec(seed.getBytes(StandardCharsets.UTF_8), ALGORITHM);
cipher.init(type,secretKey);
return cipher;
}
@Test
public void test03() throws Exception {
//加密
String text = "小白一起学编程";
Cipher cipher1 = CipherUtils.getCipherCBC(Cipher.ENCRYPT_MODE, "0123456789abcdef");
byte[] encodedBytes = cipher1.doFinal(text.getBytes(StandardCharsets.UTF_8));
String baseStr = Base64.encodeBase64String(encodedBytes);
String hexStr = Hex.encodeHexString(encodedBytes);
System.out.println(baseStr);
System.out.println(hexStr);
//解密
Cipher cipher2 = CipherUtils.getCipherCBC(Cipher.DECRYPT_MODE, "0123456789abcdef");
byte[] decodeBytes1 = cipher2.doFinal(Base64.decodeBase64(baseStr.getBytes(StandardCharsets.UTF_8)));
byte[] decodeBytes2 = cipher2.doFinal(Hex.decodeHex(hexStr));
String decodeStr1 = new String(decodeBytes1, StandardCharsets.UTF_8);
String decodeStr2 = new String(decodeBytes2, StandardCharsets.UTF_8);
System.out.println(decodeStr1);
System.out.println(decodeStr2);
}
@Test
public void test04() throws Exception {
//加密
String text = "小白一起学编程";
Cipher cipher1 = CipherUtils.getCipherECB(Cipher.ENCRYPT_MODE, "0123456789abcdef");
byte[] encodedBytes = cipher1.doFinal(text.getBytes(StandardCharsets.UTF_8));
String baseStr = Base64.encodeBase64String(encodedBytes);
String hexStr = Hex.encodeHexString(encodedBytes);
System.out.println(baseStr);
System.out.println(hexStr);
//解密
Cipher cipher2 = CipherUtils.getCipherECB(Cipher.DECRYPT_MODE, "0123456789abcdef");
byte[] decodeBytes1 = cipher2.doFinal(Base64.decodeBase64(baseStr.getBytes(StandardCharsets.UTF_8)));
byte[] decodeBytes2 = cipher2.doFinal(Hex.decodeHex(hexStr));
String decodeStr1 = new String(decodeBytes1, StandardCharsets.UTF_8);
String decodeStr2 = new String(decodeBytes2, StandardCharsets.UTF_8);
System.out.println(decodeStr1);
System.out.println(decodeStr2);
}
为什么要有?对于固定的加密算法,每个块有固定大小(BlockSize),比如8个byte,明文分块后,加密前需要保证对最后一块的大小为8个byte,如果不够则使用特定数据进行填充
NoPadding: 不自动填充
des时要求原文必须是8个字节的整数倍,aes时是16个字节的整数倍
PKCS5Padding(限制了块大小为8个byte的PKCS7Padding) /PKCS7Padding
PKCS:Public-Key Cryptography Standards,公钥密码学标准
定义
加密和解密使用的是两个不同的秘钥(public key 和 private key)公钥可以给任何人,私钥总是自己保留。
为什么会出现
对称加解密使用相同的秘钥,但对不同的原始内容加密会采用不同的秘钥,导致秘钥数量巨大,难以维护
对称加密容易破解,非对称不容易破解
加解密
可以使用公钥加密,对应的就是私钥解密;也可以使用私钥加密,对应的就是公钥解密
数字签名
数字信封
数字证书
package com.zs.codeTest.asymmetric;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.junit.Test;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* rsa测试
*
* @author zs
* @date 2022-09-03
*/
public class RsaTest {
private static final String ALGORITHM = "RSA";
private static String publicKeyPath;
private static String privateKeyPath;
/**
* RSA单次最大加密的明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA单次最大解密的密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
static {
ClassLoader cl = RsaTest.class.getClassLoader();
publicKeyPath = cl.getResource("rsa.pub").getPath();
privateKeyPath = cl.getResource("rsa.pri").getPath();
}
/**
* 加密
*
* @param originalCount 原始数
* @param key 公钥或私钥
* @return {@link String} base64b编码后的加密内容
*/
public String encrypt(String originalCount, Key key) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] bytes = doCodec(cipher, originalCount.getBytes(StandardCharsets.UTF_8),MAX_ENCRYPT_BLOCK);
return Base64.encodeBase64String(bytes);
}
/**
* 解密
*
* @param encryptedStr 加密后的内容
* @param key 公钥或私钥
* @return {@link String} 原始内容
*/
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, StandardCharsets.UTF_8);
}
/**
* 执行加密或者解密
*
* @param cipher 密码
* @param bytes 字节
* @param maxBlockSize 最大block大小
* @return {@link byte[]}
*/
private byte[] doCodec(Cipher cipher, byte[] bytes, int maxBlockSize) throws Exception {
//字节数组的长度
int inputLen = bytes.length;
//偏移量,相当于指针来用
int offset = 0;
//分段缓冲数组
byte[] cache;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//循环变量
int i = 0;
//循环分段处理
while ((inputLen - offset) > 0) {
//判断处理数据的长度是否需要分段处理
if ((inputLen - offset) > maxBlockSize) {
//第三个参数是要处理多长
cache = cipher.doFinal(bytes,offset,maxBlockSize);
} else {
//不需要分段处理
cache = cipher.doFinal(bytes,offset,inputLen - offset);
}
//把当前cache的内容存起来
baos.write(cache,0,cache.length);
//偏移量的增加
offset = ++i * maxBlockSize;
}
//加密或解密后的结果
byte[] codecBytes = baos.toByteArray();
baos.close();
return codecBytes;
}
/**
* 获取公钥
* 从生成好的公钥文件rsa.pub(进过base64编码后存储的)中获取公钥对象
*
* @return {@link PublicKey}
*/
private PublicKey getPublicKey() throws Exception {
String publicKeyBase64Str = FileUtils.readFileToString(new File(publicKeyPath), StandardCharsets.UTF_8);
byte[] decodeBase64 = Base64.decodeBase64(publicKeyBase64Str);
//公钥的规则就是 x509
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(decodeBase64);
//生成公钥对象
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
return keyFactory.generatePublic(x509EncodedKeySpec);
}
/**
* 获取私钥
* 从生成好的私钥文件rsa.pri(进过base64编码后存储的)中获取私钥对象
*
* @return {@link PublicKey}
*/
private PrivateKey getPrivateKey() throws Exception {
String privateKeyBase64Str = FileUtils.readFileToString(new File(privateKeyPath), StandardCharsets.UTF_8);
byte[] decodeBase64 = Base64.decodeBase64(privateKeyBase64Str);
// 私钥的规则就是 PKCS8
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(decodeBase64);
//生成公钥对象
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
}
/**
* 生成经过base64编码后的密钥对(公钥/私钥)并存储在文件中
*/
private void writeKey2File() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
keyPairGenerator.initialize(1024);
// 通过KeyPair生成器生成KeyPair
KeyPair keyPair = keyPairGenerator.generateKeyPair();
//生成公钥对象,获取公钥编码值 把对象转成字节数组,转成base64字符串
PublicKey publicKey = keyPair.getPublic();
byte[] publicKeyEncoded = publicKey.getEncoded();
String publicKeyBase64Str = Base64.encodeBase64String(publicKeyEncoded);
//生成私钥对象,获取私钥编码值 把对象转成字节数组,转成base64字符串
PrivateKey privateKey = keyPair.getPrivate();
byte[] privateKeyEncoded = privateKey.getEncoded();
String privateKeyBase64Str = Base64.encodeBase64String(privateKeyEncoded);
//分别吧公钥字符串和私钥字符串写入文件
FileUtils.writeStringToFile(new File(URLDecoder.decode(publicKeyPath, StandardCharsets.UTF_8.name())),
publicKeyBase64Str,StandardCharsets.UTF_8);
FileUtils.writeStringToFile(new File(URLDecoder.decode(privateKeyPath, StandardCharsets.UTF_8.name())),
privateKeyBase64Str,StandardCharsets.UTF_8);
}
@Test
public void testWriteKey2File() throws Exception {
writeKey2File();
}
@Test
public void testRsa() throws Exception {
String str = "小白一起学编程";
// 测试 公钥加密 --> 私钥解密
String encryptedStr = encrypt(str, getPublicKey());
System.out.println("公钥加密结果:" + encryptedStr);
String decryptedStr = decrypt(encryptedStr, getPrivateKey());
System.out.println("私钥解密结果:" + decryptedStr);
// 测试 私钥加密 --> 公钥解密
encryptedStr = encrypt(str, getPrivateKey());
System.out.println("私钥加密结果:" + encryptedStr);
decryptedStr = decrypt(encryptedStr, getPublicKey());
System.out.println("公钥解密结果:" + decryptedStr);
}
}
公钥加密结果:nnc5MyIAICAliEa8yyyo7z3h2aurGDXFvuDs0dXaSGvu1YeWNl/4s6p21UaW3nzwGu5GcaEXyh57J0TDSSrmO8V2Mlr2EMWXnvUQ0LhjK/eY89TwXfvGNS75Dx6BWizEPenIBMA4zQGXUJOJWMdIwKCu9MCnkzxPMUW2ZhXBElk=
私钥解密结果:小白一起学编程
私钥加密结果:Ds81AdB/fCP2ULDe70AKTq4zwmofmaWAy6riY98OSeGOkJxrszdclrddnf/fe371py5HZxL+8YC7CzBSidoWqjVkrjJFZ257vpmIq54p6zcmdvrRIDl1qHn7rrttMP3YR+jY9PAx87pG61Fm+jEkS5F+kiM9y6WzQ8fDt7f5nlQ=
公钥解密结果:小白一起学编程
注意:秘钥需要先生成到文件中
发送方A:
原始内容
① 摘要算法 ==》原始内容摘要 str
②摘要 str ==》A私钥加密 ==》 数字签名。
③发送给 B: 数字签名 + A的公钥
接收方B:
① A公钥解密 数字签名 ==》摘要 str
package com.zs.codeTest.asymmetric;
import org.apache.commons.codec.binary.Hex;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
/**
* 签名测试
*
* @author zs
* @date 2022-09-03
*/
public class SignatureTest {
// public static final String SIGNTURE_ALGORITHM = "mD5withRSA";//大小写无所谓
public static final String SIGNTURE_ALGORITHM = "sha256withrsa";//大小写无所谓
public static void main(String[] args) throws Exception {
String content = "今晚8点,行动!";
byte[] contentBytes = content.getBytes(StandardCharsets.UTF_8);
String sign = sign(contentBytes);
System.out.println("签名:" + sign);
boolean status = verify(contentBytes,sign);
System.out.println("验证结果:\r" + status);
}
/**
* 对信息使用私钥生成数字签名
*
* @param data 原始数据
* @return {@link String}
*/
private static String sign(byte[] data) throws Exception {
//获取私钥
PrivateKey privateKey = new RsaTest().getPrivateKey();
//用指定的算法初始化签名对象:进行摘要再进行加密
Signature signature = Signature.getInstance(SIGNTURE_ALGORITHM);
signature.initSign(privateKey);
signature.update(data);
return Hex.encodeHexString(signature.sign());
}
/**
* 验证数字签名
*
* @param data 原始数据
* @param sign 数字签名
* @return boolean
*/
private static boolean verify(byte[] data, String sign) throws Exception {
Signature signature = Signature.getInstance(SIGNTURE_ALGORITHM);
signature.initVerify(new RsaTest().getPublicKey());
signature.update(data);
return signature.verify(Hex.decodeHex(sign));
}
}
对称加密的秘钥分发不安全 ==》 发送方用接收方的公钥 【加密秘钥】 ==》 接收方用私钥解开
注意: 分发秘钥的场景,秘钥会随着每个零件的数据一起发送给接收方
Q: 有了数字签名就可以证明发送者的身份了,还有什么问题?
A:B这里存储的A的公钥被换成C的公钥,A发送的信息也换了C的私钥生成的签名。现在需要一种手段,
来证明 B 这里存储的A的公钥,就是A的公钥
涉及的一些概念:
ca
certificate authority, 使用pki(public key Infrastructure) 技术的机构,也可自己内部搭建,如使用ejbca.
ca 根证书
Q: B 除了存储A的数字证书对应的ca公钥,假设还有N个人给B发信息,难道B都要保存一份他们的数字证书的CA公钥吗?
A:不需要,CA认证中心可以给B一份“根证书”,里面存储的CA公钥可以验证所有CA分中心颁发的数字证书
public class NumberCertificateTest {
@Test
public void test() throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
//证书是使用jdk自带的keytool工具生成的
String filePath = NumberCertificateTest.class.getClassLoader().getResource("certificate/tomcat.pem").getPath();
FileInputStream in = new FileInputStream(filePath);
//生成一个证书对象并使用从输入流中读取的数据对他进行初始化
Certificate c = cf.generateCertificate(in);
PublicKey publicKey = c.getPublicKey();
//获取到了证书中的公钥
String key = Base64.encodeBase64String(publicKey.getEncoded());
System.out.println(key);
}
}