Search code examples
javaencryptioninputstreamoutputstreamxor

Encrypting a string with XOR using Java filter streams


I'm trying to encrypt a text with XOR using pseudorandom numbers and then write the encrypted text to a file. It then reads the file again and if the user enters the correct key it decrypts the text. However all I get is ?????. What am I doing wrong?

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Random;
import java.util.Scanner;

public class MainTest
{
    public static void main(String[] args)
    {
        long seed = 754; // key
        Random random = new Random(seed);

        String message = "hello";

        try
        {
            OutputStream os = new FileOutputStream("test.txt");
            OutputStream bos = new BufferedOutputStream(os);
            EncryptOutputStream eos = new EncryptOutputStream(bos);

            for(int i = 0; i < message.length(); i++)
            {
                int z = random.nextInt();
                eos.write(message.charAt(i), z);
            }

            eos.close();

            InputStream is = new FileInputStream("test.txt");
            InputStream bis = new BufferedInputStream(is);
            DecryptInputStream dis = new DecryptInputStream(bis);

            Scanner scanner = new Scanner(System.in);
            System.out.print("Enter key: ");
            long key = scanner.nextLong();
            scanner.close();

            random = new Random(key);

            int c;
            while((c = dis.read(random.nextInt())) != -1)
            {
                System.out.print((char)c);  
            }

            dis.close();
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
}
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

public class DecryptInputStream extends FilterInputStream
{

    protected DecryptInputStream(InputStream in)
    {
        super(in);
    }

    public int read(int z) throws IOException
    {
        int c = super.read();
        if(c == -1) return -1;
        return c^z;
    }
}
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class EncryptOutputStream extends FilterOutputStream
{

    public EncryptOutputStream(OutputStream o)
    {
        super(o);
    }

    public void write(int c, int z) throws IOException
    {
        super.write(c^z);
    }

}

Solution

  • When decrypting, you need to mask out the higher order bits after XOR-ing.

    public int read(int z) throws IOException
    {
      int c = super.read();
      if(c == -1) return -1;
      return (c^z) & 0xFF; /* Only use lowest 8 bits */
    }
    

    When you encrypt, you are producing a 32-bit int. The most significant 16 bits contain the upper bits of the key stream, while the lower 16 contain the ciphertext. However, only the lowest 8 bits are written to the output file, because OutputStream writes bytes. For the message "hello," this happens to be okay, since all of those characters are encoded in the lowest 7 bits anyway.

    When you decrypt, however, you are again creating a 32-bit int where the upper 24-bits are filled with bits from the keystream. When you cast to a char, bits 31-16 are discarded, but bits 15-8 are filled with junk, and lowest bits, 0-8, should contain the original character. Because the upper bits of the char are garbage, you get garbled output.