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

news2024/11/28 6:52:11

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

AES加密详解

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

AES解密

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

整体流程

假设大家都了解了AES的加密过程,那么我们知道加密共11轮,而解密亦是如此。加密第一轮就只进行一次轮密钥加,而最后一轮加密则是无需列混淆的参与。解密的过程则恰好相反,解密它先是进行最后一轮加密过程逆置,之后往前递推,直至第一轮加密逆置,如下图所示。
在这里插入图片描述
在了解完整体流程后下面就来利用“分而治之”思想,将难点进行拆分讲解。

轮密钥加

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

逆向行移位

AES加密算法中行移位
第一行不动,第二行循环左移一位,第三行循环左移两位,第四行循环左移三位。
在这里插入图片描述
也就是说原来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 为下标】【结合图片更便于理解】

上面是加密中行移位的算法,理解了这一点,解密的逆向行移位就变得简单了。
解密无非是将左移改为右移,即 第一行不动,第二行循环右移一位,第三行循环右移两位,第四行循环右移三位。如下图所示
在这里插入图片描述
变成
在这里插入图片描述
【注意,此矩阵读法为自上往下,自左往右读】。
为了方便理解和计算,我们可以把这一矩阵一维化成一维数组,即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介绍文章,运算均为模运算】计算出密文矩阵和列混淆固定矩阵的乘积然后获取结果矩阵。固定矩阵如下图所示:
在这里插入图片描述
为了方便计算,也可以将矩阵进行一维化之后进行模数运算。这里我采用了一种比较快速的模数运算方案:
/**
* 算法解释:
* 伽罗瓦域乘法默认为模数运算
* (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));
	}
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/646743.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ffmpeg编译笔记:ubuntu18.04编译ffmpeg5.1 x86与64

一、前言 本篇描述了ffmpeg5.1在ubuntu18.04上的编译经验。编译后的库支持h264&#xff0c;h265软硬解码&#xff0c;支持https&#xff0c;支持SDL。本篇同时描述openssl在ffmpeg中的编译经验&#xff0c;以及提供ffmpeg编译和openssl编译的32位和64位的配置命令。 二、相关…

这8道接口测试面试题

接口测试常见的问题了。 大家乍一看&#xff01; 接口测试面试题 这几个问题&#xff0c;能答出来几个&#xff1f;有没有8个都能够完美的答出来的&#xff1f;在留言区打出你的数字。&#xff08;0~8&#xff09; 这些问题你回答起来&#xff0c;不要吞吞吐吐只说几个关键字…

1. java.io.File 类的使用

1.1 概述 • File 类及本章下的各种流&#xff0c;都定义在 java.io 包下。 • 一个 File 对象代表硬盘或网络中可能存在的一个文件或者文件目录&#xff08;俗称文件夹&#xff09;&#xff0c; 与平台无关。&#xff08;体会万事万物皆对象&#xff09; • File 能新建、删除…

重启好多次路由器,还是上不了网怎么办?

大家好&#xff0c;我的网工朋友 遇到突发的网络断连&#xff0c;你一般会怎么做&#xff1f; 我觉得很多人都会插拔一下路由器&#xff0c;这和电脑不行了&#xff0c;马上就重启电脑一样&#xff0c;是刻在DNA里的傻瓜操作。 但是也有很多时候&#xff0c;这个傻瓜操作是解…

PrivateGPT:安全和私密的离线 GPT-4

在人工智能 (AI) 和自然语言处理 (NLP) 领域&#xff0c;隐私通常是一个基本问题&#xff0c;尤其是在处理敏感数据时。PrivateGPT 是这一领域的突破性发展&#xff0c;正面解决了这个问题。它旨在在没有互联网连接的情况下在本地运行&#xff0c;通过防止数据离开您的执行环境…

这些方法可以手写扫描识别

小伙伴们知道有一项技术是可以将我们手写的东西识别出来吗&#xff1f;这一项创新的技术就是手写识别功能&#xff0c;它能够将手写内容快速转换为数字或文本格式&#xff0c;并提高信息处理和管理的效率。而且相比传统的手工记录方式&#xff0c;手写识别功能具有较高的准确性…

腾讯测试开发 4 轮面试,接到 30k*15 的 Offer !详解面试流程和真题

在互联网做了几年之后&#xff0c;去大厂“镀镀金”是大部分人的首选。大厂不仅待遇高、福利好&#xff0c;更重要的是&#xff0c;它是对你专业能力的背书&#xff0c;大厂工作背景多少会给你的简历增加几分竞争力。 但说实话&#xff0c;想进大厂还真没那么容易。我的一个朋…

find命令

你将看到的第一个命令是find。这是个用于搜索文件的命令&#xff0c;它极其有用&#xff0c;但Linux初学者常常觉得它不易使用&#xff0c;这不仅仅是因为它有选项、测试和动作类型的参数&#xff0c;还因为其中一个参数的处理结果可能会影响到后续参数的处理。在深入研究这些选…

pandas---缺失值的处理

1. 处理缺失值 判断数据中是否包含NaN&#xff1a; pd.isnull(df)&#xff1b;pd.notnull(df) 存在缺失值nan: 删除存在缺失值的:dropna(axisrows) 不会修改原数据&#xff0c;需要接受返回值&#xff1b; 替换缺失值:fillna(value, inplaceTrue) value:替换成的值&#…

基于STM32的四旋翼无人机项目(一):基础知识篇

前言&#xff1a;本篇博客为飞控专栏的第一篇系统性概述文章&#xff0c;将对飞控系统进行详细讲解介绍。考虑到飞控项目具有一定工程复杂度&#xff0c;所以作者将整个项目进行分章节教学与讲解&#xff0c;希望可以给读者朋友带来更好地学习体验。项目将以 C-Quad 四轴无人机…

SpringBoot 中使用 JWT 案例分享详解

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

大模型LLM领域,有哪些可以作为学术研究方向?

清湛人工智能研究院 2023-05-31 09:23 发表于江苏 编者&#xff1a;本文转载了清华大学计算机系刘知远教授对大模型的一些思索&#xff0c;以飨读者。 刘知远 CCF 高级会员&#xff0c;CCCF 前编委。清华大学计算机系副教授、博士生导师。已在ACL、IJCAI、AAAI等人工智能领域…

回归预测 | MATLAB实现基于GRU-AdaBoost门控循环单元结合AdaBoost多输入单输出回归预测

回归预测 | MATLAB实现基于GRU-AdaBoost门控循环单元结合AdaBoost多输入单输出回归预测 目录 回归预测 | MATLAB实现基于GRU-AdaBoost门控循环单元结合AdaBoost多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.MATLAB实现基于GRU-AdaBoost门…

单品GMV破千万,这些品类正在抖音热卖

优势品类及核心产品能更好触达消费者&#xff0c;以较低的成本让用户感知品牌&#xff0c;塑造品牌力。 抖音作为品牌最核心的线上渠道之一&#xff0c;该如何找到平台优势品类&#xff1f;制定品牌营销策略&#xff1f;有效提升产品销量呢&#xff1f; 近期&#xff0c;新抖上…

【CesiumJS入门】(4)加载3D Tiles并获取tileset

前言 本次&#xff0c;我们将写一个函数来加载3D Tiles数据&#xff0c; 3D Tiles数据的文档&#xff1a;CesiumGS/3d-tiles: Specification for streaming massive heterogeneous 3D geospatial datasets (github.com) 同时我们将获取加载成功后的tileset数据集&#xff08;有…

Python 中错误 ImportError: No Module Named Sklearn

在 Python 中,sklearn 被用作机器学习工具,用于在回归、集群等方面创建程序。很多时候,导入它会抛出错误—— No module named sklearn。 这意味着由于安装错误、无效的 Python 或 pip 版本或其他问题,系统无法找到它。 Python中错误ImportError: No module named sklearn…

基于Java营业厅宽带系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

Karl Guttag评Vision Pro:比Quest Pro做了更多正确选择

上周苹果正式发布Vision Pro&#xff0c;尽管要到明年才发售&#xff0c;但光学领域的专业博主Karl Guttag也发表了自己的看法。他提到&#xff1a;目前受邀体验的媒体中要不是苹果粉丝、要不就是对AR、VR了解比较少&#xff0c;没有我看到“批判性思维”或太多对技术分析的内容…

MySQL常用操作(一)

创建表 create table user(id int unsigned primary key not null auto_increment,name varchar(50) unique not null,age tinyint not null,sex enum(M, W) not null )engineINNODB default charsetutf8;# 查看创建表语句 show create table user两种插入方式比较 # 方式1 i…

加速千行百业转型,华为如何为智能世界构建感知底座

导读&#xff1a;感知正成为行业数字化的新引擎。 我们正在迈进一个万物感知的时代。 日常生活中&#xff0c;感知已经无处不在。小到智能家居的控制系统&#xff0c;大到智慧城市虚实联动的数字孪生&#xff0c;感知是一切智能化的前提&#xff0c;也是行业数字化转型的基础。…