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

news2024/11/25 4:23:16

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

什么是分组密码?

分组密码就是按照固定长度的字符对明文加密/密文解密的一种加解密算法。常见的有DES、AES等。具体如下图所示
在这里插入图片描述

什么是IV(偏移量)?

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

什么是分组加密模式?

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

ECB(电子密码本模式)
在这里插入图片描述
过程/原理:
这种分组加密模式就属于最简单的不经过处理的分组加密模式,直接对明文或密文按组进行加密或解密得到结果。

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

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

CBC(密文分组链接模式)
在这里插入图片描述
过程/原理:
加密:
第一轮:生成初始化向量iv,与第一组明文分组异或运算后共同加密。
后续:拿上一轮CBC运算结果与当前分组明文异或运算后加密。
解密:
第一轮:生成初始化向量iv,先对第一组密文分组解密后再与初始化向量进行异或运算。
后续:先对当前密文分组解密后再与上一轮密文分组进行异或运算
加解密原理:
A异或B异或B = A
例如第一轮加密后的第一组密文 e1 = Ek(iv xor 第一组明文)
则第一轮解密后的第一组明文 d1 = (Ek’(第一组密文) xor iv) = (iv xor 第一组明文) xor iv = 第一组明文

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

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

CFB(密文反馈模式)
在这里插入图片描述
过程/原理:
加密:
第一轮:生成初始化向量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));
	}

最后是运行截图
在这里插入图片描述
————————————————PKCS5Padding后续再讲————————————————————

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

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

相关文章

学生公寓一进四出智能电表的功能介绍

学生公寓一进四出智能电表石家庄光大远通电气有限公司模块时间控制功能&#xff1a;可设定每个宿舍自动断电和供电的时间&#xff1b;也可以设定某时间段内为小功率输出,设定时间后自动恢复正常供电。权限管理&#xff1a;管理者可对操作人员设定不同操作权限&#xff1b; 软件…

毅哥铡特:修改后的Bellmanford最短路径路由动画演示

修改背景&#xff1a;毅哥铡特自带的《routing_bellmanford.cpp》&#xff0c;按路由跳数进行更新路由表&#xff0c;但是&#xff0c;卫星互联网的卫星路由器节点&#xff0c;可能需要考虑传播传输时延&#xff0c;对应的&#xff0c;可能需要按照两个网络节点的距离来更新路由…

OpenEuler 上安装redis服务

访问redis的下载地址 Index of /releases/地址&#xff1a;Index of /releases/ 选择对应的版本。我选择5.0的版本。 下载对应的版本redis wget https://download.redis.io/releases/redis-5.0.8.tar.gz 解压 redis tar -zxvf redis-5.0.9.tar.gz 进入redis目录 cd redis-5…

使用 VScode 开发 ROS 的Python程序(简例)

一、任务介绍 本篇作为ROS学习的第二篇&#xff0c;是关于如何在Ubuntu18.04中使用VSCode编写一个Python程序&#xff0c;输出“Hello&#xff01;”的内容介绍。 首先我们来了解下ROS的文件系统&#xff0c;ROS文件系统级指的是在硬盘上ROS源代码的组织形式&#xff0c;其结构…

不用技术代码,如何制作成绩查询系统?

为了解决学校无力承担传统学生考试成绩查询平台的高昂费用&#xff0c;老师们可以考虑使用易查分这样的工具来免费制作一个学生考试成绩查询平台。易查分是一种简单易用的在线成绩查询系统&#xff0c;可以帮助老师们快速创建一个个性化的学生考试成绩查询平台。 使用易查分制作…

水库大坝安全监测系统实施方案

一、方案概述 水库大坝作为特殊的建筑&#xff0c;其安全性质与房屋等建筑物完全不同&#xff0c;并且建造在地质构造复杂、岩土特性不均匀的地基上&#xff0c;目前对于大坝监测多采用人工巡查的方法&#xff0c;存在一定的系统误差&#xff0c;其工作性态和安全状况随时都在变…

【PCIE体系结构十六】PCIE电源管理之ASPM

&#x1f449;个人主页&#xff1a;highman110 &#x1f449;作者简介&#xff1a;一名硬件工程师&#xff0c;持续学习&#xff0c;不断记录&#xff0c;保持思考&#xff0c;输出干货内容 参考书籍&#xff1a;《PCI.EXPRESS系统体系结构标准教材 Mindshare》 PCIe总线…

若依部署前后端

