Skip to content
标签
工具
字数
8062 字
阅读时间
37 分钟

一、了解内容

加密解密介绍

  1. 明文:原始信息。
  2. 密钥:加密与解密算法的参数,直接影响对明文进行变换的结果。
  3. 密文:对明文进行变换的结果。
  4. 加密算法:以密钥为参数,对明文进行多种置换和转换的规则和步骤,变换结果为密文。
  5. 解密算法:加密算法的逆变换,以密文为输入、密钥为参数,变换结果为明文。

ASCII编码

•美国信息交换标准代码,主要用于显示现代英语和其他西欧语言。

内容:获取字符串的ASCII编码,为后面的凯撒加密算法准备

获取字符串的ASCII编码将字符串的每个字符转成int类型即可获取。

凯撒加密算法

凯撒密码作为一种最为古老的加密技术,在古罗马的时候都已经很流行,他的基本思想是:通过把字母移动一定的位数来实现加密和解密。明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。

​ 例如,当偏移量是 3 的时候,所有的字母 A 将被替换成 D,B 变成 E

•把字母移动一定的位数来实现加密和解密,•英文中字母e出现的频率最高

java

/**
 * 2.凯撒加密算法
 */
public class KaiserDemo {
	
	public static void main(String[] args) {
		//demo1();
		
		//多个字符,凯撒加密
		String input = "Hello I love You!";
		//秘钥
		int key = 3;
		String encrypt = encrypt(input, key);
		System.out.println("凯撒加密:" + encrypt);
		
		//解密
		String decrypt = decrypt(encrypt, key);
		System.out.println("凯撒解密:" + decrypt);
		
	}

	/**
	 * 凯撒加密
	 * @param input
	 * @param key
	 * @return 
	 */
	public static String encrypt(String input, int key) {
		//获取字符数组
		char[] charArray = input.toCharArray();
		StringBuilder stringBuilder = new StringBuilder();
		for (char c : charArray) {
			//遍历每一个字符,获取对应的ascii编码
			int ascill = c;
			ascill += key;
			//获取ascii对应的字符
			char result = (char) ascill;
			//System.out.print(result);
			stringBuilder.append(result);
		}
		return stringBuilder.toString();
	}
	
	/**
	 * 凯撒解密
	 */
	public static String decrypt(String input, int key) {
		//获取字符数组
		char[] charArray = input.toCharArray();
		StringBuilder stringBuilder = new StringBuilder();
		for (char c : charArray) {
			//遍历每一个字符,获取对应的ascii编码
			int ascill = c;
			ascill -= key;//逆过来
			//获取ascii对应的字符
			char result = (char) ascill;
			//System.out.print(result);
			stringBuilder.append(result);
		}
		return stringBuilder.toString();
	}


	/**
	 * 偏移单个字符
	 */
	public static void demo1() {
		//凯撒加密底层机制:对字符偏移一定的位数,A-> 1 = B,f->g
		char ch = 'A';
		//获取支付ascii编码
		int ascii = ch;
		//偏移
		//ascii = ascii + 1;
		ascii += 2;
		
		System.out.println(ascii);
		
		//获取ascii编码对应的字符
		char result = (char) ascii;
		System.out.println("A偏移2位:" + result);
	}

}

频度分析法破解凯撒加密算法

​ 在任何一种书面语言中,不同的字母或字母组合出现的频率各不相同。而且,对于以这种语言书写的任意一段文本,都具有大致相同的特征字母分布。

​ 比如,在英语中,字母 E 出现的频率很高,而 X 则出现得较少。

破解流程: ​ 1. 统计密文里出现次数最多的字符,例如出现次数最多的字符是是’h’。 ​ 2. 计算字符’h’到’e’的偏移量,值为 3,则表示原文偏移了 3 个位置。 ​ 3. 将密文所有字符恢复偏移 3 个位置。

注意点:

  1. 统计密文里出现次数最多的字符时,需多统计几个备选,因为最多的可能是空格或者其他字符。

  2. 短文可能严重偏离标准频率,假如文章少于100个字母,那么对它的解密就会比较困难,而且不是所有文章都适用标准频度.

java

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * 频率分析法破解凯撒密码
 */
public class FrequencyAnalysis {
	// 英文里出现次数最多的字符
	private static final char MAGIC_CHAR = 'e';
	// 破解生成的最大文件数
	private static final int DE_MAX_FILE = 4;

	public static void main(String[] args) throws Exception {
		// 测试1,统计字符个数
		// printCharCount("article.txt");

		// 加密文件
		// int key = 3;
		// encryptFile("article.txt", "article_en.txt", key);
		// 统计密文哪个字符出现频率最高
		printCharCount("article_en.txt");

		// 读取加密后的文件
		String artile = Util.file2String("article_en.txt");
		// 解密(会生成多个备选文件)
		decryptCaesarCode(artile, "article_de.txt");
	}

	public static void printCharCount(String path) throws IOException {
		String data = Util.file2String(path);
		List<Entry<Character, Integer>> mapList = getMaxCountChar(data);
		for (Entry<Character, Integer> entry : mapList) {
			// 输出前几位的统计信息
			System.out.println("字符'" + entry.getKey() + "'出现" + entry.getValue() + "次");
		}
	}

	public static void encryptFile(String srcFile, String destFile, int key) throws IOException {
		String artile = Util.file2String(srcFile);
		// 加密文件
		String encryptData = KaiserDemo.encrypt(artile, key);
		// 保存加密后的文件
		Util.string2File(encryptData, destFile);
	}

