当前位置: 首页 > news >正文

程序猿成长之路之密码学篇-分组密码加密模式及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"); 在进行加解密编程的时候应该有很多小伙伴接触过以上的语句&#xff0c;但是大伙儿在编码过程中是否了解过ECB/CBC的含义、区别以及PKCS5Padding…...

Windows下批处理删除文件

最近我使用Maven的时候会出现下载jar包不成功的现象&#xff0c;然后需要把它删除然后重新下载&#xff0c;但是有时候文件过多&#xff0c;一个个删除太花费时间&#xff0c;所以用bat的批处理会很舒服。 bat的语法我之前没遇到过&#xff0c;然后我是边学习边试验&#xff0…...

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个。 首先要清楚什么是对象&#xff1f; Java 是一种面向对象的语言&#xff0c;而 Java 对象在 JVM 中的存储也是有一定的结构的&#xff0c;在 HotSpot 虚拟机中&#xff0c;存储的形式就是 oop-klass model&#xff0c;即 Java 对象模型…...

引入三阶失真的非线性放大器的模拟输出及使用中值滤波器去除峰值研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

【观察者设计模式详解】C/Java/JS/Go/Python/TS不同语言实现

简介 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型模式。它定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。 观察者模式使用三个类Subject、Observer和Client。Subject…...

精细解析中文公司名称:智能分词工具助力地名、品牌名、行业词和后缀提取

精细解析中文公司名称&#xff1a;智能分词工具助力地名、品牌名、行业词和后缀提取 中文公司名称分词工具&#xff0c;支持公司名称中的地名&#xff0c;品牌名&#xff08;主词&#xff09;&#xff0c;行业词&#xff0c;公司名后缀提取。 对公司名文本解析&#xff0c;识…...

网络编程(JavaEE初阶系列10)

目录 前言&#xff1a; 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、配置&#xff1a; git config --list 查看当前配置 git congig --global user.name user 这个会显示你的提交到git的名字 格式&#xff1a;git config [–local|–global|–system] –unset section.key 格式&#xff1a;git config [–l…...

LoadRunner(2)

一、Controller 1.1场景设计 1.通过VUG打开 施压机器&#xff1a;发起请求的角色(用户本地电脑) 被压机器&#xff1a;处理请求的角色(服务器) 2.直接双击Controller 场景设计&#xff1a;需要关注三个部分 第一部分&#xff1a; 第二部分&#xff1a; 2.1运行场景…...

CTF之逆向之阿里巴巴

题目地址&#xff1a;http://www.shiyanbar.com/ctf/13 题目预览&#xff1a; 解题过程&#xff1a; 1、下载附件发现是exe文件 2、使用PEid和Detect It Easy查壳 和 开发语言&#xff0c;发现没有加壳&#xff0c;都是用C#开发的 3、C#和Java Python属于解释型语言&#xff…...

Labview控制APx(Audio Precision)进行测试测量(五)

驱动程序 VIs如何处理配置设置中的单元 APx500 应用程序具有复杂的控件&#xff0c;具有以下功能: 数值和单位组合在一个控制中(例如&#xff0c;1.000 Vrms ) •值转换为 SI 格式(例如&#xff0c;1.000 mVrms 或 1.000 μVrms) •单位之间的转换发生在控制(例如&#xff0c;V…...

在单元测试中使用Jest模拟VS Code extension API

对VS Code extension进行单元测试时通常会遇到一个问题&#xff0c;代码中所使用的VS Code编辑器的功能都依赖于vscode库&#xff0c;但是我们在单元测试中并没有添加对vscode库的依赖&#xff0c;所以导致运行单元测试时出错。由于vscode库是作为第三方依赖被引入到我们的VS C…...

django boostrap html实现可拖拽的左右布局,鼠标拖动调整左右布局的大小或占比

一、实现的效果 最近需要在Django项目中,实现一个左右布局的html页面,页面框架使用的是boostrap。但这个布局不是简单的左右分栏布局,而是需要实现可以通过鼠标拖拽的方式动态调整左右两侧布局的大小和占比。效果大致如下: 一开始,页面分为左右两块布局: 鼠标放到中间的…...

谈谈闭包和闭包使用场景

一、什么是闭包 概念&#xff1a;闭包还是作用域的一种特殊应用 二、触发闭包的情况 1.函数当做返回值被返回 2.函数当做参数被传递 3.自执行匿名函数 //情况1&#xff1a;函数当做返回值被返回 function fn(){const a 1;return function(){console.log(a) //1}; } const a …...

MATLAB算法实战应用案例精讲-【图像处理】边界框锚框

目录 目标检测 应用场景 目标检测发展历程 常用数据集 边界框(bounding box)...

04什么场景要用到微服务

