程序猿成长之路之密码学篇-AES算法解密详解及代码呈现

各位csdn的小伙伴们大家好呀,我又回来了,这篇文章为上一次介绍AES加密算法的姊妹篇,重点将会详细介绍一下AES算法的解密过程并呈上AES加解密的代码。【暂时不包含iv即偏移量】。下面请跟随我一同进入AES解密的世界。

AES加密详解

如果有小伙伴对AES算法或者AES加密还有所疑问的,可以参考一下我之前写的文章。
https://blog.csdn.net/qq_31236027/article/details/129796471

AES解密

与DES解密仅需要调换密钥组的顺序相比,AES解密会变得更为繁琐。

整体流程

假设大家都了解了AES的加密过程,那么我们知道加密共11轮,而解密亦是如此。加密第一轮就只进行一次轮密钥加,而最后一轮加密则是无需列混淆的参与。解密的过程则恰好相反,解密它先是进行最后一轮加密过程逆置,之后往前递推,直至第一轮加密逆置,如下图所示。
程序猿成长之路之密码学篇-AES算法解密详解及代码呈现_第1张图片
在了解完整体流程后下面就来利用“分而治之”思想,将难点进行拆分讲解。

轮密钥加

密钥参与运算也是从后往前,即加密的最后一轮的密钥参与第一轮的解密。为了方便编程我们可以使用全局变量来存储加解密密钥。这里我不过多介绍密钥的编排原理,大家如有疑问可参考我上一篇介绍AES加密的博客。

逆向行移位

AES加密算法中行移位
第一行不动,第二行循环左移一位,第三行循环左移两位,第四行循环左移三位。
程序猿成长之路之密码学篇-AES算法解密详解及代码呈现_第2张图片
也就是说原来4*4的矩阵中B[0][1] -> B[1][2](如图中的B1 -> B5) 【注意,此矩阵读法为自上往下,自左往右读】。
为了方便理解和计算,我们可以把这一矩阵一维化成一维数组,即B[0] = B[0][0] = B0, B[1] = B[0][1] = B1, B[2] = B[0][2] = B2 … B[16] = B[3][3] = B15,
那么我们通过数学归纳法,根据移位后B[1] -> B[5], B[2] -> B[10], B[3]->B[15] 易得:B[i] = B[(4 * (i/4) + 5 * (i % 4))mod 16] 【i 为下标】【结合图片更便于理解】

上面是加密中行移位的算法,理解了这一点,解密的逆向行移位就变得简单了。
解密无非是将左移改为右移,即 第一行不动,第二行循环右移一位,第三行循环右移两位,第四行循环右移三位。如下图所示
程序猿成长之路之密码学篇-AES算法解密详解及代码呈现_第3张图片
变成
程序猿成长之路之密码学篇-AES算法解密详解及代码呈现_第4张图片
【注意,此矩阵读法为自上往下,自左往右读】。
为了方便理解和计算,我们可以把这一矩阵一维化成一维数组,即B[0] = B[0][0] = B0, B[1] = B[0][1] = B1, B[2] = B[0][2] = B2 … B[16] = B[3][3] = B15,
那么我们同样通过数学归纳法易得:B[i] = B[(4 * (i/4) + 13 * (i % 4))mod 16] 【i 为下标】【结合图片更便于理解】

逆向列混淆