	/**
	 * 破解凯撒密码
	 * 
	 * @param input
	 *            数据源
	 * @return 返回解密后的数据
	 */
	public static void decryptCaesarCode(String input, String destPath) {
		int deCount = 0;// 当前解密生成的备选文件数
		// 获取出现频率最高的字符信息(出现次数越多越靠前)
		List<Entry<Character, Integer>> mapList = getMaxCountChar(input);
		for (Entry<Character, Integer> entry : mapList) {
			// 限制解密文件备选数
			if (deCount >= DE_MAX_FILE) {
				break;
			}

			// 输出前几位的统计信息
			System.out.println("字符'" + entry.getKey() + "'出现" + entry.getValue() + "次");

			++deCount;
			// 出现次数最高的字符跟MAGIC_CHAR的偏移量即为秘钥
			int key = entry.getKey() - MAGIC_CHAR;
			System.out.println("猜测key = " + key + ", 解密生成第" + deCount + "个备选文件" + "\n");
			String decrypt = KaiserDemo.decrypt(input, key);

			String fileName = "de_" + deCount + destPath;
			Util.string2File(decrypt, fileName);
		}
	}

	// 统计String里出现最多的字符
	public static List<Entry<Character, Integer>> getMaxCountChar(String data) {
		Map<Character, Integer> map = new HashMap<Character, Integer>();
		char[] array = data.toCharArray();
		for (char c : array) {
			if (!map.containsKey(c)) {
				map.put(c, 1);
			} else {
				Integer count = map.get(c);
				map.put(c, count + 1);
			}
		}

		// 输出统计信息
		/*
		 * for (Entry<Character, Integer> entry : map.entrySet()) {
		 * System.out.println(entry.getKey() + "出现" + entry.getValue() + "次"); }
		 */

		// 获取获取最大值
		int maxCount = 0;
		for (Entry<Character, Integer> entry : map.entrySet()) {
			// 不统计空格
			if (/* entry.getKey() != ' ' && */entry.getValue() > maxCount) {
				maxCount = entry.getValue();
			}
		}

		// map转换成list便于排序
		List<Entry<Character, Integer>> mapList = new ArrayList<Map.Entry<Character, Integer>>(map.entrySet());
		// 根据字符出现次数排序
		Collections.sort(mapList, new Comparator<Entry<Character, Integer>>() {
			@Override
			public int compare(Entry<Character, Integer> o1, Entry<Character, Integer> o2) {
				return o2.getValue().compareTo(o1.getValue());
			}
		});
		return mapList;
	}
}

Byte和bit

​ •Byte:字节,数据存储单位。

•bit:比特,又名位。一个位要么0,要么1,数据传输的单位。

•一个字节占8位。

•‘A’对应ascii编码:65

•65转成二进制:01000001

•A字符一个字节(Byte),占8位(bit)

java

import java.io.UnsupportedEncodingException;

/**
 * 4.Byte和bit
 */
public class ByteAndBit {
	
