热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ZipInputStream和RSA算法的纠葛

背景以前有一篇文章介绍过系统升级操作的实现流程:通过上传zip压缩包、并通过RMI方式调用另一个Java程序执行upgrade.sh脚本完成的。其中有一个系统版本信息校验的逻辑,版本信息是一

背景

以前有一篇文章介绍过系统升级操作的实现流程:通过上传zip压缩包、并通过RMI方式调用另一个Java程序执行upgrade.sh脚本完成的。其中有一个系统版本信息校验的逻辑,版本信息是一段xml信息经过RSA算法加密,直接打包到zip文件中。系统升级操作,首先对zip文件中的版本描述信息进行解密。

存在一个诡异的问题,只有我本机的360压缩工具生成的zip文件,直接读取密文并解密才不会出错,而winRAR或者7Zip工具生成的压缩文件都报解密异常。这个问题困扰了鄙人一年,这是个大坑啊,如果不解决,万一我的电脑挂了,这个功能就无法正常运转了。昨天没事儿,想着把这个坑抹平,就下决心要找找原因。最终被同事找到根源了,记录一下IO中的坑。

直接读取ZIP文件

升级操作直接读取zip文件流中的加密密文,Java直接读取Zip文件的流程如下:

public static void readFromZip(String zipFileName) throws IOException{
ZipFile zf = null;
InputStream in = null;
ZipInputStream zin = null;

try{
zf = new ZipFile(zipFileName);
in = new BufferedInputStream(new FileInputStream(zipFileName));
zin = new ZipInputStream(in);

ZipEntry ze = null;
while ((ze = zin.getNextEntry()) != null) {
String zipName = ze.getName();
if(zipName.contains("descriptor")){//找到密文文件并读取
InputStream inputStream = zf.getInputStream(ze);
byte[] data = new byte[inputStream.available()];
int len = 0;
while ((len = inputStream.read(data)) > 0) {
System.out.println("length:"+len);
}
System.out.println("data is :"+Arrays.toString(data)); }
}
} finally {
try {
zin.closeEntry();
in.close();
zf.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}

直接使用ZipInputStream类,逐个遍历压缩包中的每个文件,找到加密文件后读取该文件的内容到字节数组中。这里处理时有一个问题,Java对不同压缩工具生成的压缩文件处理方式有差异。

不同压缩工具的对应Java实现的差异

1 )WinRAR压缩文件,在使用Java IO工具读取时,zf.getInputStream()流的实例对象为:

这里写图片描述

这个类的read(data)操作,分了两次才读完数据。执行打印结果为:

length:765
length:3

2)360压缩文件,在使用Java IO工具读取时,zf.getInputStream()流的实例对象为:

这里写图片描述

这个类的read(data)操作,一次读完数据。执行打印结果为:

length:768

3)使用BufferedInputStream,定义缓存取后,两种流都能一次读完加密数据。

    public static byte[] bufferedReadFromInputStream(InputStream inputstream) throws IOException{
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputstream);
byte[] data = new byte[inputstream.available()];
int len = 0;
while ((len = bufferedInputStream.read(data)) > 0) {

}

return data;
}

这是因为原始的InputStream的read()操作,是每完成一次io读取,就往内部缓冲区执行写入一次数据;而缓冲区定义后,只有缓冲区写满或者读不到数据时才写入内存,这就能保证每次写入内存时的数据长度是有保障的。

RSA解密流程

压缩文件中的加密文件是通过RSA算法生成的,解密代码如下:

private byte[] decryptDescriptor(InputStream inputstream) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
X509EncodedKeySpec keySpec;
try {
keySpec = new X509EncodedKeySpec(
new BASE64Decoder().decodeBuffer(Encription.KEY_PUBLIC));
PublicKey key = KeyFactory.getInstance("RSA").generatePublic(keySpec);

Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] data = new byte[cipher.getOutputSize(inputstream.available())];
int len = 0;
//直接循环读取密文输入流,doFinal解密写入字节输出流中
while ((len = bufferedInputStream.read(data)) > 0) {
bos.write(cipher.doFinal(data, 0, len));
}

