程序猿成长之路之密码学篇-分组密码加密模式及IV(偏移量)的详解
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 偏移量*/@Overridepublic 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 偏移量*/@Overridepublic 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后续再讲————————————————————
相关文章:
程序猿成长之路之密码学篇-分组密码加密模式及IV(偏移量)的详解
Cipher.getInstance("AES/ECB/PKCS5Padding"); Cipher cipher Cipher.getInstance("AES/CBC/PKCS5Padding"); 在进行加解密编程的时候应该有很多小伙伴接触过以上的语句,但是大伙儿在编码过程中是否了解过ECB/CBC的含义、区别以及PKCS5Padding…...
Windows下批处理删除文件
最近我使用Maven的时候会出现下载jar包不成功的现象,然后需要把它删除然后重新下载,但是有时候文件过多,一个个删除太花费时间,所以用bat的批处理会很舒服。 bat的语法我之前没遇到过,然后我是边学习边试验࿰…...
html中文件上传储存到本地路径
第一步:写html文件 <form action"/uplode" method"post" enctype"multipart/form-data">姓名:<input type"text" name"username"><br>年龄:<input type"text" name"age"><…...
第九章 SpringBoot 自动配置原理 入门
1. 引导加载自动配置类 SpringBootApplication -- SpringBootConfiguration -- EnableAutoConfiguration -- ComponentScan //SpringBootApplicationSpringBootConfiguration EnableAutoConfiguration ComponentScan(excludeFilters { Filter(type FilterType.CUSTOM, cl…...
String str=new String(“tango“) 创建了几个对象?
面试回答 创建的对象数 应该是1个或者2个。 首先要清楚什么是对象? Java 是一种面向对象的语言,而 Java 对象在 JVM 中的存储也是有一定的结构的,在 HotSpot 虚拟机中,存储的形式就是 oop-klass model,即 Java 对象模型…...
引入三阶失真的非线性放大器的模拟输出及使用中值滤波器去除峰值研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
【观察者设计模式详解】C/Java/JS/Go/Python/TS不同语言实现
简介 观察者模式(Observer Pattern)是一种行为型模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 观察者模式使用三个类Subject、Observer和Client。Subject…...
精细解析中文公司名称:智能分词工具助力地名、品牌名、行业词和后缀提取
精细解析中文公司名称:智能分词工具助力地名、品牌名、行业词和后缀提取 中文公司名称分词工具,支持公司名称中的地名,品牌名(主词),行业词,公司名后缀提取。 对公司名文本解析,识…...
网络编程(JavaEE初阶系列10)
目录 前言: 1.网络编程的基础 1.1为什么需要网络编程 1.2什么是网络编程 1.3网络编程中的基本概念 1.3.1发送端和接收端 1.3.2请求和响应 1.3.3客户端和服务端 2.Socket套接字 2.1概念 2.2分类 3.UDP数据报套接字编程 3.1DataGramSocket API 3.2Datagr…...
Git常用的指令
Git常用的指令 OMMP提交代码的流程 0、配置: git config --list 查看当前配置 git congig --global user.name user 这个会显示你的提交到git的名字 格式:git config [–local|–global|–system] –unset section.key 格式:git config [–l…...
LoadRunner(2)
一、Controller 1.1场景设计 1.通过VUG打开 施压机器:发起请求的角色(用户本地电脑) 被压机器:处理请求的角色(服务器) 2.直接双击Controller 场景设计:需要关注三个部分 第一部分: 第二部分: 2.1运行场景…...
CTF之逆向之阿里巴巴
题目地址:http://www.shiyanbar.com/ctf/13 题目预览: 解题过程: 1、下载附件发现是exe文件 2、使用PEid和Detect It Easy查壳 和 开发语言,发现没有加壳,都是用C#开发的 3、C#和Java Python属于解释型语言ÿ…...
Labview控制APx(Audio Precision)进行测试测量(五)
驱动程序 VIs如何处理配置设置中的单元 APx500 应用程序具有复杂的控件,具有以下功能: 数值和单位组合在一个控制中(例如,1.000 Vrms ) •值转换为 SI 格式(例如,1.000 mVrms 或 1.000 μVrms) •单位之间的转换发生在控制(例如,V…...
在单元测试中使用Jest模拟VS Code extension API
对VS Code extension进行单元测试时通常会遇到一个问题,代码中所使用的VS Code编辑器的功能都依赖于vscode库,但是我们在单元测试中并没有添加对vscode库的依赖,所以导致运行单元测试时出错。由于vscode库是作为第三方依赖被引入到我们的VS C…...
django boostrap html实现可拖拽的左右布局,鼠标拖动调整左右布局的大小或占比
一、实现的效果 最近需要在Django项目中,实现一个左右布局的html页面,页面框架使用的是boostrap。但这个布局不是简单的左右分栏布局,而是需要实现可以通过鼠标拖拽的方式动态调整左右两侧布局的大小和占比。效果大致如下: 一开始,页面分为左右两块布局: 鼠标放到中间的…...
谈谈闭包和闭包使用场景
一、什么是闭包 概念:闭包还是作用域的一种特殊应用 二、触发闭包的情况 1.函数当做返回值被返回 2.函数当做参数被传递 3.自执行匿名函数 //情况1:函数当做返回值被返回 function fn(){const a 1;return function(){console.log(a) //1}; } const a …...
MATLAB算法实战应用案例精讲-【图像处理】边界框锚框
目录 目标检测 应用场景 目标检测发展历程 常用数据集 边界框(bounding box)...
04什么场景要用到微服务
一句话导读 根据微服务的特点,可以总结为在构建复杂的、大型的、分布式的、高可用、高并发、高性能的应用时可以使用微服务架构。 目录 一句话导读 一、微服务适用场景 1.业务复杂,模块多且相对独立 2.团队多,管理隔离 3.应用规模大&#…...
.NET SqlSuger 简单介绍,超快开发数据库
文章目录 前言SqlSugar使用我的环境Nuget 安装新建连接串DB First 和 Code First使用增删改查 总结 前言 我之前介绍过EFCore 怎么使用Nuget快速创建数据库,我之后发现SqlSugar更快。这里简单再说一下SqlSugar如何使用 .NET Core 数据库DB First自动生成࿰…...
SpringBoot复习:(28)【前后端不分离】自定义View
一、自定义View package cn.edu.tju.view;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Comp…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