AES加密算法中列混淆
使用伽罗瓦域运算的加法和乘法【伽罗瓦域详见AES介绍文章,运算均为模运算】计算出密文矩阵和列混淆固定矩阵的乘积然后获取结果矩阵。固定矩阵如下图所示:
程序猿成长之路之密码学篇-AES算法解密详解及代码呈现_第5张图片
为了方便计算,也可以将矩阵进行一维化之后进行模数运算。这里我采用了一种比较快速的模数运算方案:
/**
* 算法解释:
* 伽罗瓦域乘法默认为模数运算
* (110)2 * (11)2 => (x^2 + x) * (x + 1) => x^3 + x^2 + x^2 + x => x^3 + x => (1010)2
* 可以理解为先将(110)2左移一位后与(110)进行异或运算 得到 => (1010)2
*/`
while (i < 模数2长度) {
result ^=(numb << (模数2长度 - i)); //表示模数1向左移n位后的值存储于result中并于之前的值进行异或。详见算法解释。
i自加
}
其中numb为模数1的值。之后矩阵模数运算后的结果如果超过8位就要进行归化处理,即与不可约多项式m(x)=x8+x4+x3+x+1(0X11B)进行异或运算

在了解完AES算法的列混淆之后,AES解密的列混淆也就变得简单了,它只要修改固定矩阵就行,如下所示:
{“0E”,“0B”,“0D”,“09”},
{“09”,“0E”,“0B”,“0D”},
{“0D”,“09”,“0E”,“0B”},
{“0B”,“0D”,“09”,“0E”}

逆向字节代换

这个比较简单就是和加密一样,将8位密文前4位作为行数,后4位作为列数在逆置s盒中获值即可。

逆置s盒:

/**
	 * 逆向s-盒
	 */
	public static final String[][] REVERSE_SBOX = {
			{"52","09","6A","D5","30","36","A5","38","BF","40","A3","9E","81","F3","D7","FB"},
			{"7C","E3","39","82","9B","2F","FF","87","34","8E","43","44","C4","DE","E9","CB"},
			{"54","7B","94","32","A6","C2","23","3D","EE","4C","95","0B","42","FA","C3","4E"},
			{"08","2E","A1","66","28","D9","24","B2","76","5B","A2","49","6D","8B","D1","25"},
			{"72","F8","F6","64","86","68","98","16","D4","A4","5C","CC","5D","65","B6","92"},
			{"6C","70","48","50","FD","ED","B9","DA","5E","15","46","57","A7","8D","9D","84"},
			{"90","D8","AB","00","8C","BC","D3","0A","F7","E4","58","05","B8","B3","45","06"},
			{"D0","2C","1E","8F","CA","3F","0F","02","C1","AF","BD","03","01","13","8A","6b"},
			{"3A","91","11","41","4F","67","DC","EA","97","F2","CF","CE","F0","B4","E6","73"},
			{"96","AC","74","22","E7","AD","35","85","E2","F9","37","E8","1C","75","DF","6E"},
			{"47","F1","1A","71","1D","29","C5","89","6F","B7","62","0E","AA","18","BE","1B"},
			{"FC","56","3E","4B","C6","D2","79","20","9A","DB","C0","FE","78","CD","5A","F4"},
			{"1F","DD","A8","33","88","07","C7","31","B1","12","10","59","27","80","EC","5F"},
			{"60","51","7F","A9","19","B5","4A","0D","2D","E5","7A","9F","93","C9","9C","EF"},
			{"A0","E0","3B","4D","AE","2A","F5","B0","C8","EB","BB","3C","83","53","99","61"},
			{"17","2B","04","7E","BA","77","D6","26","E1","69","14","63","55","21","0C","7d"}
	};

设计思路

在掌握了解密流程及算法思想后,我们可以进行解密的设计。

  1. 首先通过一个decrypt方法实现密文分组。
  2. 通过baseDecrypt方法实现每组的解密。
  3. 通过decLoop方法实现迭代解密(将轮密钥加、列混淆、行位移、字节代换写在里面)。
  4. 输出明文。

下面为正式代码:

package aes;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import common.EncodeUtil;
import common.IEncrytion;
import common.EncodeUtil.EncodeRadix;
import constant.AESConstant;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

/**
 * aes 加解密工具(无iv【偏移量】版)
 * @author zygswo
 *
 */
public class AesUtil implements IEncrytion{
	
	/**
	 * 密钥对象
	 */
	private KeyUtil subKeyObj = new KeyUtil().init(); 
	/**
	 * subkeys
	 */
	public List<String> subKeys = Collections.synchronizedList(new ArrayList<>());

	/**
	 * 分组加密(128位一组)
	 * @param text 明文
	 */
	@Override
	public 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(baseEncrypt(substr));
		}
		return new BASE64Encoder().encode(sb.toString().toLowerCase().trim().getBytes());
	}
	
	/**
	 * 加密(每个密文都是128位)
	 * @param text 需要加密的文本
	 * @return
	 */
	private String baseEncrypt(String text) {
		//获取11组密钥
		if (subKeys == null || subKeys.isEmpty()) {
			subKeys = subKeyObj.generateKeys();
		}
		if (subKeys.size() != 11) {
			throw new IllegalArgumentException("密钥长度有误");
		}
		//转成16进制
		return EncodeUtil.binaryToHexStr(
				encloops(text, subKeys)
		).trim();
	}

	/**
	 * 10轮循环加密
	 * @param step1Result 初始置换后的结果
	 * @param subKeys 16组子密钥
	 * @return 循环加密结果
	 */
	private String encloops(String text, List<String> subKeys) {
		//转二进制
		String binTempStr = EncodeUtil.strtoBinary(text, AESConstant.CHAR_LEN);
		//1.初始化密钥加法层
//		System.out.println("binTempStr0 = " + binTempStr);
		binTempStr = xor(binTempStr, subKeys.get(0));
		//第一轮至第十轮
		for(int level = 1; level <= 10; level++) {
//			System.out.println("binTempStr1 = " + binTempStr);
			//2.字节代换层
			String[] temp = replace(binTempStr, AESConstant.SBOX);
			//3.扩撒层
			//3.1 行位移
			temp = shiftRow(temp);
			//3.2列混淆
			if (level < 10) {
				binTempStr = mixColumn(temp);
//				System.out.println("binTempStr3 = " + binTempStr);
			} else {
				binTempStr = "";
				for(String str:temp) {
					binTempStr += str;
				}
			}
//			System.out.println("binTempStr4 = " + binTempStr);
			//4.密钥加法层
			binTempStr = xor(binTempStr, subKeys.get(level));
//			System.out.println("binTempStr5 = " + binTempStr);
		}
//		System.out.println("binTempStr6 = " + binTempStr);
		return binTempStr;
	}
	
	/**
	 * 列混淆 【重点】
	 * @param _8bitArr 8位字符串数组 (2进制)
	 * @return 返回2进制字符串,方便后续的密钥加法
	 */
	private String mixColumn(String[] _8bitArr) {
		StringBuilder sb = new StringBuilder();
		for(int i = 0; i < _8bitArr.length; i+=4) {
			/**
			 * 注意:在列混淆中,每一位代表着x的指数,如(2)16=>(10)2 => x, (3)16=>(11)2 => x+1、 (25)16 => (0010 0101)2【采用十六进制】
			 * 使用矩阵乘积后的结果作为每一字节位上的结果,矩阵相乘用到了异或和或运算,异或模拟GF(2^8)域相乘,或模拟不同位相加
			 * 
			 * 如
			 * (01)16*(25)16=>		 x^5 	   + x^2 	 + 1
			 * (01)16*(25)16=> 		 x^5 	   + x^2	 + 1
			 * (02)16*(25)16=> x^6 		 + x^3 		 + x
			 * (03)16*(25)16=> x^6 + x^5 + x^3 + x^2 + x + 1
			 * +____________________________________________
			 * 				   		 x^5 	   + x^2 	 + 1
			 * 
			 * 注意如果度》8 要进行模约简
			 * 模约简方式,与不可约多项式m(x)=x8+x4+x3+x+1(十六进制表示为'11B')进行相加运算(异或)
			 */
			for (int j = 0; j < AESConstant.MIX_COLUMN_BOX.length; j++) {
				int res = 0;
				int rowNb = j;
				for (int m = 0; m < AESConstant.MIX_COLUMN_BOX[0].length; m++) {
					res ^= Integer.parseInt(
						EncodeUtil.binaryToDec(
								multiply(_8bitArr[i+j], AESConstant.MIX_COLUMN_BOX[rowNb][m])
						)	
					);
				}
				//超过了8位就和不可约多项式进行异或
				if (res >= 0x100) {
					res ^= 0x11B;  //11B => 不可约多项式m(x)=x8+x4+x3+x+1
				}
				//转二进制
				String finalRes = EncodeUtil.toBinary(res + "",EncodeRadix.DEC);
				//扩充
				while(finalRes.length() < 8) {
					finalRes = "0" + finalRes;
				}
				sb.append(finalRes);
			}
		}
		return sb.toString();
	}

	/**
	 * 二进制相乘
	 * @param source - 要处理的数(2进制)
	 * @param columnBox - 列混淆box(16进制)
	 * @return 相乘后结果(2进制)
	 */
	private String multiply(String source, String columnBox) {
		//将乘数十六进制转为二进制
		String temp = EncodeUtil.toBinary(columnBox, EncodeRadix.HEX);
		int result = 0;
		for (int i = 0; i < temp.length(); i++) {
			//如果开头位为0就跳过
			if (temp.charAt(i) == '0') {
				continue;
			}
			//否则就进行计算
			//转10进制
			int numb = Integer.parseInt(
				EncodeUtil.binaryToDec(source)
			);
			/**
			 * 算法解释:
			 * 伽罗瓦域乘法默认为模数运算
			 * (110)2 * (11)2 => (x^2 + x) * (x + 1) => x^3 + x^2 + x^2  + x => x^3 + x => (1010)2 
			 * 可以理解为先将(110)2左移一位后与(110)进行异或运算 得到 => (1010)2
			 */
			result ^=(numb << (temp.length()-1-i));
			/**
			 * 注意如果度》8 要进行模约简
			 * 模约简方式,与不可约多项式m(x)=x8+x4+x3+x+1(十六进制表示为'11B')进行相加运算(异或)
			 */
			if (result >= 0x100) {
				result ^= 0x11B;  //11B => 不可约多项式m(x)=x8+x4+x3+x+1
			}
		}
		return EncodeUtil.toBinary(result+"", EncodeRadix.DEC);
	}

	/**
	 * 行位移 
	 * @param _8bitArr 8位字符串数组【16进制】
	 * @return 按行输出(2进制)
	 */
	private String[] shiftRow(String[] _8bitArr) {
		String[] res = new String[_8bitArr.length];
		for(int i = 0; i < _8bitArr.length / 4;i++) {
			for (int j = 0; j < 4; j++) {
				int index = i*4 + j%4;
				//经过行位移后, 原来B0B1B2B3 -> B0B5B10B15, B4B5B6B7 -> B4B9B14B3 。。。 于是找到了这个规律
				res[index] = EncodeUtil.toBinary(
						_8bitArr[(4*i + 5*j)% _8bitArr.length], EncodeRadix.HEX
				); 
				//扩充
				while(res[index].length() < 8) {
					res[index]= "0" + res[index];
				}
			}
		}
		return res;
	}

	/**
	 * 字节代换层
	 * @param binStr 二进制流
	 * @param _128bitsStr 128位字符串
	 * @return 16进制数据
	 */
	private String[] replace(String _128bitsStr, String[][] sbox) {
		String[] result = new String[16];
		//分组计算
		for (int i = 0; i <result.length; i++) {
			String rowNb = EncodeUtil.binaryToDec(_128bitsStr.substring(i * 8, i * 8 + 4));
			String colNb = EncodeUtil.binaryToDec(_128bitsStr.substring(i * 8 + 4, i * 8 + 8));
			result[i] = sbox[Integer.parseInt(rowNb)][Integer.parseInt(colNb)];
		}
		return result;
	}

	/**
	 * 异或运算
	 * @param text1 text1 
	 * @param text2 text2
	 * @return
	 */
	private String xor(String text1, String text2) {
		if (text1 == null || text2 == null || text1.length() != text2.length()) {
			throw new IllegalArgumentException("异或运算失败");
		}
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < text1.length();i++) {
			char ch1 = text1.charAt(i);
			char ch2 = text2.charAt(i);
			sb.append((ch1) ^ (ch2));
		}
		return sb.toString().trim();
	}


	/**
	 * 分组解密
	 * @param encrytedText 密文
	 */
	@Override
	public 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;
	}
	
	/**
	 * 解密
	 * @param encHexStr 加密16进制文本
	 * @return
	 */
	private String baseDecrypt(String encHexStr) {
		//1. 获取密钥
		if (subKeys == null || subKeys.isEmpty()) {
			throw new IllegalArgumentException("密钥获取失败");
		}
		if (subKeys.size() != 11) {
			throw new IllegalArgumentException("密钥长度有误");
		}
		//2. 讲16进制转二进制并迭代解密输出结果
		return EncodeUtil.binaryToStr(
				decLoop(encHexStr,subKeys),
				AESConstant.CHAR_LEN
		);
	}
	
	/**
	 * 迭代递归解密
	 * @param encHexStr 加密16进制文本
	 * @param subKeys 子密钥
	 * @return 解密结果
	 */
	private String decLoop(String encHexStr, List<String> subKeys) {
		//1.16进制转二进制
		String binTempStr = EncodeUtil.toBinary(
				encHexStr, EncodeRadix.HEX);
		//2.逆向迭代解密
		for (int level=10;level > 0;level--) {
			//密钥加法层
//			System.out.println("reverseReplace binTempStr1 = " + binTempStr);
			binTempStr = xor(binTempStr, subKeys.get(level)); //没问题
//			System.out.println("reverseReplace binTempStr2 = " + binTempStr);
			String[] temp = new String[AESConstant.ARR_LEN];
			//转成字符串数组
			int len = 8; //字节数组
			for (int i = 0; i < AESConstant.ARR_LEN; i++) {
				temp[i] = binTempStr.substring(i* len, (i+1)*len);
			}
			//逆向列混淆层
			if (level < 10) {
				binTempStr = reverseMixColumn(temp);
//				System.out.println("reverseReplace binTempStr3 = " + binTempStr);
				for (int i = 0; i < AESConstant.ARR_LEN; i++) {
					temp[i] = binTempStr.substring(i* len, (i+1)*len);
				}
			}
			//逆向行位移层
			binTempStr = reverseShiftRow(temp);
//			System.out.println("reverseReplace binTempStr4 = " + binTempStr);
			//逆向字节代换
			binTempStr = reverseReplace(binTempStr, AESConstant.REVERSE_SBOX);
//			System.out.println("reverseReplace binTempStr5 = " + binTempStr);
		}
		//密钥加法层
//		System.out.println("reverseReplace binTempStr6 = " + binTempStr);
		binTempStr = xor(binTempStr, subKeys.get(0));
//		System.out.println("reverseReplace binTempStr7 = " + binTempStr);
		return binTempStr;
	}
	
	/**
	 * 逆向字节代换层
	 * @param binStr 二进制流
	 * @param _128bitsStr 128位字符串
	 * @return 2进制进制数据
	 */
	private String reverseReplace(String _128bitsStr, String[][] sbox) {
		StringBuilder result = new StringBuilder();
		//分组计算
		for (int i = 0; i < AESConstant.ARR_LEN; i++) {
			String rowNb = EncodeUtil.binaryToDec(_128bitsStr.substring(i * 8, i * 8 + 4));
			String colNb = EncodeUtil.binaryToDec(_128bitsStr.substring(i * 8 + 4, i * 8 + 8));
			result.append(EncodeUtil.toBinary(sbox[Integer.parseInt(rowNb)][Integer.parseInt(colNb)], EncodeRadix.HEX));
		}
		return result.toString();
	}
	
	/**
	 * 逆向行位移 
	 * @param _8bitArr 字符串数组(16位)【2进制】
	 * @return 按行输出(2进制)
	 */
	private String reverseShiftRow(String[] _8bitArr) {
		StringBuilder res = new StringBuilder();
		for(int i = 0; i < _8bitArr.length / 4;i++) {
			for (int j = 0; j < 4; j++) {
				//经过逆向行位移后, 原来B0B1B2B3 -> B0B13B10B7, B4B5B6B7 -> B4B1B14B11 。。。 于是找到了这个规律
				String temp =_8bitArr[(4*i + 13*j)% _8bitArr.length];
				//扩充
				while(temp.length() < 8) {
					temp = "0" + temp;
				}
				res.append(temp);
			}
		}
		return res.toString();
	}
	
	/**
	 * 列混淆 【重点】
	 * @param _8bitArr 8位字符串数组 (2进制)
	 * @return 返回2进制字符串,方便后续的密钥加法
	 */
	private String reverseMixColumn(String[] _8bitArr) {
		StringBuilder sb = new StringBuilder();
		for(int i = 0; i < _8bitArr.length; i+=4) {
			/**
			 * 注意:在列混淆中,每一位代表着x的指数,如(2)16=>(10)2 => x, (3)16=>(11)2 => x+1、 (25)16 => (0010 0101)2【采用十六进制】
			 * 使用矩阵乘积后的结果作为每一字节位上的结果,矩阵相乘用到了异或和或运算,异或模拟GF(2^8)域相乘,或模拟不同位相加
			 * 
			 * 如
			 * (01)16*(25)16=>		 x^5 	   + x^2 	 + 1
			 * (01)16*(25)16=> 		 x^5 	   + x^2	 + 1
			 * (02)16*(25)16=> x^6 		 + x^3 		 + x
			 * (03)16*(25)16=> x^6 + x^5 + x^3 + x^2 + x + 1
			 * +____________________________________________
			 * 				   		 x^5 	   + x^2 	 + 1
			 * 
			 * 注意如果度》8 要进行模约简
			 * 模约简方式,与不可约多项式m(x)=x8+x4+x3+x+1(十六进制表示为'11B')进行相加运算(异或)
			 */
			for (int j = 0; j < AESConstant.REVERSE_MIX_COLUMN_BOX.length; j++) {
				int res = 0;
				int rowNb = j;
				int initLen = _8bitArr[i+j].length();
				for (int m = 0; m < AESConstant.REVERSE_MIX_COLUMN_BOX[0].length; m++) {
					res ^= Integer.parseInt(
						EncodeUtil.binaryToDec(
								multiply(_8bitArr[i+j], AESConstant.REVERSE_MIX_COLUMN_BOX[rowNb][m])
						)	
					);
				}
				//超过了8位就和不可约多项式进行异或
				if (res >= 0x100) {
					res ^= 0x11B;  //11B => 不可约多项式m(x)=x8+x4+x3+x+1
				}
				//转二进制
				String finalRes = EncodeUtil.toBinary(res + "",EncodeRadix.DEC);
				//扩充
				while(finalRes.length() < initLen) {
					finalRes = "0" + finalRes;
				}
				sb.append(finalRes);
			}
		}
		return sb.toString();
	}

	public static void main(String[] args) {
		AesUtil util = new AesUtil();
		String encrytedStr = util.encrypt("{\"code\":200,\"message\":\"成功!\",\"data\":{\"id\":\"2103813902831\",\"name\":\"章鱼哥是我哦\"}}");
		System.out.println("encrytedStr = " + encrytedStr);
		System.out.println("result= " + util.decrypt(encrytedStr));
	}
}

—————————————创作不易,多多支持一下作者,感谢————————

你可能感兴趣的:(密码学,算法,web安全)