return bos.toByteArray();
} catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException
| InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
logger.error("解密升级描述文件异常.",e);
}finally{
try {
inputstream.close();
} catch (IOException e) {
logger.error("关闭升级描述文件流异常.",e);
}
}

return null;
}

上述解密代码,直接使用InputStream的read(data)方法,现将文件读入字节数组中,然后调用doFinal进行解密。那么对于WinRAR压缩包莫名多出一次的读取操作而言,就有问题。

RSA解密算法会根据待解密数据的长度,得到一个固定长度,然后每次doFinal时只解密指定长度的数据。

byte[] data = new byte[cipher.getOutputSize(inputstream.available())];

这行代码定义的data长度,是128,根据真正的文件长度768得到的。那么InputStream在执行read操作过程中会读768/128=6次,每次读取128字节的数据。360压缩文件是符合这逻辑的。但是WinRAR压缩文件因为莫名多读了一次,导致第6次读取的长度是125,最后3字节还需再读一次。而doFinal在第6次执行时期待128字节,结果只有正常的125字节的数据,所以就直接报异常了。

这里写图片描述

解决办法:通过缓冲区输入流,让不同的压缩文件都read相同的次数,保证doFinal操作能得到正确的数据。修改上述代码如下:

private byte[] decryptDescriptor(InputStream inputstream) {
//使用缓冲字节流
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputstream);
……
try {
……

byte[] data = new byte[cipher.getOutputSize(bufferedInputStream .available())];
int len = 0;
//缓冲读取密文,doFinal解密写入字节输出流中
while ((len = bufferedInputStream .read(data)) > 0) {
bos.write(cipher.doFinal(data, 0, len));
}

return bos.toByteArray();
} catch (){ ……
}

return null;
}

启示录

处理文件的时候,尽量使用BufferedInputstream,速度高效,减少不必要的IO次数。万一遇到本文这样的问题,就一并解决了。

“人是有惰性的动物,如果过多地沉湎于温柔乡里,就会削弱重新投入风暴的勇气和力量。”摘自路遥先生的《早晨从中午开始》,欠下的债总是要还的,也是对自己的警醒。


推荐阅读
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 集成电路企业在进行跨隔离网数据交换时面临着安全性问题,传统的数据交换方式存在安全性堪忧、效率低下等问题。本文以《Ftrans跨网文件安全交换系统》为例,介绍了如何通过丰富的审批流程来满足企业的合规要求,保障数据交换的安全性。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • 本文讨论了编写可保护的代码的重要性,包括提高代码的可读性、可调试性和直观性。同时介绍了优化代码的方法,如代码格式化、解释函数和提炼函数等。还提到了一些常见的坏代码味道,如不规范的命名、重复代码、过长的函数和参数列表等。最后,介绍了如何处理数据泥团和进行函数重构,以提高代码质量和可维护性。 ... [详细]
  • 本文介绍了如何使用JSONObiect和Gson相关方法实现json数据与kotlin对象的相互转换。首先解释了JSON的概念和数据格式,然后详细介绍了相关API,包括JSONObject和Gson的使用方法。接着讲解了如何将json格式的字符串转换为kotlin对象或List,以及如何将kotlin对象转换为json字符串。最后提到了使用Map封装json对象的特殊情况。文章还对JSON和XML进行了比较,指出了JSON的优势和缺点。 ... [详细]
  • GreenDAO快速入门
    前言之前在自己做项目的时候,用到了GreenDAO数据库,其实对于数据库辅助工具库从OrmLite,到litePal再到GreenDAO,总是在不停的切换,但是没有真正去了解他们的 ... [详细]
  • Java和JavaScript是什么关系?java跟javaScript都是编程语言,只是java跟javaScript没有什么太大关系,一个是脚本语言(前端语言),一个是面向对象 ... [详细]
  • 本文介绍了OkHttp3的基本使用和特性,包括支持HTTP/2、连接池、GZIP压缩、缓存等功能。同时还提到了OkHttp3的适用平台和源码阅读计划。文章还介绍了OkHttp3的请求/响应API的设计和使用方式,包括阻塞式的同步请求和带回调的异步请求。 ... [详细]
  • 14亿人的大项目,腾讯云数据库拿下!
    全国人 ... [详细]
author-avatar
平凡洗护店
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有