程序猿成长之路之密码学篇-分组密码加密模式及IV(偏移量)的详解

Cipher.getInstance("AES/ECB/PKCS5Padding");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
在进行加解密编程的时候应该有很多小伙伴接触过以上的语句,但是大伙儿在编码过程中是否了解过ECB/CBC的含义、区别以及PKCS5Padding的含义?如果不清楚的话那么希望这篇文章可以帮到你们。

什么是分组密码?

分组密码就是按照固定长度的字符对明文加密/密文解密的一种加解密算法。常见的有DES、AES等。具体如下图所示
程序猿成长之路之密码学篇-分组密码加密模式及IV(偏移量)的详解_第1张图片

什么是IV(偏移量)?

可以理解为加解密过程中设置的可变变量,可以是时间戳、uuid也可以是其他随机值,目的是为了增加混乱度,降低被破译的风险。

什么是分组加密模式?

对于一般加解密(或ECB)而言,我们只是调用了加解密函数分组加密就完事了,但是如果遇到了重放攻击(多次发起同一请求),那么黑客在截获密文后可以较为轻松的按照分组进行解密,那么这时候就会对系统安全性造成威胁。为了防止这种情况的发生,我们就可以采取一定的措施去对分组密码进行轮次迭代处理,上述提到的ECB(电子密码本模式)/CBC(密文分组链接模式)就属于分组加密模式。此外还有CFB(密文反馈模式)等也属于分组加密模式。
下面我们来一一分析不同分组加密模式的含义以及各自的优缺点。

ECB(电子密码本模式)
程序猿成长之路之密码学篇-分组密码加密模式及IV(偏移量)的详解_第2张图片
过程/原理:
这种分组加密模式就属于最简单的不经过处理的分组加密模式,直接对明文或密文按组进行加密或解密得到结果。

优点:
-支持多线程异步处理,效率较高
-无需分组轮次迭代计算,计算量更小

缺点:
-安全性能较差,容易被攻破

CBC(密文分组链接模式)
程序猿成长之路之密码学篇-分组密码加密模式及IV(偏移量)的详解_第3张图片
过程/原理:
加密:
第一轮:生成初始化向量iv,与第一组明文分组异或运算后共同加密。
后续:拿上一轮CBC运算结果与当前分组明文异或运算后加密。
解密:
第一轮:生成初始化向量iv,先对第一组密文分组解密后再与初始化向量进行异或运算。
后续:先对当前密文分组解密后再与上一轮密文分组进行异或运算
加解密原理:
A异或B异或B = A
例如第一轮加密后的第一组密文 e1 = Ek(iv xor 第一组明文)
则第一轮解密后的第一组明文 d1 = (Ek’(第一组密文) xor iv) = (iv xor 第一组明文) xor iv = 第一组明文

优点:
-使用了iv作为随机变量,增加了破译的难度,使得每次针对同一明文加密后的结果不一致
-加密使用前一轮的输出作为后一轮的输入

缺点:
-增加了计算量,加大了计算开销

CFB(密文反馈模式)
程序猿成长之路之密码学篇-分组密码加密模式及IV(偏移量)的详解_第4张图片
过程/原理:
加密:
第一轮:生成初始化向量iv,先进行加密计算后与第一组明文分组进行异或运算。
后续:拿上一轮CFB运算结果先加密后与当前分组明文进行异或运算。
解密:
第一轮:生成初始化向量iv,先进行加密计算后与第一组密文分组进行异或运算。
后续:拿上一轮CFB运算结果先加密后与当前分组密文进行异或运算。
加解密原理:
A异或B异或B = A
例如第一轮加密后的第一组密文 e1 = Ek(iv) xor 第一组明文
则第一轮解密后的第一组明文 d1 = (Ek(iv) xor 第一组密文) = (Ek(iv) xor (Ek(iv) xor 第一组明文) ) = 第一组明文

