java中RSA分段加解密及Data must not be longer than异常处理
谈到RSA非对称加密,作为开发的我们第一想到的是安全,几乎不会被破解,以及公钥加密,私钥解密这些。在Java代码中,我们常使用一些现成的工具类如hutool中提供的工具类、网上在线的或者博客上的RSAUtils工具类来实现公钥私钥的生成以及加解密方法。但探究其中的一些原理可以来帮我们更助于理解和使用。
本文主要谈论的是加解密中的对于加解密长度的限制和规定。一般情况下,我们使用Jdk(SunJCE version 1.8)自带的RSA相关类算法进行操作,有的项目可能会引入BC库,即org.bouncycastle库,可以通过maven引入相关依赖,如下:
<dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.45</version>
</dependency>
并在静态代码块设置对应的provider
static {try{Security.addProvider(new BouncyCastleProvider());}catch(Exceptione){e.printStackTrace();}
}
如果不清楚自己项目使用的是哪种库,可以输入如下代码打印查看
for (Provider provider : Security.getProviders()) {System.out.println("算法提供者名:" + provider.getName());System.out.println("算法提供者的版本:" + provider.getVersion());System.out.println("算法提供者的信息:" + provider.getInfo());
}
稍微扩展下,就此打住。本文主要谈论Sun jdk中的RSA算法,关于密钥的生成,我们一般这么操作
private static KeyPair generateKeyPair() {try {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(2048);KeyPair keyPair = keyPairGenerator.generateKeyPair();return keyPair;} catch (NoSuchAlgorithmException e) {e.printStackTrace();}return null;}
其中“RSA”为指定算法,当我们输入RSA时,会默认使用一种名为PKCS1Padding填充方式,下面会详细谈。
2048为生成密钥的模长,会生成2048位(bit)的二进制数。注意这并不是指公钥私钥的字符串长度,一般情况我们会将密钥通过如PEM、DER或Base64编码转变成人能识别的文本形式,由于编码的不同,字符串的长度也会不一样。
还需要注意的是,为什么此处填2048,在jdk1.8源码中,规定了生成长度的限制,最小为512位,最大为16384位
可以通过如下方法打印佐证,输出结果均为2048
KeyPair keyPair = generateKeyPair();
RSAPublicKey aPublic = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey aPrivate = (RSAPrivateKey) keyPair.getPrivate();
System.out.println(aPublic.getModulus().bitLength());
System.out.println(aPrivate.getModulus().bitLength());
密钥生成好后,下面就来看看加解密,我们知道一般用公钥来加密,私钥来解密,代码如下
/*** 加密* @param data* @param publicKey* @return*/
private static String encrypt(String data, PublicKey publicKey) {try {Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.ENCRYPT_MODE, publicKey);byte[] bytes = cipher.doFinal(data.getBytes());return Base64.encode(bytes);} catch (Exception e) {e.printStackTrace();}return null;
}/*** 解密* @param data* @param privateKey* @return*/
private static String decrypt(String data, PrivateKey privateKey) {try {Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.DECRYPT_MODE, privateKey);byte[] bytes = cipher.doFinal(Base64.decode(data));return new String(bytes);} catch (Exception e) {e.printStackTrace();}return null;
}
测试加解密方法,能够正常加解密
public static void main(String[] args) {// 生成密钥对KeyPair keyPair = generateKeyPair();RSAPublicKey aPublic = (RSAPublicKey) keyPair.getPublic();RSAPrivateKey aPrivate = (RSAPrivateKey) keyPair.getPrivate();// 打印公钥和私钥模长System.out.println(aPublic.getModulus().bitLength());System.out.println(aPrivate.getModulus().bitLength());// 将公钥和私钥转换为Base64编码String publicKey = Base64.encode(aPublic.getEncoded());String privateKey = Base64.encode(aPrivate.getEncoded());System.out.println("公钥:" + publicKey);System.out.println("私钥:" + privateKey);// 测试加密解密String data = "Hello, world!";String encrypt = encrypt(data, keyPair.getPublic());System.out.println("加密后:" + encrypt);String decrypt = decrypt(encrypt, keyPair.getPrivate());System.out.println("解密后:" + decrypt);
}
你可能以为结束了,但如果解析长文本时,会发现报错
// 加密解密长文本
public static void main(String[] args) {// 生成密钥对KeyPair keyPair = generateKeyPair();RSAPublicKey aPublic = (RSAPublicKey) keyPair.getPublic();RSAPrivateKey aPrivate = (RSAPrivateKey) keyPair.getPrivate();// 打印公钥和私钥模长System.out.println(aPublic.getModulus().bitLength());System.out.println(aPrivate.getModulus().bitLength());// 将公钥和私钥转换为Base64编码String publicKey = Base64.encode(aPublic.getEncoded());String privateKey = Base64.encode(aPrivate.getEncoded());System.out.println("公钥:" + publicKey);System.out.println("私钥:" + privateKey);// 加密解密长文本StringBuilder sb = new StringBuilder();for (int i = 0; i < 1000; i++) {sb.append(data);}data = sb.toString();encrypt = encrypt(data, keyPair.getPublic());System.out.println("加密后:" + encrypt);decrypt = decrypt(encrypt, keyPair.getPrivate());System.out.println("解密后:" + decrypt);}
报错如下,提示长度不能超过245个字节
javax.crypto.IllegalBlockSizeException: Data must not be longer than 245 bytesat com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:346)at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:391)at javax.crypto.Cipher.doFinal(Cipher.java:2168)at cn.hutool.crypto.KeyUtil.encrypt(KeyUtil.java:1046)at cn.hutool.crypto.KeyUtil.main(KeyUtil.java:1097)
java.lang.IllegalArgumentException: Null input bufferat javax.crypto.Cipher.doFinal(Cipher.java:2164)at cn.hutool.crypto.KeyUtil.decrypt(KeyUtil.java:1064)at cn.hutool.crypto.KeyUtil.main(KeyUtil.java:1099)
所以我们能得知,加密的明文长度最长为245,好像跟2048也没啥关系呀。深入探究得知,加密的明文长度需要满足条件:0<明文长度<密钥模长/8-11,其中“模长/8”为字节数;“-11”为填充的字节长度,而填充的字节长度正是由上文提到的填充方式决定的。当我们在加解密时,选择了RSA,在jdk库下,就会默认设置填充方式为PKCS1Padding
Cipher cipher = Cipher.getInstance("RSA");
详细说下PKCS1Padding填充方式。PKCS1Padding即PKCS#1 v1.5是RSA加密的一种常用填充模式,它通过在明文前添加特定的填充字节来确保加密数据的完整性和安全性。这种填充模式要求加密后的数据块(Encryption Block, EB)长度与RSA密钥的模长(modulus)相等。
填充结构
在PKCS#1 v1.5填充模式下,加密块(EB)的结构大致如下:
- EB = 00 || BT || PS || 00 || D
- 00:一个字节的保留位,总是0x00。
- BT:一个字节的块类型(Block Type),对于公钥加密操作,通常为0x02。
- PS:填充字符串(Padding String),由随机数(对于公钥加密)或特定值(对于私钥操作)组成,长度可变,但最小为8字节。
- 00:一个字节的分隔符,用于区分填充字符串和明文。
- D:待加密的明文数据。
长度计算
由于EB的长度必须与RSA密钥的模长相等,且需要留出空间给上述的填充部分,因此可加密的明文长度(D的长度)会受到限制。具体来说,限制因素包括:
- 保留位(00):1字节
- 块类型(BT):1字节
- 填充字符串(PS):至少8字节(PKCS#1规范建议的最小长度)
- 分隔符(00):1字节
因此,对于给定的RSA密钥模长(以字节为单位),可加密的明文最大长度大致为:模长 - 1(保留位) - 1(块类型) - 8(填充字符串最小长度) - 1(分隔符)= 模长 - 11字节。
示例
以1024位RSA密钥为例,其模长(以字节为单位)为1024/8 = 128字节。因此,在PKCS#1 v1.5填充模式下,可加密的明文最大长度约为128 - 11 = 117字节。
所以,如果当我们的明文长度超过117字节时,如果不进行分段处理,就会报错;因为加密的密文长度是模长,所以不用减。
所以应对明文长度超过模长大小时,修改加解密代码如下:
/*** 加密** @param data* @param publicKey* @return*/
private static String encrypt(String data, PublicKey publicKey) {try {Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.ENCRYPT_MODE, publicKey);// 分块加密,每次最大加密长度为密钥长度/8-11int keyLength = ((RSAPublicKey) publicKey).getModulus().bitLength();int blockSize = keyLength / 8 - 11;ByteArrayOutputStream encryptedBytesStream = new ByteArrayOutputStream();byte[] inputBytes = data.getBytes();for (int i = 0; i < inputBytes.length; i += blockSize) {int end = Math.min(inputBytes.length, i + blockSize);byte[] block = Arrays.copyOfRange(inputBytes, i, end);byte[] encryptBytes = cipher.doFinal(block);encryptedBytesStream.write(encryptBytes);}return Base64.encode(encryptedBytesStream.toByteArray());} catch (Exception e) {e.printStackTrace();}return null;
}/*** 解密** @param data* @param privateKey* @return*/
private static String decrypt(String data, PrivateKey privateKey) {try {Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.DECRYPT_MODE, privateKey);// 分块解密,每次最大解密长度为密钥长度/8int keyLength = ((RSAPrivateKey) privateKey).getModulus().bitLength();int blockSize = keyLength / 8;ByteArrayOutputStream decryptedBytesStream = new ByteArrayOutputStream();byte[] inputBytes = Base64.decode(data);for (int i = 0; i < inputBytes.length; i += blockSize) {int end = Math.min(inputBytes.length, i + blockSize);byte[] block = Arrays.copyOfRange(inputBytes, i, end);byte[] decryptBytes = cipher.doFinal(block);decryptedBytesStream.write(decryptBytes);}return new String(decryptedBytesStream.toByteArray());} catch (Exception e) {e.printStackTrace();}return null;
}
当报文长度超过最大长度时也能够成功地进行加解密。
如果您对技术有兴趣,友好交流,可以加v进技术群一起沟通,v:zzs1067632338,备注csdn即可
相关文章:

java中RSA分段加解密及Data must not be longer than异常处理
谈到RSA非对称加密,作为开发的我们第一想到的是安全,几乎不会被破解,以及公钥加密,私钥解密这些。在Java代码中,我们常使用一些现成的工具类如hutool中提供的工具类、网上在线的或者博客上的RSAUtils工具类来实现公钥私…...

MySQL数据分析进阶(十二)设计数据库——PART3
※食用指南:文章内容为‘CodeWithMosh’SQL进阶教程系列学习笔记,笔记整理比较粗糙,主要目的自存为主,记录完整的学习过程。(图片超级多,慎看!) 【中字】SQL进阶教程 | 史上最易懂S…...

Kubernetes-1.22.0 可视化部署
目录 Kubeadm方式部署3master,2work集群(Kubernetes-1.22.0)-CSDN博客 1. 官方Dashboard 2. Kuboard 部署 3. Rainbond 部署 4. Kubesphere 部署 1. 官方Dashboard kubectl apply -f https://kuboard.cn/install-script/k8s-dashboard/v2…...
在 vue3 中动态路由问题记录
第一种 如果这样子的话需要加上 /* vite-ignore / ,但是在这样用这行部署服务器上跳转会有问题 component: () > import(/ vite-ignore */ ../views/ e.component .vue) 第二种 // 解决跳转问题const modeules imporet.meta.glob(/views/**/**.vue)component: modules…...