一句话导读 根据微服务的特点&#xff0c;可以总结为在构建复杂的、大型的、分布式的、高可用、高并发、高性能的应用时可以使用微服务架构。 目录 一句话导读 一、微服务适用场景 1.业务复杂&#xff0c;模块多且相对独立 2.团队多&#xff0c;管理隔离 3.应用规模大&#…...

.NET SqlSuger 简单介绍,超快开发数据库

文章目录 前言SqlSugar使用我的环境Nuget 安装新建连接串DB First 和 Code First使用增删改查 总结 前言 我之前介绍过EFCore 怎么使用Nuget快速创建数据库&#xff0c;我之后发现SqlSugar更快。这里简单再说一下SqlSugar如何使用 .NET Core 数据库DB First自动生成&#xff0…...

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…...

前端工具库lodash与lodash-es区别详解

lodash 和 lodash-es 是同一工具库的两个不同版本&#xff0c;核心功能完全一致&#xff0c;主要区别在于模块化格式和优化方式&#xff0c;适合不同的开发环境。以下是详细对比&#xff1a; 1. 模块化格式 lodash 使用 CommonJS 模块格式&#xff08;require/module.exports&a…...

机器学习复习3--模型评估

误差与过拟合 我们将学习器对样本的实际预测结果与样本的真实值之间的差异称为&#xff1a;误差&#xff08;error&#xff09;。 误差定义&#xff1a; ①在训练集上的误差称为训练误差&#xff08;training error&#xff09;或经验误差&#xff08;empirical error&#x…...

大模型的LoRa通讯详解与实现教程

一、LoRa通讯技术概述 LoRa(Long Range)是一种低功耗广域网(LPWAN)通信技术,由Semtech公司开发,特别适合于物联网设备的长距离、低功耗通信需求。LoRa技术基于扩频调制技术,能够在保持低功耗的同时实现数公里甚至数十公里的通信距离。 LoRa的主要特点 长距离通信:在城…...

LSTM-XGBoost多变量时序预测(Matlab完整源码和数据)

LSTM-XGBoost多变量时序预测&#xff08;Matlab完整源码和数据&#xff09; 目录 LSTM-XGBoost多变量时序预测&#xff08;Matlab完整源码和数据&#xff09;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 普通的多变量时序已经用腻了&#xff0c;审稿人也看烦了&#…...

Modbus转ETHERNET IP网关:快速冷却系统的智能化升级密钥

现代工业自动化系统中&#xff0c;无锡耐特森Modbus转Ethernet IP网关MCN-EN3001扮演着至关重要的角色。通过这一技术&#xff0c;传统的串行通讯协议Modbus得以在更高速、更稳定的以太网环境中运行&#xff0c;为快速冷却系统等关键设施的自动化控制提供了强有力的支撑。快速冷…...

Linux 内核内存管理子系统全面解析与体系构建

一、前言: 为什么内存管理是核心知识 内存管理是 Linux 内核最核心也最复杂的子系统之一&#xff0c;其作用包括&#xff1a; 为软件提供独立的虚拟内存空间&#xff0c;实现安全隔离分配/回收物理内存资源&#xff0c;维持系统稳定支持不同类型的内存分配器&#xff0c;最优…...

nginx部署

配置阿里云yum源 安装如下编译工具 yum install -y gcc gcc-c autoconf automake make #安装使用nginx还得安装nginx所需的一些第三方系统库的支持&#xff0c;比如nginx的静态资源压缩功能所需的gzip lib库&#xff0c;nginx需要支持URL重写&#xff0c;所需的pcre库&…...

用电脑通过网口控制keysight示波器

KEYSIGHT示波器HD304MSO性能 亮点: 体验 200 MHz 至 1 GHz 的带宽和 4 个模拟通道。与 12 位 ADC 相比,使用 14 位模数转换器 (ADC) 将垂直分辨率提高四倍。使用 10.1 英寸电容式触摸屏轻松查看和分析您的信号。捕获 50 μVRMS 本底噪声的较小信号。使用独有区域触摸在几秒…...

八股---7.JVM

1. JVM组成 1.1 JVM由哪些部分组成?运行流程? 难易程度:☆☆☆ 出现频率:☆☆☆☆ Java Virtual Machine:Java 虚拟机,Java程序的运行环境(java二进制字节码的运行环境)好处:一次编写,到处运行;自动内存管理,垃圾回收机制程序运行之前,需要先通过编译器将…...

Selenium自动化操作

1、跳转页面播放视频 我们以b站为例&#xff0c;测试是否可以点击模块进行播放视频 //点击页面看是否视频正常播放public void turn() throws InterruptedException {driver.findElement(By.cssSelector("#i_cecream > div.bili-feed4 > main > div.feed2 >…...