打包项目 前端打包 npm run build:prod将代码上传到指定目录 配置nginx转发 server{listen 8090;server_name localhost;location / {root /home/cc_library/dist;index index.html index.htm;# 配置 history模式&#xff0c;刷新页面会404&#xff0c;&#xff0c;因为服…

语音同声翻译软件助你跨越语言障碍

嘿&#xff0c;你在日常工作中是否曾经参加过跨国会议&#xff0c;是否也曾由于语言不通而感到尴尬&#xff1f;别担心&#xff0c;因为现在有了会议实时翻译软件&#xff0c;这些问题都将成为过去式&#xff01;那么你知道会议实时翻译的软件有哪些吗&#xff1f;接下来就让我…

JavaScript基础 第五天

1.什么是对象以及对象的基本使用 2.对象的操作 --增删改查 3.对象的方法 4.数学内置对象 5.简单数据类型和引用数据类型 一.什么是对象以及对象的基本使用 ① 对象是什么 可以理解为一种无序的数据集合&#xff0c;数组是有序的数据集合对象通常用来描述某个事物&#x…

springboot+mybatis+mybatis-plus对crud项目进行改进

springbootmybatis实现简单的增、删、查、改https://blog.csdn.net/heyl163_/article/details/132197201上一篇文章&#xff0c;已经详细地介绍了怎么通过springboot项目整合mybatis实现简单的数据库表的增删改查功能&#xff0c;是最简单的springboot项目的结构。所以有很多问…

FinClip | 7月做出了一些微不足道的贡献

FinClip 的使命是使您&#xff08;业务专家和开发人员&#xff09;能够通过小程序解决关键业务流程挑战&#xff0c;并完成数字化转型的相关操作。不妨让我们看看在本月的产品与市场发布亮点&#xff0c;看看是否有助于您实现目标。 产品方面的相关动向&#x1f447;&#x1f…

Python中的排序

一、列表排序 举例sort和sorted对列表排序&#xff0c;说明两者的区别。 import relist1 [0, -1, 3, -10, 5, 9] list1.sort(reverseFalse) print(list1.sort在list1基础上修改&#xff0c;无返回值, list1) list2 [0, -1, 3, -10, 5, 9] res sorted(list2, reverseFalse)…

团队管理之PDP大法

PDP 是什么&#xff0c;为什么有些人会谈PDP色变呢&#xff1f;人常常会对自己不了解的东西感到恐惧 一、什么是PDP 团队管理中的PDP可能指"Personal Development Plan"&#xff08;个人发展计划&#xff09;&#xff0c;它是一种用于帮助团队成员提升个人能力和达成…

leetcode 面试题 02.07. 链表相交

题目&#xff1a;leetcode 面试题 02.07. 链表相交 描述&#xff1a; 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 思路&…

ARP请求拦截及响应

一、前言 本文主要是介绍如何对arp请求包进行拦截&#xff0c;并代替系统进行响应。对arp请求进行拦截&#xff0c;需要在驱动中进行&#xff0c;具体代码如下文所示。&#xff08;本文仅供参考&#xff09; 二、环境 OS Ubuntu 20.04.6 LTSLinux ubuntu 5.15.0-71-generic三…

Java培训班出来能找到工作吗?有没有想详细了解的呢

参加Java培训班可以提升你的编程技能和就业竞争力&#xff0c;但能否找到工作还取决于多个因素&#xff0c;如个人能力、市场需求、就业竞争等。参加Java培训班可以帮助你获得系统的Java编程知识和实践经验&#xff0c;了解行业最佳实践和流行的技术框架。这有助于你在面试时展…

SpringBoot案例-部门管理-删除

目录 查看页面原型&#xff0c;明确需求 页面原型 需求 阅读接口文档 思路分析 功能接口开发 控制层&#xff08;Controllre类&#xff09; 业务层&#xff08;Service类&#xff09; 持久层&#xff08;Mapper类&#xff09; 接口测试 前后端联调 查看页面原型&a…

Linux进程管理命令

一、进程 程序由一条条指令构成&#xff0c;在运行一个程序的时候就是把这些指令从第一条执行到最后一条&#xff0c;而进程是一个正在运行的程序。 比如说&#xff0c;一个main.c文件是不可以直接运行的&#xff0c;对main.c进行编译链接之后生成一个main.exe&#xff08;在W…

QT学习笔记-QT安装oracle oci驱动

QT学习笔记-QT安装oracle oci驱动 0、背景1、环境以及条件说明2、编译驱动2.1 下载oracle instant client2.2 编译qt oci驱动2.2.1 修改oci.pro2.2.2 MinGW64构建套件编译2.2.3 MSVC2019_64构建套件编译 3、访问数据库运行成功 0、背景 在使用QT开发应用的过程中&#xff0c;往…