进程编程及其函数的使用
1. 创建进程 创建进程的核心操作是使用 fork() 系统调用。 1.1 fork() 系统调用 fork() 创建一个新进程(子进程),新进程几乎是父进程的完整拷贝。fork() 返回两次: 在父进程中,返回子进程的 PID。在子进程中&#…...
为什么funnel图在邮件中不显示
在电子邮件中嵌入的Funnel图或其他图表可能不显示的原因有以下几种: 1. 邮件客户端对外部内容的限制 大多数邮件客户端为了安全,会阻止从外部服务器加载的内容,如图片、脚本或嵌入式图表。Funnel图通常是通过链接或外部脚本生成的ÿ…...

C语言 ——— 写一个函数,判断一个字符串是否为另外一个字符串旋转之后的字符串
目录 题目要求 代码思路 代码实现 题目要求 写一个函数,判断一个字符串是否为另外一个字符串旋转之后的字符串 例如 s1 "AABCD" ;s2 "BCDAA" ,返回1 s1 "AABcd" ;s2 "BCDAA" …...
白骑士的Matlab教学实战项目篇 4.4 机器学习与AI
系列目录 上一篇:白骑士的Matlab教学实战项目篇 4.3 控制系统设计 机器学习与人工智能(AI)是当前技术发展的前沿领域,通过数据驱动的模型和算法,可以解决许多复杂的问题。MATLAB 提供了丰富的工具和函数,支…...
事件监控模块——Channel模块
这个模块就是 记录文件描述符 和 想要监控的事件,实际就绪的事件,以及事件发生之后要怎么做,判断有没有监控这个事件,获取事件。 class Poller; class EventLoop; class Channel {private:int _fd;EventLoop *_loop;uint32_t _ev…...

OCR调研
OCR调研 一、介绍 OCR(Optical Character Recognition,光学字符识别)是一种将图像中的文字转换为计算机可处理格式的技术。OCR技术经历了从传统OCR到基于深度学习的OCR的转变。深度学习OCR技术通过模拟人脑神经元结构处理文本和图像数据&am…...
数据结构(学习版)
考纲 (一)数据结构部分; 线性表栈、队列、数组查找和内部排序树和图 (二)计算机算法设计部分: 递归与分省策路、回溯法贪心算法、分支限界法、动态规划算法设计中的数据结构运用 (三)程序设计基础(C或C)部分: 基本数据类型、各种运算符和表达式、基本控制结构。数组的定义…...

除了知云文献翻译外,这几款翻译工具值得推荐!
近年来,市面上涌现出众多优秀的文献翻译工具,其中知云文献翻译凭借其强大的功能受到了广泛好评。然而,除了知云文献翻译外,还有几款翻译工具同样值得推荐。今天,就让我们一起来了解一下! Foxit在线翻译 链…...
Element UI动态实现面包屑导航~
思路:监听路由变化,在路由规则中添加meta然后在组件中渲染。 import Vue from "vue" import VueRouter from "vue-router" Vue.use(VueRouter) // 解决ElementUI导航栏中的vue-router在3.0版本以上重复点菜单报错问题 const origin…...

安科瑞Acrel-2000ES储能能量管理系统在新型电力系统下分布式储能的研究
摘要:传统电力系统的结构和运行模式在以新能源为主体的新型电力系统中发生了巨大的变化,分布式储能作为电力系统中重要的能量调节器,也迎来了新的发展机遇。立足于储能技术发展现状,分析了分布式储能技术特点及在清洁可再生能源方…...

Git 逆转时光:版本回退操作详解
git 版本回退操作详解 一、Git的工作流程二、git clean尚未 commit 的修改三、已经 commit 尚未 push 到 remote 仓库四、已经提交到 remote 仓库五、回退建议六、总结 一、Git的工作流程 在讲这个版本回退之前,我们要温习一下Git的原理。下面这张图就是 Git 的整个…...

8.6.数据库基础技术-数据库的控制
并非控制 事务:由一系列DML操作组成,这些操作,要么全做,要么全不做,它从第一个DML操作开始,rollback、commit或者DDL结束,拥有以下四种特性,详解如下: (操作)…...
php语言基础入门
文章目录 php语言基础入门一、简介二、基础语法1、变量2、常量3、注释4、基础数据类型4.1、整形数据类型4.2、布尔数据类型4.3、字符串数据类型4.4、浮点型数据类型4.5、PHP数据类型之查看和判断数据类型 5、流程控制5.1、if-elseif-else语句使用5.2、switch语句使用5.3、while…...

告别杂音,从 AI 音频降噪开始
生活中,音频无处不在。无论是聆听动人的音乐,还是参与重要的电话会议,又或是沉浸于精彩的网课学习,清晰、纯净的音频质量都至关重要。然而,音频中的噪声却像不速之客,扰乱着这份美好。 音频中的噪声形式多样…...
Postman中params传参与Body传参区别以及Body中不同类型的区别
Postman中params传参与Body传参区别 在HTTP请求中,参数可以通过不同的方式传递给服务器,其中最常见的两种方式是使用params(查询参数)和body(请求体)。以下是它们的主要区别: 1. 位置…...

数据结构入门——05队列
1.队列 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先 进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾出队列:进行删除操作的一端称为队头 队列也可以数…...

【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...

以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...