ZipInputStream 和 RSA 算法的纠葛

背景以前实现过一个系统升级操作:通过上传 zip 压缩包、并通过 RMI 方式调用另一个 Java 程序执行upgrade.sh 脚本完成的 。 其中有一个系统版本信息校验的逻辑 , 版本信息是一段经过 RSA 算法加密的 xml 信息 , 直接打包到 zip 文件中 。
系统升级操作 , 首先会对 zip文件中的版本描述信息进行解密 , 与当前系统数据库中维护的版本信息进行比对 , 校验通过才运行执行升级操作 。
该功能存在一个诡异的问题 , 只有我本机的 360压缩工具生成的 zip 文件 , 解密代码才不会出错 , 而用 winRAR 或者 7Zip 工具生成的压缩文件都报解密异常 。
这个问题困扰了我一年 , 因为这个不常用的功能 , 所以缺陷只有我知道 。 但是 , 这是个大坑 , 如果不解决 , 万一我的电脑挂了或者离职了 , 这个功能就无法正常运转了 。
某一天没事儿 , 想着把这个坑抹平 , 就下决心要找找原因 。 最终找到根源了 , 本文来分享一下 Java IO 操作中的坑 。
直接读取 ZIP 文件【ZipInputStream 和 RSA 算法的纠葛】升级操作直接读取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 = http://kandian.youth.cn/index/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()流的实例对象为:
ZipInputStream 和 RSA 算法的纠葛文章插图
这个类的read(data)操作 , 分了两次才读完数据 。 执行打印结果为:
length:765length:3
2、360压缩文件在使用Java IO工具读取时 , zf.getInputStream()流的实例对象为:
ZipInputStream 和 RSA 算法的纠葛文章插图
这个类的read(data)操作 , 一次读完数据 。 执行打印结果为:
length:768
使用BufferedInputStream , 定义缓存取后 , 两种流都能一次读完加密数据 。
public static byte[] bufferedReadFromInputStream(InputStream inputstream) throws IOException{BufferedInputStream bufferedInputStream = new BufferedInputStream(inputstream);byte[] data = http://kandian.youth.cn/index/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 = http://kandian.youth.cn/index/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压缩包莫名多出一次的读取操作而言 , 就有问题 。