Search code examples
javaencryptionrc4-cipher

Is it possible to encrypt a file line by line without encoding the resulting byte array before writing to file?


I need to encrypt a file line by line using the RC4 algorithm without encoding the resulting byte array from the encryption. I saw a post on here about how to encrypt line by line and it works fine but when I attempt to do it skipping the encoding step only the first line gets decrypted successfully . Is it possible to just write the byte array to file without encoding it and be able to successfully decrypt that file?

This is what I have tried:

while ((line = br.readLine()) != null) 
{

        Cipher rc4 = Cipher.getInstance("RC4");                             
        SecretKeySpec rc4Key = new SecretKeySpec(pwd.getBytes(), "RC4");                                    
        rc4.init(Cipher.ENCRYPT_MODE, rc4Key);
        byte [] cipherText = rc4.doFinal(line.getBytes());                                 
        fos.write(cipherText);                                
        fos.flush();
}

//decrypt file
    byte [] decrypt = Files.readAllBytes(Paths.get(outputFile));
    Cipher rc4d = Cipher.getInstance("RC4");
    SecretKeySpec rc4dKey = new SecretKeySpec(pwd.getBytes(), "RC4");
    rc4d.init(Cipher.DECRYPT_MODE, rc4dKey);
    byte [] decrypted = rc4d.doFinal(decrypt);
    String results = new String(decrypted);
    System.out.println("Decrypted : " + results);

Solution

  • Yes, of course that is possible, but in that case you need to keep the line endings present. Then you can create a method decryptLine which will end when the plaintext consists of a line ending. This will probably require you to decrypt byte by byte though.

    If you remove any indication of lines, as you currently do for the plaintext message, then there is no way to see the lines anymore. The stream cipher will encrypt the lines but as a stream cipher does not pad or otherwise alter the plaintext, the line endings are gone, and there are no other indicators that show where the lines should be.

    RC4 is an old insecure cipher. Much worse, using RC4 by re-using doFinal over a plaintext is so insecure that anybody should be able to retrieve the plaintext. Basically, you start over the encryption phase which is just XOR with the key stream generated by RC4, which will allow attacks like that on a reused one-time-pad.

    Besides that, if the RC4 encryption over the lines with line encodings is just advanced using update rather than restarted using doFinal then the file will just be the same as the binary encoding of the file. In other words, you might as well simply encrypt the entire file.

    So whoever asked you to perform the task and the person that wrote this particular example do not seem to have a clue about crypto.


    But yeah, sometimes you just want to see some code for learning purposes. The following code uses functionality in the Java stream API to read and write the lines, while using CipherOutputStream and CipherInputStream and to encrypt and decrypt the binary data.

    Note that:

    • you'd need one class instance for each file, reusing a class instance (or the key) will make the code insecure as RC4 doesn't use an IV;
    • with BufferedReader you are able to extract the lines from the ciphertext after decryption but note that the reader may decrypt more than just the line in the underlying buffer;
    • this code does not handle exceptions well (see here how to handle crypto exceptions correctly);
    • this will convert the line endings to the platform default encoding ("%n" format string - which should really be stored in a constant, but yeah);
    • it assumes UTF-8 / compatible encoding for files.
    package com.stackexchange.so;
    
    import static java.nio.charset.StandardCharsets.UTF_8;
    
    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.FileReader;
    import java.io.FileWriter;
    import java.io.InputStreamReader;
    
    import javax.crypto.Cipher;
    import javax.crypto.CipherInputStream;
    import javax.crypto.CipherOutputStream;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.SecretKeySpec;
    
    public class LineStreamRC4 {
    
        private SecretKey rc4Key;
    
        public LineStreamRC4(SecretKey rc4Key) {
            this.rc4Key = rc4Key;
    
        }
    
        public void encryptLineByLine(File in, File out) throws Exception {
            Cipher c = Cipher.getInstance("RC4");
            c.init(Cipher.ENCRYPT_MODE, rc4Key);
    
            try (BufferedReader reader = new BufferedReader(
                    new FileReader(in, UTF_8));
                    CipherOutputStream cryptWriter = new CipherOutputStream(
                            new FileOutputStream(out), c)) {
    
                String line;
                while ((line = reader.readLine()) != null) {
                    line += String.format("%n");
                    cryptWriter.write(line.getBytes(UTF_8));
                }
            }
        }
    
        public void decryptLineByLine(File in, File out) throws Exception {
            Cipher c = Cipher.getInstance("RC4");
            c.init(Cipher.DECRYPT_MODE, rc4Key);
    
            try (BufferedReader cryptReader = new BufferedReader(
                    new InputStreamReader(
                            new CipherInputStream(new FileInputStream(in), c), UTF_8));
                    FileWriter writer = new FileWriter(out, UTF_8)) {
    
                String line;
                while ((line = cryptReader.readLine()) != null) {
                    line += String.format("%n");
                    writer.write(line);
                }
            }
        }
    
        public static void main(String[] args) throws Exception {
            File pt = new File("src/com/stackexchange/so/LineStreamRC4.java");
            File ct = new File("bla.ct");
            LineStreamRC4 rc4LineStream = new LineStreamRC4(new SecretKeySpec(new byte[16], "RC4"));
            rc4LineStream.encryptLineByLine(pt, ct);
            File pt2 = new File("bla.pt");
            rc4LineStream.decryptLineByLine(ct, pt2);
        }
    }