优点:
-使用了iv作为随机变量,增加了破译的难度,使得每次针对同一明文加密后的结果不一致
-加密使用前一轮的输出作为后一轮的输入
-iv在分组加密算法中可以单独使用

缺点:
-增加了计算量,加大了计算开销

分组加密模式代码实现

注:如需要AES代码详见以下文章:
程序猿成长之路之密码学篇-AES算法解密详解及代码呈现 https://blog.csdn.net/qq_31236027/article/details/131206018

枚举类型

public enum EncryptMode {
	CBC("CBC"),
	CFB("CFB");
	
	private String name;
	
	private EncryptMode(String name) {
		this.setName(name);
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

加密部分

/**
	 * 分组加密(128位一组)【完整版】
	 * @param text 明文
	 * @param mode 加密模式(CFB、CBC)
	 * @param iv 偏移量
	 */
	@Override
	public String encrypt(String text, String iv, EncryptMode mode) {
		if(mode == null) {
			return encrypt(text);
		}
		String result = null;
		switch(mode) {
			case CBC: result = encryptCBC(text,iv); break;
			case CFB: result = encryptCFB(text,iv);break;
			default: result = encrypt(text);break;
		}
		return result;
	}
	

	/**
	 * 分组加密(128位一组)(无iv【偏移量】版)
	 * @param text 明文
	 */
	private String encrypt(String text) {
		StringBuilder sb = new StringBuilder();
		int textLen = text.length();
		//获取分组长度
		// DIV_LEN * CHAR_LEN = 128
		// 根据DIV_LEN进行分组,如CHAR_LEN=16位,那么就每8个字符一组
		int divLen = textLen % AESConstant.DIV_LEN == 0 ? textLen / AESConstant.DIV_LEN : (textLen / AESConstant.DIV_LEN + 1);
		//分组加密处理
		for (int i = 0; i < divLen; i++) {
			int startIndex = i * AESConstant.DIV_LEN;
			int endIndex = (startIndex + AESConstant.DIV_LEN) >= textLen ? textLen : (startIndex + AESConstant.DIV_LEN);
			String substr = text.substring(startIndex, endIndex);
			//尾部填充
			while(substr.length() < AESConstant.DIV_LEN) {
				substr += " ";
			}
			sb.append(EncodeUtil.binaryToHexStr(baseEncrypt(substr)).trim());
		}
		return new BASE64Encoder().encode(sb.toString().trim().getBytes());
	}
	
	/**
	 * 分组加密(128位一组),(有iv【偏移量】CBC版,更安全)
	 * 
	 * CBC特性
	 * 1. 每一组分组的密文都依赖于上一组的结果
	 * 2. 加入了iv偏移量使得每次加密执行后的结果都不一致
	 * 
	 * @param text 明文
	 * @param iv 偏移量
	 */
	private String encryptCBC(String text,String iv) {
		StringBuilder sb = new StringBuilder();
		int textLen = text.length();
		//获取分组长度
		// DIV_LEN * CHAR_LEN = 128
		// 根据DIV_LEN进行分组,如CHAR_LEN=16位【UNICODE】,那么就每8个字符一组
		int divLen = textLen % AESConstant.DIV_LEN == 0 ? textLen / AESConstant.DIV_LEN : (textLen / AESConstant.DIV_LEN + 1);
		// CFB加密初始化向量
		String encryptedPart = iv;
		//分组加密处理
		for (int i = 0; i < divLen; i++) {
			int startIndex = i * AESConstant.DIV_LEN;
			int endIndex = (startIndex + AESConstant.DIV_LEN) >= textLen ? textLen : (startIndex + AESConstant.DIV_LEN);
			String substr = text.substring(startIndex, endIndex);
			//尾部填充
			while(substr.length() < AESConstant.DIV_LEN) {
				substr += " ";
			}
			while(encryptedPart.length() < AESConstant.DIV_LEN) {
				encryptedPart += " ";
			}
			//CBC关键,需要拿明文与上一轮结果进行异或得到的结果共同加密作为下一轮的输入
			encryptedPart = EncodeUtil.binaryToStr(
					baseEncrypt(strXor(encryptedPart,substr)), 16
			);
			sb.append(encryptedPart);
		}
		//批量处理为16进制后base64运算
		String result = sb.toString().trim();
		result = EncodeUtil.strtoBinary(result, 16);
		result = EncodeUtil.binaryToHexStr(result);
		return new BASE64Encoder().encode(result.getBytes());
	}
	
	/**
	 * 分组加密(128位一组),(有iv【偏移量】CFB版,更安全)
	 * 
	 * CFB特性
	 * 
	 * @param text 明文
	 * @param iv 偏移量
	 */
	private String encryptCFB(String text,String iv) {
		StringBuilder sb = new StringBuilder();
		int textLen = text.length();
		//获取分组长度
		// DIV_LEN * CHAR_LEN = 128
		// 根据DIV_LEN进行分组,如CHAR_LEN=16位【UNICODE】,那么就每8个字符一组
		int divLen = textLen % AESConstant.DIV_LEN == 0 ? textLen / AESConstant.DIV_LEN : (textLen / AESConstant.DIV_LEN + 1);
		// CFB加密初始化向量
		String encryptedPart = iv;
		//分组加密处理
		for (int i = 0; i < divLen; i++) {
			int startIndex = i * AESConstant.DIV_LEN;
			int endIndex = (startIndex + AESConstant.DIV_LEN) >= textLen ? textLen : (startIndex + AESConstant.DIV_LEN);
			String substr = text.substring(startIndex, endIndex);
			//尾部填充
			while(substr.length() < AESConstant.DIV_LEN) {
				substr += " ";
			}
			while(encryptedPart.length() < AESConstant.DIV_LEN) {
				encryptedPart += " ";
			}
			//CFB关键,需要拿明文与上一轮加密结果进行异或得到的结果作为下一轮的输入
			encryptedPart = strXor(EncodeUtil.binaryToStr(
					baseEncrypt(encryptedPart), 16
			),substr);
			sb.append(encryptedPart);
		}
		//批量处理为16进制后base64运算
		String result = sb.toString().trim();
		result = EncodeUtil.strtoBinary(result, 16);
		result = EncodeUtil.binaryToHexStr(result);
		return new BASE64Encoder().encode(result.getBytes());
	}

解密部分

	/**
	 * 分组解密(128位一组)【完整版】
	 * @param encrytedText 密文
	 * @param mode 加密模式(CFB、CBC)
	 * @param iv 偏移量
	 */
	@Override
	public String decrypt(String encrytedText, String iv, EncryptMode mode) {
		if(mode == null) {
			return decrypt(encrytedText);
		}
		String result = null;
		switch(mode) {
			case CBC: result = decryptCBC(encrytedText,iv); break;
			case CFB: result = decryptCFB(encrytedText,iv);break;
			default: result = decrypt(encrytedText);break;
		}
		return result;
	}


	/**
	 * 分组解密
	 * @param encrytedText 密文
	 */
	private String decrypt(String encrytedText) {
		try {
			//base64解码
			byte[] bytes = new BASE64Decoder().decodeBuffer(encrytedText);
			String str = new String(bytes,Charset.forName("UTF8"));
			int textLen = str.length();
			StringBuilder sb = new StringBuilder();
			int divLen = textLen < 32 ? 1 : (int)(Math.ceil(textLen/(4*8*1.0))); //因为加密后会自动填充所以长度必为字符长度的倍数(HEX 4位)
			//分组解密
			for (int i = 0; i< divLen; i++) {
				int startIndex = i * (4*8);
				int endIndex = (startIndex + (4*8));
				String temp = str.substring(startIndex, endIndex);
				sb.append(baseDecrypt(temp));
			}
			return sb.toString();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 分组解密(128位一组),(有iv【偏移量】CBC版)
	 * @param encrytedText 密文
	 * @param iv 偏移量
	 * @return 明文
	 */
	private String decryptCBC(String encrytedText,String iv) {
		try {
			//base64解码
			byte[] bytes = new BASE64Decoder().decodeBuffer(encrytedText);
			String str = new String(bytes,Charset.forName("UTF8"));
			int textLen = str.length();
			StringBuilder sb = new StringBuilder();
			int divLen = textLen < 32 ? 1 : (int)(Math.ceil(textLen/(4*8*1.0))); //因为加密后会自动填充所以长度必为字符长度的倍数(HEX 4位)
			//CFB解密初始化向量
			String decryptedPart = iv;
			//分组解密
			for (int i = 0; i< divLen; i++) {
				int startIndex = i * (4*8);
				int endIndex = (startIndex + (4*8));
				String temp = str.substring(startIndex, endIndex);
				//尾部填充
				while(decryptedPart.length() < AESConstant.DIV_LEN) {
					decryptedPart += " ";
				}
				//转换成16位的字符,方便strXor运算
				sb.append(strXor(baseDecrypt(temp),decryptedPart));
				//位数转换
				decryptedPart = EncodeUtil.binaryToStr(EncodeUtil.toBinary(temp, EncodeRadix.HEX), 16);
			}
			return sb.toString();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 分组解密(128位一组),(有iv【偏移量】CFB版)
	 * @param encrytedText 密文
	 * @param iv 偏移量
	 * @return 明文
	 */
	private String decryptCFB(String encrytedText,String iv) {
		try {
			//base64解码
			byte[] bytes = new BASE64Decoder().decodeBuffer(encrytedText);
			String str = new String(bytes,Charset.forName("UTF8"));
			int textLen = str.length();
			StringBuilder sb = new StringBuilder();
			int divLen = textLen < 32 ? 1 : (int)(Math.ceil(textLen/(4*8*1.0))); //因为加密后会自动填充所以长度必为字符长度的倍数(HEX 4位)
			//CFB解密初始化向量(转为16进制方便计算
			String decryptedPart = iv;
			//分组解密
			for (int i = 0; i< divLen; i++) {
				int startIndex = i * (4*8);
				int endIndex = (startIndex + (4*8));
				String temp = str.substring(startIndex, endIndex);
				//转换成16位的字符,方便strXor运算
				temp = EncodeUtil.binaryToStr(EncodeUtil.toBinary(temp, EncodeRadix.HEX), 16);
				//尾部填充
				while(decryptedPart.length() < AESConstant.DIV_LEN) {
					decryptedPart += " ";
				}
				//转换成16位的字符,方便strXor运算
				sb.append(
					strXor(EncodeUtil.binaryToStr(baseEncrypt(decryptedPart), 16),temp)
				);
				decryptedPart = temp;
			}
			return sb.toString();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

运行代码

public static void main(String[] args) {
		AesUtil util = new AesUtil();
		//偏移量(8个字符,每个字符16位)
		String iv = UUID.randomUUID().toString().substring(0,8);
		//CFB(密文反馈模式)
		String encrytedStr = util.encrypt(
				"{\"code\":200,\"message\":\"成功!\",\"data\":{\"id\":\"2103813902831\",\"name\":\"章鱼哥是我啊\",\"gender\":\"男\"}}"
				,iv
				,EncryptMode.CFB
		);
		System.out.println("encrytedStr = " + encrytedStr);
		System.out.println("result= " + util.decrypt(encrytedStr,iv,EncryptMode.CFB));
	}

最后是运行截图
程序猿成长之路之密码学篇-分组密码加密模式及IV(偏移量)的详解_第5张图片
————————————————PKCS5Padding后续再讲————————————————————

你可能感兴趣的:(笔记,密码学,java,网络,算法)