【问题标题】:Incomplete Java fileoutputstream with textfiles带有文本文件的不完整 Java 文件输出流
【发布时间】:2013-11-29 19:03:56
【问题描述】:

我正在编写一个小工具来加密和解密文件。加密文件将被添加到 zip 存档中。到目前为止,以下代码正在运行。我可以加密除文本文件以外的任何文件。如果我选择一个文本文件,它写得不完整。如果有以下文字: “一二三检查检查检查”它会像“一二三检查车”一样被删减。

没有抛出异常,只是丢失了几个字节。任何其他文件(pdf、mp3、mp4、exe 等)都是完整编写的。

package encryptme.crypto;

import encryptme.fileoperations.FileIn;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;

public class Encrypt {

    private String algorithm;
    private int keylength;
    private Key key;
    private KeyPair keypair;
    private File[] source;
    private String destination;

    public Encrypt(String a, int kl, Key k, File[] s, String dest) {
        this.algorithm = a;
        this.keylength = kl;
        this.key = k;
        this.source = s;
        this.destination = dest;
        this.output = o;
    }

    public void encOut() {
        CipherOutputStream cout = null;
        ZipOutputStream zout = null;
        OutputStream out = null;
        Cipher cipher;
        try {
            out = new FileOutputStream(this.destination);
            System.out.println(this.destination);
            zout = new ZipOutputStream(out);
            cipher = Cipher.getInstance(this.algorithm);
            cipher.init(Cipher.ENCRYPT_MODE, this.key);
            for (int i = 0; i < this.source.length; i++) {
                FileInputStream in = new FileInputStream(this.source[i].getAbsolutePath());
                ZipEntry ze = new ZipEntry(this.source[i].getName());                 
                zout.putNextEntry(ze);
                cout = new CipherOutputStream(zout, cipher);
                byte[] buffer = new byte[1024];
                long bytesRead = 0;
                for (bytesRead = 0; (bytesRead = in.read(buffer)) != -1;) {
                    cout.write(buffer, 0, (int) bytesRead);
                }
                zout.closeEntry();
                in.close();
            }
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchPaddingException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidKeyException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                if (cout != null) {
                    cout.close();
                }
                if (zout != null) {
                    zout.close();
                }
                if (out != null) {
                    out.close();
                }
            } catch (IOException ex) {
                Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

使用 jFileChooser 选择要加密的文件并添加到一个数组中,该数组作为参数传递给上面的类。我考虑过为那些通过结尾识别它们的文本文件使用 PrintWriter,例如“textfile.txt”或“textfile.odt”。但是如果文件没有像'textfile'这样的结尾,我该怎么办?

我对 Java 和一般编码还很陌生,所以我希望只是一个严重的错误导致了问题。

【问题讨论】:

  • 如果您跳过密码输出流而只使用常规流,它可以工作吗?
  • 如果我直接写入 ZipOutputStream 就可以了。
  • 尝试刷新然后关闭 CipherOutputStream?
  • 您是否尝试将缓冲区大小更改为 byte[] buffer = new byte[2048];您的 bytesread 计数是否会增加缓冲区大小?
  • 刷新关闭不生效。增加缓冲区大小也不起作用。

标签: java encryption fileoutputstream zipoutputstream


【解决方案1】:

您需要在调用zout.closeEntry 之前关闭cout,以便清除密码缓冲的任何剩余数据。不幸的是,cout.close() 将调用 zout.close(),我们绝对不希望这样做,因为这会阻止您编写任何进一步的条目。

我会使用CloseShieldOutputStream from commons-io

cout = new CipherOutputStream(new CloseShieldOutputStream(zout), cipher);

此类旨在阻止 close() 调用传播到底层封装流(在本例中为 zout)。

【讨论】:

  • 就是这样!现在就像一个魅力。我之前尝试在没有 CloseShieldOutputStream 的情况下关闭 CipherOutputStream,就像你说的那样,这导致 ZipOutputStream 关​​闭。我已经坐了好几个星期了……非常感谢。
  • @Seppel 这就是为什么我希望更多的过滤流类型具有像 GZIPOutputStream 这样的finish() 方法,将“完成你正在做的事情”与“并关闭底层流”分开。但如果没有这个,关闭的屏蔽流是一个方便的工具。
【解决方案2】:

我不是 100% 确定您的要求是什么,但是您想将收到的文件数组清理到您的 Encrypt 类吗?一种方法确实是查看文件扩展名:

public class ImageFileFilter implements FileFilter {

        private String[] imageExtentions = {"png", "jpg", "gif" };

        @Override
        public boolean accept(File file) {
                for(String ext : imageExtentions){
                        if(file.isFile()){
                                if(file.getName().toLowerCase().endsWith(ext))
                                        return true;
                        }
                }
                return false;
        }
}

上面的示例是一个应该处理图像扩展的类,但它是通用的,因此它适用于传递给接受方法的任何扩展。关键是实现 FileFilter 接口,然后你可以清理你的文件。

编辑: 啊,你编辑了你的问题,让你更清楚你想要什么,这不是我给出的答案所解决的问题。

【讨论】:

  • 清理阵列是解决问题的一种方法。但是如果文件没有扩展名,我就不能使用它。我的意思是文本文件通常看起来像'file.txt'。如果它只是没有 .txt 的“文件”,则您的解决方案将不起作用。
  • @Seppel 你试过看看它是否排除了以“.txt”结尾的文件扩展名,无论如何,你可以使用正则表达式来解决你的问题。就像在 accept 方法中一样,编写一个正则表达式来检查文件是否不以“.txt”结尾
  • 缺少的文件扩展名只是一个理论案例。如果用户删除扩展名以“隐藏”它。我想我会写一些 FileFilters 并将 StreamWriter 用于文本文件。不匹配的文件将不会被加密,用户将收到一条错误消息。我唯一不明白的是为什么它在没有 CipherOutputStream 的情况下工作。
猜你喜欢
  • 1970-01-01
  • 2012-05-16
  • 1970-01-01
  • 2016-01-18
  • 2013-09-05
  • 1970-01-01
  • 1970-01-01
  • 2013-10-21
  • 1970-01-01
相关资源
最近更新 更多