Search code examples
javainputstreamencryptioncrypt

Lines being split while decrypting file into a list


Im trying to read my encrypted file and placing its decrypted contents into a list, but some lines towards the end split randomly or half way to a new line. Any ideas why it's doing this? (In the decrypt method). Btw the buffer is 1024 if that helps.

public Crypto() {
    try {
        PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        SecretKey key = keyFactory.generateSecret(keySpec);
        ecipher = Cipher.getInstance("PBEWithMD5AndDES");
        dcipher = Cipher.getInstance("PBEWithMD5AndDES");
        byte[] salt = new byte[8];
        PBEParameterSpec paramSpec = new PBEParameterSpec(salt, 100);
        ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
        dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
        } catch (Exception e) {
    }
}

@SuppressWarnings("resource")
public static List<String> decrypt(String file) {
    List<String> list = new LinkedList<String>();
    try {
        InputStream in = new CipherInputStream(new FileInputStream(file), dcipher);
        int numRead = 0;
        while ((numRead = in.read(buffer)) >= 0) {
            list.add(new String(buffer, 0, numRead);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return list;
}

Solution

  • The read method on a Stream just reads a number of Bytes into a buffer. So your code reads the file in chunks of size 1024 and saves each chunk read into a List.

    There are several ways to read a Stream line by line, I would recommend the BufferedReader

    final List<String> list = new LinkedList<String>();
    try {
        final BufferedReader reader = new BufferedReader(new InputStreamReader(new CipherInputStream(new FileInputStream(file), dcipher)));
        String line;
        while ((line = reader.readLine()) != null) {
            list.add(line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    One thing to note that has caught me out on numerous occasions is that the InputStreamReader does an implicit conversion from Byte to String - this requires an encoding. By default the platform encoding will be used, this means that your code is platform dependent. The same goes for your original code as new String(byte[]) also uses the platform encoding be default.

    I would recommend always specifying the encoding explicitly:

    final BufferedReader reader = new BufferedReader(new InputStreamReader(new CipherInputStream(new FileInputStream(file), dcipher), "UTF-8"));
    

    Or for the String constructor

    new String(bytes, "UTF-8")
    

    The same goes for any code that writes a String to a File:

    try {
        try (final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new CipherOutputStream(new FileOutputStream(file), dcipher), "UTF-8"))) {
            writer.append(data);
        }
    } catch (IOException ex) {
        e.printStackTrace();
    }
    

    This way you avoid nasty surprises when running your application on a different OS as each family use a different default encoding (UTF-8 on Linux, ISO-8859-1 on Windows and MacRoman on Mac).

    Another note is that you do not close your Stream (or Reader now) - this is necessary, it can be done in a finally in java 6 or using the new try-with-resources construct of java 7.

    try (final BufferedReader reader = new BufferedReader(new InputStreamReader(new CipherInputStream(new FileInputStream(file), dcipher), "UTF-8"))) {
        String line;
        while ((line = reader.readLine()) != null) {
            list.add(line);
        }
    }