	public static void main(String[] args) {
		
		String input = "ABc";
		//获取字符串字节
		byte[] bytes = input.getBytes();
		System.out.println("一个英文字母占用"+bytes.length+"字节");
		for (byte b : bytes) {
			System.out.print(b + " ");
			//获取每一个字节占用位数:转成二进制
			String binary = Integer.toBinaryString(b);
			System.out.println(binary);
		}
		
		//中文Byte和bit:utf-8每一个中文占用3个字节,gbk每一个中文占用2个字节

		String input2 = "我";
		//获取字符串字节
		byte[] bytes2 = input2.getBytes();
		try {
			byte[] bytes3 = input2.getBytes("GBK");
			System.out.println("一个中文字母占用"+bytes2.length+"字节");
			for (byte b : bytes2) {
				System.out.print(b + " ");
				//获取每一个字节占用位数:转成二进制
				String binary = Integer.toBinaryString(b);
				System.out.println(binary);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

二、对称加密算法

加密和解密都使用同一把密钥,这种加密方法称为对称加密,也称为单密钥加密。

基于“对称密钥”的加密算法主要有DES算法,3DES算法,AES算法,Blowfish算法,RC5算法和IDEA算法等等,

特点:

•加密速度快,可以加密大文件

•对称可逆,秘钥暴露文件就泄漏

•加密后编码表找不到对应字符,乱码

•结合Base64使用

常用数学运算

◆移位和循环移位   移位就是将一段数码按照规定的位数整体性地左移或右移。循环右移就是当右移时,把数码的最后的位移到数码的最前头,循环左移正相反。

​ 例如,对十进制数码12345678循环右移1位(十进制位)的结果为81234567,而循环左移1位的结果则为23456781。 ◆置换   就是将数码中的某一位的值根据置换表的规定,用另一位代替。它不像移位操作那样整齐有序,看上去杂乱无章。这正是加密所需,被经常应用。 ◆扩展   就是将一段数码扩展成比原来位数更长的数码。扩展方法有多种,例如,可以用置换的方法,以扩展置换表来规定扩展后的数码每一位的替代值。 ◆压缩   就是将一段数码压缩成比原来位数更短的数码。压缩方法有多种,例如,也可以用置换的方法,以表来规定压缩后的数码每一位的替代值。 ◆异或   这是一种二进制布尔代数运算。异或的数学符号为⊕ ,它的运算法则如下: ​ 1⊕1 = 0  ​ 0⊕0 = 0  ​ 1⊕0 = 1  ​ 0⊕1 = 1    也可以简单地理解为,参与异或运算的两数位如相等,则结果为0,不等则为1。 ◆迭代   迭代就是多次重复相同的运算,这在密码算法中经常使用,以使得形成的密文更加难以破解。

**总结:**这些运算主要是对比特位进行操作,其共同目的就是把被加密的明文数码尽可能深地打乱,从而加大破译的难度。

DES加密和解密

​ Data Encryption Standard,数据加密标准

DES是一个分组加密算法,典型的DES以64位为分组对数据加密,加密和解密用的是同一个算法。它的密钥长度是56位(因为每个第8 位都用作奇偶校验),密钥可以是任意的56位的数,其保密性依赖于密钥。

​ DES加密的算法框架大致如下:

首先要生成一套加密密钥,从用户处取得一个64位长的密码口令,然后通过等分、移位、选取和迭代形成一套16个加密密钥,分别供每一轮运算中使用。

​ DES对64位(bit)的明文分组M进行操作,M经过一个初始置换IP,置换成m0。将m0明文分成左半部分和右半部分,各32位长。

​ 然后进行16轮完全相同的运算(迭代),在每一轮运算过程中数据与相应的密钥结合。

经过16轮迭代后,左、右半部分合在一起经过一个末置换(数据整理),这样就完成了加密过程。

•秘钥64个bit位

java

import java.security.Key;
import java.security.spec.KeySpec;

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;

import com.itheima.crypt.util.Base64;

/**
 * 对称加密:DES加密和解密
 */
public class DESCrypt {
	
	//1FylY3LQOEYE73WwIPSKyrI9zQHBhUUSypzLX1R06wo=

	/**
	 * 如果不写工作模式和填充模式,默认使用的是:ECB/PKCS5Padding
	 * CBC模式:要求init方法需求添加额外参数
	 */
	private static final String TRANSFORMATION = "DES/CBC/PKCS5Padding";// 算法/工作模式/填充模式
	//private static final String TRANSFORMATION = "DES/CBC/NoPadding";//不填充:DES原文长度必须是8个byte整数倍
	private static final String ALGORITHM = "DES";// 算法

	public static void main(String[] args) {
		// 原文
		String input = "欢迎来到黑马程序员";//
		System.out.println("原文byte数组长度:" + input.getBytes().length);
		// 秘钥
		String password = "12345678";// DES秘钥长度8位
		
		//DES加密密文长度8的整数倍

		// 加密算法思路:通过查看api文档封装公司自己的加密算法
		// 加密算法核心类:Cipher

		String encrypt = encrypt(input, password);
		System.out.println("DES加密:" + encrypt);
		System.out.println(encrypt.length());

		String decrypt = decrypt(encrypt, password);
		System.out.println("DES解密:" + new String(decrypt));
	}

	/**
	 * 5.DES加密
	 * 
	 * @param input
	 * @param password
	 */
	/*public static byte[] encrypt(String input, String password) {
		try {
			// 加密算法三
	 * @return
	 */
	public static String encrypt(String input, String password) {
		try {
			// 加密算法三部曲
			// 1.创建Cipher对象
			Cipher cipher = Cipher.getInstance(TRANSFORMATION);
			// 秘钥工厂
			SecretKeyFactory skf = SecretKeyFactory.getInstance(ALGORITHM);
			KeySpec keySpec = new DESKeySpec(password.getBytes());// 秘钥规则对象
			Key key = skf.generateSecret(keySpec);// 秘钥对象
			// 2.初始化模式:加密/解密
			//cipher.init(Cipher.ENCRYPT_MODE, key);
			IvParameterSpec iv = new IvParameterSpec(password.getBytes());
			cipher.init(Cipher.ENCRYPT_MODE, key, iv);
			// 3.加密/解密
			byte[] encryptBytes = cipher.doFinal(input.getBytes());
			//System.out.println("没有Base64编码长度="+encryptBytes.length);

			// System.out.println("DES加密:"+new String(encryptBytes));

			return Base64.encode(encryptBytes);

		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * DES解密
	 * 
	 * @param input
	 * @param password
	 */
	public static String decrypt(String input, String password) {
		try {
			// 加密算法三部曲
			// 1.创建Cipher对象
			Cipher cipher = Cipher.getInstance(TRANSFORMATION);
			// 秘钥工厂
			SecretKeyFactory skf = SecretKeyFactory.getInstance(ALGORITHM);
			KeySpec keySpec = new DESKeySpec(password.getBytes());// 秘钥规则对象
			Key key = skf.generateSecret(keySpec);// 秘钥对象
			// 2.初始化模式:加密/解密
			IvParameterSpec iv = new IvParameterSpec(password.getBytes());
			//cipher.init(Cipher.DECRYPT_MODE, key);
			cipher.init(Cipher.DECRYPT_MODE, key, iv);
			// 3.加密/解密
			byte[] encryptBytes = cipher.doFinal(Base64.decode(input));//Base64解密

			// System.out.println("DES加密:"+new String(encryptBytes));

			return new String(encryptBytes);

		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

}

Base64编码和解码

加密后的结果是字节数组,这些被加密后的字节在码表(例如GBK、 UTF-8 码表)上找不到对应字符,会出现乱码,当乱码字符串再次转换为字节数组时,长度会变化,导致解密失败,所以转换后的数据是不安全的。

​ 使用 Base64 对字节数组进行编码,任何字节都能映射成对应的 Base64 字符,之后能恢复到字节数组,利于加密后数据的保存和传输,所以转换是安全的。

•DES加密密文长度:8的整数倍

•DES加密后编码表找不到对应字符:Bsae64编码

•DES解密前:对密文Base64解码

​ 内容:Base64编码和解密,解决DES等加密后乱码问题

java

import com.itheima.crypt.util.Base64;

/**
 * 6.Base46编码和解密
 */
public class Base64Demo {

	public static void main(String[] args) {
		// 原文
		String input = "黑马";
		// 秘钥
		String password = "12345678";// DES秘钥长度8位
		printBytes(input);
		
		System.out.println("原文byte数组长度:" + input.getBytes().length);
		
		String encrypt = DESCrypt.encrypt(input, password);
		
		System.out.println("DES加密密文:" + encrypt);
		//System.out.println("DES加密密文Base64编码:" + Base64.encode(encrypt));
		printBytes(new String(encrypt));
		
		//加密后密文长读发生改变,在编码表找不到对应字符,乱码
		
		System.out.println("DES密文byte数组长度:" + encrypt.getBytes().length);
		
		//密文希望变成可见:ABC-Z
		
	}
	
	public static void printBytes(String input){
		byte[] bytes = input.getBytes();
		for (byte b : bytes) {
			System.out.print(b + " ");
		}
	}

}

3DES算法

3-DES 算法使用了 3 个独立密钥(密钥长度为168bit)进行三重 DES 加密,这就比 DES 大大提高了安全性。如果 56 位 DES 用穷举搜索来破译需要 2∧56 次运算,而 3-DES 则需要 2∧112 次。

AES加密和解密

高级加密标准

AES使用的分组大小为128bit,密钥长度可以为128、192、256 bit。最简单最常用的也就是 128 bit(16字节) 的密钥。

AES 加密过程涉及到 4 种操作:字节替代(SubBytes)、行移位(ShiftRows)、列混淆(MixColumns)和轮密钥加(AddRoundKey)。

​ 解密过程分别为对应的逆操作。由于每一步操作都是可逆的,按照相反的顺序进行解密即可恢复明文。加解密中每轮的密钥分别由初始密钥扩展得到。

密钥128个bit位

java

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import com.itheima.crypt.util.Base64;

/**
 * 6.对称加密算法:AES加密解密
 */
public class AESCrypt {
	
	
	
	private static final String ALGORITHM = "AES";
	//private static final String TRANSFORMATION  = "AES/ECB/PKCS5Padding";
	private static final String TRANSFORMATION  = "AES/CBC/PKCS5Padding";
	// private static final String TRANSFORMATION  = "AES/CBC/NoPadding";//不填充:AES原文长度必须是16个字节整数倍

	public static void main(String[] args) {
		String input = "欢迎来到黑马程序员";
		String password = "1234567812345678";//AES秘钥长度16
		
		String encrypt = encrypt(input, password);
		System.out.println("AES加密:" + encrypt);
		System.out.println(encrypt.length());
		String decrypt = decrypt(encrypt, password);
		System.out.println("AES解密:" + decrypt);
		
	}

	/**
	 * AES加密
	 * @param input
	 * @param password
	 */
	public static String encrypt(String input, String password) {
		try {
			//加密算法三部曲
			
			//1.创建Cipher对象
			Cipher cipher = Cipher.getInstance(TRANSFORMATION);
			//秘钥工厂生成秘钥:没有AES参数
			//秘钥规则类
			SecretKeySpec key = new SecretKeySpec(password.getBytes(), ALGORITHM);
			
			//Key key = null;//秘钥对象:通过字符串封装成AES需要的类型
			//2.初始化加密/解密模式
			//cipher.init(Cipher.ENCRYPT_MODE, key);
			IvParameterSpec iv = new IvParameterSpec(password.getBytes());
			cipher.init(Cipher.ENCRYPT_MODE, key, iv);
			//3.加密/解密
			byte[] encryptBytes = cipher.doFinal(input.getBytes());
			String encrypt = Base64.encode(encryptBytes);
			
			//System.out.println("AES加密:" + new String(encryptBytes));
			//System.out.println(encrypt);
			return encrypt;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * AES解密
	 * @param input 密文
	 * @param password
	 * @return
	 */
	public static String decrypt(String input, String password) {
		try {
			//加密算法三部曲
			
			//1.创建Cipher对象
			Cipher cipher = Cipher.getInstance(TRANSFORMATION);
			//秘钥工厂生成秘钥:没有AES参数
			//秘钥规则类
			SecretKeySpec key = new SecretKeySpec(password.getBytes(), ALGORITHM);
			
			//Key key = null;//秘钥对象:通过字符串封装成AES需要的类型
			//2.初始化加密/解密模式
			//cipher.init(Cipher.DECRYPT_MODE, key);
			IvParameterSpec iv = new IvParameterSpec(password.getBytes());
			cipher.init(Cipher.DECRYPT_MODE, key, iv);
			//3.加密/解密
			byte[] encryptBytes = cipher.doFinal(Base64.decode(input));
			
			//System.out.println("AES加密:" + new String(encryptBytes));
			//System.out.println(encrypt);
			return new String(encryptBytes);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}


}

DES和AES加密分析

java

/**
 * 7.DES、AES秘钥长度分析
 */
public class DesAesKeyLength {
	
	public static void main(String[] args) {
		
		String input = "欢迎来到黑马程序员";
		String desPassword = "12345678";// DES秘钥长度8位
		//String desPassword = "il黑马";// 
		
	/*	
	 * 
	 * DES/CBC/NoPadding (56) 
		DES/CBC/PKCS5Padding (56) 
		DES/ECB/NoPadding (56) 
		DES/ECB/PKCS5Padding (56) 
		
		括号里面:bit位数:56位,密码长度是56位
		
		DES秘钥长度8个字节,一个字节占8个bit,一共占8*8=64个bit=64位,
		DES秘钥最后一个字节不参与加密计算,剩余7个字节参与计算,7 * 8 = 56 bit位
		
		*/
		
		//Byte和bit关系:一个字节占8位,1个Byte=8个bit
		System.out.println("DES秘钥字节个数:" + desPassword.getBytes().length);
		
		DESCrypt.encrypt(input, desPassword);
		
		
		
		//*******************************************************
		String aesPassword = "1234567812345678";//AES秘钥长度16
		
		/*AES/CBC/NoPadding (128) 
		AES/CBC/PKCS5Padding (128) 
		AES/ECB/NoPadding (128) 
		AES/ECB/PKCS5Padding (128) 
		
		128个bit位,等于128/8 = 16个字节,AES秘钥长度16个字节
		
		*/
		
		String encrypt = AESCrypt.encrypt(input, aesPassword);
		System.out.println(encrypt);

		
		
	}

}

工作模式

默认模式 ECB,•CBC工作模式:需要额外参数

名称英文全名方法优点缺点
ECBElectronic codebook电子密码本每块独立加密1.分块可以并行处理1.同样的原文得到相同的密文,容易被攻击
CBCCipher-block chaining密码分组链接每块加密依赖于前一块的密文1.同样的原文得到不同的密文 2.原文微小改动影响后面全部密文1.加密需要串行处理 2.误差传递

填充模式

•常用填充模式:PKCS5Padding、NoPadding,默认模式 PKCS5Padding

•NoPadding填充模式。DES:明文8个字节整数倍AES:明文16个字节整数倍

填充是对需要按块处理的数据,当数据长度不符合块处理需求时,按照一定方法填充满块长的一种规则。

名称方法示例
Zero padding最常见的方式,全填充0x00AA AA AA AA 00 00 00 00
PKCS7ANSI X.923的变体 填充1个字符就全0x01 填充2个字符就全0x02 不需要填充就增加一个块,填充块长度,块长为8就填充0x08,块长为16就填充0x10AA AA AA AA AA AA AA 01 AA AA AA AA 04 04 04 04 AA AA AA AA AA AA AA AA08 08 08 08 08 08 08 08
ANSI X.923Zero的改进,最后一个字节为填充字节个数AA AA AA AA 00 00 00 04
ISO 10126随机填充AA AA AA AA 81 A6 23 04
ISO/IEC 7816-4以0x80开始作为填充开始标记,后续全填充0x00AA AA AA AA AA AA AA 80 AA AA AA AA 80 00 00 00

三、非对称加密算法

•非对称加密算法RSA

•秘钥对:公钥和私钥

•非对称:公钥加密私钥解密、私钥加密公钥解密

•加密速度慢

非对称加密算法RSA介绍

•RSA不能手动指定秘钥,必须系统生成,•每次最大加密117字节。•每次最大解密128字节

•没必要每次都生成秘钥对

•一次生成,保存起来

java

import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.Cipher;

import com.itheima.crypt.util.Base64;

/**
 * 非对称加密RSA解密和解密
 */
public class RSACrypt {

	private static final String algorithm = "RSA";

	/** 分段加密,每次最大加密长度117字节 */
	private static final int ENCRYPT_MAX_SIZE = 117;
	/** 每次最大解密长度:128字节 */
	private static final int DECRYPT_MAX_SIZE = 128;
	
	private static final String publicStr = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCBFnKLitg+zWbLeepj+fHmtzaGTAmVvTx+qqzhenqjpWufnQQs3CIvmFA95c1XTu1JmXE0oY3mz9BVkmyNvmm5V98pkAjNc6pTA5D9xAPXGAZPcUkMEKAH+vNR301AKOIb8boBZqMqc+O/3LrYYeO8/jKaExIxpQqWQCzeCqTHvwIDAQAB";
	private static final String privateStr = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIEWcouK2D7NZst56mP58ea3NoZMCZW9PH6qrOF6eqOla5+dBCzcIi+YUD3lzVdO7UmZcTShjebP0FWSbI2+ablX3ymQCM1zqlMDkP3EA9cYBk9xSQwQoAf681HfTUAo4hvxugFmoypz47/cuthh47z+MpoTEjGlCpZALN4KpMe/AgMBAAECgYAftUXZpPdNJeMUJBRBDoou494Oyuqnz13mt0HT+OPbAq0W/diitEfpbP541dPEmGyM4ZX/GZfjlDRWcKsdfiI1vTUiJ5Y0SmQazjAIoHLayJA3ZbH0CrbxSMS7oFuKXOHDDj4iCYsJQVto81NAKPREgAQbuLGmrnz/p4TppVEOEQJBAMG4Cfap+j0ywp2HYHCygLRtlbV9H2IOEYhZE2KboWDU4ikmjfQ8SFftZDgbxSpsbrZSds27pKGjVq7MGoRXeJMCQQCqlvXsP8DaCO0zlMf38xxiuNFNeYV+yHoP6SWArscy+o6bnch/xlmIDr9nfRVgTA804M9S/hQmvnS0xKU160ulAkAYLRK5QP9k8dfN9x43El/zpJWBf+sRvrW7cXp03P1n60mKXzBqIbfZmVvfkL+rirrKcEI5bMigD5V63SgWCiCXAkAIEQlh+YXKCaAFz2RBUkqmVGz5R+TLIPm8pN60Hg+nVnfF3gksGZoEOAZPA3guTIpviq3jE8aqKllkbuQND6pNAkBrPTWuM3xZsHW1VE6EXQsz+SqGgXwXIw+9bERRX06LfcleAkz9qhqRRbvXzGxUQCBlvIEvo6VRnDLM4KT4maom";

	public static void main(String[] args) {
		try {
			/*// 秘钥对生成器
			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
			// 秘钥对称对象
			KeyPair keyPair = keyPairGenerator.generateKeyPair();

			// 生成秘钥对:公钥和私钥
			PrivateKey privateKey = keyPair.getPrivate();
			PublicKey publicKey = keyPair.getPublic();*/
			
			//创建秘钥工厂对象
			KeyFactory kf = KeyFactory.getInstance(algorithm);
			
			PrivateKey privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(privateStr)));
			PublicKey publicKey = kf.generatePublic(new X509EncodedKeySpec(Base64.decode(publicStr)));
			// 转成字符串
			String privateKeyStr = Base64.encode(privateKey.getEncoded());
			String publicKeyStr = Base64.encode(publicKey.getEncoded());
			System.out.println("公钥:" + publicKeyStr);
			System.out.println("私钥:" + privateKeyStr);

			// 原文
			String input = "黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马马黑马黑马黑马";
			;
			System.out.println("原文byte长度:" + input.getBytes().length);

			// RSA每次加密最大长度117字节
			String encryptByPublicKey = encryptByPublicKey(publicKey, input);
			System.out.println("公钥加密=" + encryptByPublicKey);
			String result = encryptByPrivateKey(privateKey, input);
			System.out.println("私钥加密=" + result);

			// 公钥解密
			String decryptByPublicKey = decryptByPublicKey(publicKey, result);
			System.out.println("公钥解密:" + decryptByPublicKey);
			String decryptByPrivateKey = decryptByPrivateKey(privateKey, encryptByPublicKey);
			System.out.println("私钥解密:" + decryptByPrivateKey);

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static PublicKey getPublicKey(){
		try {
			KeyFactory kf = KeyFactory.getInstance(algorithm);
			return kf.generatePublic(new X509EncodedKeySpec(Base64.decode(publicStr)));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public static PrivateKey getPrivateKey(){
		try {
			KeyFactory kf = KeyFactory.getInstance(algorithm);
			
			PrivateKey privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(privateStr)));
			return privateKey;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 私钥加密
	 */
	public static String encryptByPrivateKey(PrivateKey privateKey, String input) {
		try {
			// 私钥加密
			// Cipher加密解密三部曲
			// 1.创建Cipher对象
			Cipher cipher = Cipher.getInstance(algorithm);
			// 2.初始化模式:加密/解密
			cipher.init(Cipher.ENCRYPT_MODE, privateKey);
			// 3.加密/解密
			int offset = 0;
			byte[] buffer = new byte[1024];
			ByteArrayOutputStream baso = new ByteArrayOutputStream();
			while (input.getBytes().length - offset > 0) {
				if (input.getBytes().length - offset >= ENCRYPT_MAX_SIZE) {
					// 加密完整117长度
					buffer = cipher.doFinal(input.getBytes(), offset, ENCRYPT_MAX_SIZE);
					// 重新计算偏移量
					offset += ENCRYPT_MAX_SIZE;
				} else {
					// 剩余最后一块
					buffer = cipher.doFinal(input.getBytes(), offset, input.getBytes().length - offset);
					offset = input.getBytes().length;
				}
				baso.write(buffer);
			}
			// byte[] privateEncrypt = cipher.doFinal(input.getBytes());
			String result = Base64.encode(baso.toByteArray());
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 分段加密,每次最大加密长度117字节 公钥加密
	 */
	public static String encryptByPublicKey(PublicKey publicKey, String input) {
		try {
			// 私钥加密
			// Cipher加密解密三部曲
			// 1.创建Cipher对象
			Cipher cipher = Cipher.getInstance(algorithm);
			// 2.初始化模式:加密/解密
			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
			// 3.加密/解密
			int offset = 0;
			byte[] buffer = new byte[1024];
			ByteArrayOutputStream baso = new ByteArrayOutputStream();
			while (input.getBytes().length - offset > 0) {
				if (input.getBytes().length - offset >= ENCRYPT_MAX_SIZE) {
					// 加密完整117长度
					buffer = cipher.doFinal(input.getBytes(), offset, ENCRYPT_MAX_SIZE);
					// 重新计算偏移量
					offset += ENCRYPT_MAX_SIZE;
				} else {
					// 剩余最后一块
					buffer = cipher.doFinal(input.getBytes(), offset, input.getBytes().length - offset);
					offset = input.getBytes().length;
				}
				baso.write(buffer);
			}
			// byte[] privateEncrypt = cipher.doFinal(input.getBytes());
			String result = Base64.encode(baso.toByteArray());
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 公钥解密
	 * 
	 * @param input
	 *            密文(base64编码)
	 */
	public static String decryptByPublicKey(PublicKey publicKey, String input) {
		try {
			byte[] inputDecode = Base64.decode(input);
			// 私钥加密
			// Cipher加密解密三部曲
			// 1.创建Cipher对象
			Cipher cipher = Cipher.getInstance(algorithm);
			// 2.初始化模式:加密/解密
			cipher.init(Cipher.DECRYPT_MODE, publicKey);
			// 3.加密/解密
			int offset = 0;
			byte[] buffer = new byte[1024];
			ByteArrayOutputStream baso = new ByteArrayOutputStream();
			while (inputDecode.length - offset > 0) {
				if (inputDecode.length - offset >= DECRYPT_MAX_SIZE) {
					// 加密完整117长度
					buffer = cipher.doFinal(inputDecode, offset, DECRYPT_MAX_SIZE);
					// 重新计算偏移量
					offset += DECRYPT_MAX_SIZE;
				} else {
					// 剩余最后一块
					buffer = cipher.doFinal(inputDecode, offset, inputDecode.length - offset);
					offset = inputDecode.length;
				}
				baso.write(buffer);
			}
			//byte[] result = cipher.doFinal(inputDecode);
			return baso.toString();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 私钥解密
	 * @param privateKey
	 * @param input
	 * @return
	 */
	public static String decryptByPrivateKey(PrivateKey privateKey, String input) {
		try {
			byte[] inputDecode = Base64.decode(input);
			// 私钥加密
			// Cipher加密解密三部曲
			// 1.创建Cipher对象
			Cipher cipher = Cipher.getInstance(algorithm);
			// 2.初始化模式:加密/解密
			cipher.init(Cipher.DECRYPT_MODE, privateKey);
			// 3.加密/解密
			int offset = 0;
			byte[] buffer = new byte[1024];
			ByteArrayOutputStream baso = new ByteArrayOutputStream();
			while (inputDecode.length - offset > 0) {
				if (inputDecode.length - offset >= DECRYPT_MAX_SIZE) {
					// 加密完整117长度
					buffer = cipher.doFinal(inputDecode, offset, DECRYPT_MAX_SIZE);
					// 重新计算偏移量
					offset += DECRYPT_MAX_SIZE;
				} else {
					// 剩余最后一块
					buffer = cipher.doFinal(inputDecode, offset, inputDecode.length - offset);
					offset = inputDecode.length;
				}
				baso.write(buffer);
			}
			//byte[] result = cipher.doFinal(inputDecode);
			return baso.toString();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}


}

四、消息摘要算法

•消息摘要(Message Digest)又称为数字摘要(Digital Digest),它由一个单向Hash加密函数对消息进行作用而产生,消息摘要能够保证了消息的完整性。

•算法:md5、sha1、sha256

•加密后不可逆

•消息摘要的结果是固定长度,无论你的数据有多大,即使一个 G 的文件,摘要结果都是定长

•应用场景:加密用户登录注册密码

java

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.security.MessageDigest;

/**
 * 消息摘要工具类:核心类MessageDigest
 */
public class MessageDigestUtil {
	
	public static void main(String[] args) {
		//String input = "hello";//不论原文长度多长,加密密文长度固定
	/*	String input = "黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马马黑马黑马黑马";;//不论原文长度多长,加密密文长度固定
		md5(input);*/
		
		//******************读取文件md5摘要信息******************
		/*String md5File = md5File("apache-tomcat-9.0.1.zip");
		System.out.println("文件md5值:" + md5File);*/
		
		String input = "黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马黑马马黑马黑马黑马";;//不论原文长度多长,加密密文长度固定
		String sha1 = sha1(input);
		System.out.println(sha1);
		
		String sha256 = sha256(input);
		System.out.println(sha256);
		
		
	}
	
	public static String sha1(String input){
		try {
			//消息摘要2部曲
			//1.创建MessageDigest对象
			MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
			//2.调用digest方法
			byte[] digest = messageDigest.digest(input.getBytes());
			System.out.println("sha1 字节长度:" + digest.length);
			StringBuilder hex = toHex(digest);
			System.out.println("sha1 转成16进制字节长度:" + hex.toString().getBytes().length);
			return hex.toString();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public static String sha256(String input){
		try {
			//消息摘要2部曲
			//1.创建MessageDigest对象
			MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
			//2.调用digest方法
			byte[] digest = messageDigest.digest(input.getBytes());
			System.out.println("SHA-256 字节长度:" + digest.length);
			StringBuilder hex = toHex(digest);
			System.out.println("SHA-256 转成16进制字节长度:" + hex.toString().getBytes().length);
			return hex.toString();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 读取文件md5摘要
	 * @param filePath
	 * @return
	 */
	public static String md5File(String filePath) {
		//创建消息摘要对象
		try {
			//文件输入流
			FileInputStream fis = new FileInputStream(filePath);
			byte[] buffer = new byte[1024];
			int len = 0;
			//Byte数组输出流
			ByteArrayOutputStream baso = new ByteArrayOutputStream();
			while((len = fis.read(buffer)) != -1){
				baso.write(buffer, 0, len);
			}
			MessageDigest messageDigest = MessageDigest.getInstance("MD5");
			byte[] md5 = messageDigest.digest(baso.toByteArray());
			
			StringBuilder stringBuilder = toHex(md5);
			return stringBuilder.toString();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	
	
	/**
	 * MD5加密:
	 * 1.密文长度16个字节
	 * 2.转成16进制字符串32个字节
	 * @param input
	 * @return
	 */
	public static String md5(String input) {
		//创建消息摘要对象
		try {
			MessageDigest messageDigest = MessageDigest.getInstance("MD5");
			byte[] md5 = messageDigest.digest(input.getBytes());
			
			//没有转成16进制,原始密文长度
			System.out.println("md5密文长度:" + md5.length);
			
			StringBuilder stringBuilder = toHex(md5);
			System.out.println(stringBuilder.toString());
			
			System.out.println("tomcat md5值长度:" + "13a66f1118984cf9b45bbabae73d1d6d".getBytes().length);
			System.out.println("我们的 md5值长度:" + stringBuilder.toString().getBytes().length);
			return stringBuilder.toString();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 转成16进制字符串
	 * @param input
	 * @return
	 */
	public static StringBuilder toHex(byte[] input) {
		//转成16进制
		StringBuilder stringBuilder = new StringBuilder();
		//System.out.println("md5加密:"+Base64.encode(md5));
		for (byte b : input) {
			int value = b & 0xff;//转成16进制
			String hexString = Integer.toHexString(value);
			//16进制字符串长度不是2位数,前面补零
			if(hexString.length() == 1){
				stringBuilder.append("0");
			}
			//System.out.println(hexString);
			stringBuilder.append(Integer.toHexString(value));
		}
		return stringBuilder;
	}

}

消息摘要md5的使用

​ •md5加密后密文16个字节,密文转成十六进制32个字节

消息摘要sha1和sha256的使用

•sha1:摘要结果20个字节,转十六进制40个字节

•sha256:摘要结果32个字节,转十六进制64个字节

五、数字签名

•RSA数字签名=数字签名

•消息摘要和非对称加密的组合

•签名使用私钥

•校验使用公钥

•作用:校验数据完整性

java

import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;

import com.itheima.crypt.util.Base64;

/**
 * 数字签名
 */
public class SignatureDemo {
	
	private static final String ALGORITHM = "SHA256withRSA";

	public static void main(String[] args) {
		String input = "name=iPhone&price=6888&count=1";
		String sign = sign(input);
		System.out.println("签名="+sign);
		
		
		String input2 = "name=iPhone&price=600&count=1";
		boolean verify = verify(input2, sign);
		System.out.println("校验结果:" + verify);
	}

	/**
	 * 校验数字签名
	 * @param input 原文
	 * @param sign 签名
	 * @return
	 */
	public static boolean verify(String input, String sign) {
		try {
			//******************校验签名信息:四部曲******************
			//1.创建数字签名对象
			Signature signature = Signature.getInstance(ALGORITHM);
			PublicKey publicKey = RSACrypt.getPublicKey();
			//2.初始化校验
			signature.initVerify(publicKey);
			//3.传入原文
			signature.update(input.getBytes());
			//4.开始校验
			boolean verify = signature.verify(Base64.decode(sign));
			//System.out.println("校验结果="+verify);
			return verify;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}

	public static String sign(String input) {
		try {
			//******************签名四部曲******************
			//1.创建数字签名对象
			Signature signature = Signature.getInstance(ALGORITHM);
			PrivateKey privateKey = RSACrypt.getPrivateKey();
			//2.初始化签名
			signature.initSign(privateKey);
			//3.传入原文
			signature.update(input.getBytes());
			//4.开始签名
			byte[] sign = signature.sign();
			String encode = Base64.encode(sign);
			
		
			return encode;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

}

数字签名流程图分析

image-20220619222127767

数字签名实战1-时间戳

•用户登录md5密文传输,还不够安全

​ 内容:登录添加时间戳参数,避免抓包登录

java

import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

/**
 * 演示登录使用时间戳,结合数字签名算法
 */
public class MessageDigestUsage {
	
	public static void main(String[] args) {
		
		try {
			String url = "http://120.77.241.119/User/login?";
			String password = "12345678";//用户输入明文密码
			String md5 = MessageDigestUtil.md5(password);
			System.out.println(md5);
			//String registerUrl = "http://120.77.241.119/User/register?username=itheima&password="+md5;
			String params = "username=itheima&password="+md5;
			URLConnection conn = new URL(url + params).openConnection();
			System.out.println(conn.getURL().toString());;
			InputStream is = conn.getInputStream();
			String result = Util.inputStream2String(is);
			System.out.println("登录结果:" + result);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
java

import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

/**
 * 演示登录使用时间戳,结合数字签名算法
 */
public class MessageDigestUsage2 {
	
	public static void main(String[] args) {
		
		try {
			//String url = "http://120.77.241.119/User/login?";//能够重复登录,抓包危险
			//获取时间戳
			String timestamp = System.currentTimeMillis() + "";
			System.out.println(timestamp);
			String url = "http://120.77.241.119/User/login_v2?";
			String password = "12345678";//用户输入明文密码
			String md5 = MessageDigestUtil.md5(password);
			System.out.println(md5);
			//String registerUrl = "http://120.77.241.119/User/register?username=itheima&password="+md5;
			String params = "username=itheima&password="+md5+"&timestamp="+timestamp;
			//对参数签名:为了避免抓包串改时间戳,重新登录
			String sign = SignatureDemo.sign(params);
			URLConnection conn = new URL(url + params+"&sign="+sign).openConnection();
			System.out.println(conn.getURL().toString());;
			InputStream is = conn.getInputStream();
			String result = Util.inputStream2String(is);
			System.out.println("登录结果:" + result);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
java

import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

/**
 * 演示登录使用时间戳,结合数字签名算法,判断抓包重复登录
 */
public class MessageDigestUsage3 {
	
	public static void main(String[] args) {
		
		try {
			//String url = "http://120.77.241.119/User/login?";//能够重复登录,抓包危险
			//获取时间戳
			String timestamp = System.currentTimeMillis() + "";
			System.out.println(timestamp);
			String url = "http://120.77.241.119/User/login_v3?";
			String password = "12345678";//用户输入明文密码
			String md5 = MessageDigestUtil.md5(password);
			System.out.println(md5);
			//String registerUrl = "http://120.77.241.119/User/register?username=itheima&password="+md5;
			String params = "username=Marry&password="+md5+"&timestamp="+timestamp;
			//对参数签名:为了避免抓包串改时间戳,重新登录
			String sign = SignatureDemo.sign(params);
			URLConnection conn = new URL(url + params+"&sign="+sign).openConnection();
			
			String md52 = MessageDigestUtil.md5(params+"&sign="+sign);
			System.out.println("登录参数md5:" + md52);
			
			System.out.println(conn.getURL().toString());;
			InputStream is = conn.getInputStream();
			String result = Util.inputStream2String(is);
			System.out.println("登录结果:" + result);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

数字签名实战2-校验重复登录

​ 内容:登录添加时间戳参数,避免抓包登录,避免重复登录

六、加密算法总结

对称加密

•算法:DES、AES

•加密速度快

•对称:有秘钥就可以破解

•应用场景:只要可逆都可以使用,比如缓存信息

非对称加密

•算法:RSA

•秘钥对:公钥和私钥

•秘钥对不能手动指定,由系统生成

•公钥加密私钥解密、私钥加密公钥解密

•公钥互换

信息摘要

•算法:md5、sha1、sha256

•摘要后不可逆

•摘要结果是固定长度,和数据大小无关

•应用场景:不可逆都可以使用,比如用户密码

数字签名

•算法:消息摘要结合RSA,比如SHA256withRSA

•私钥签名、公钥校验

•应用场景:校验数据完整性,支付宝支付校验支付参数