2
\$\begingroup\$

I implemented a Vigenere encoding and decoding class. Ideally, the program should be able to take any abritrary file, read into an array of byte, and decode it.

import java.io.*;

class VigenereFilterReader extends FilterReader {
    private final char[] key;
    private int i = 0;

    VigenereFilterReader(Reader in, char[] k) {
        super(in);
        key = k.clone();
    }

    VigenereFilterReader(Reader in, String k) {
        super(in);
        key = k.toCharArray().clone();
    }

    public int read() throws IOException {
        int b1 = super.read();
        int b2 = super.read();

        if (b1 == -1 || b2 == -1)
            return -1;
        int b;
        b = Integer.parseInt(Character.toString((char) b1) + Character.toString((char) b2), 16);

        int out = b ^ key[i];
        i ++;
        i %= key.length;
        return out;
    }

    public String readString() throws Exception {
        StringBuilder out = new StringBuilder();
        int c;
        while ((c = this.read()) != -1) {
            out.append((char) c);
        }
        return out.toString();
    }
}

class VigenereFilterWriter extends FilterWriter {
    private final char[] key;

    VigenereFilterWriter(Writer out, String k) {
        super(out);
        key = k.toCharArray().clone();
    }

    VigenereFilterWriter(Writer out, char[] k) {
        super(out);
        key = k.clone();
    }

    public void write(String s) throws IOException{
        this.write(s.toCharArray());
    }

    public void write(char[] info) throws IOException {
        StringBuilder out = new StringBuilder();

        for(int i = 0; i < info.length; i++) {
            out.append(String.format("%02X",(info[i] ^ key[i % key.length])));
        }
        super.write(out.toString());
    }
}

class Vigenere {
    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
            throw new Exception("Missing filename");
        }

        String text = "helloworldahelloworld testing a long sequence";
        String key =  "abcdef";

        FileWriter fileEncrypt = new FileWriter(args[0]);
        VigenereFilterWriter encrypt = new VigenereFilterWriter(fileEncrypt, key);

        encrypt.write(text);

        fileEncrypt.close();

        FileReader fileIn = new FileReader(args[0]);
        VigenereFilterReader in = new VigenereFilterReader(fileIn, key);

        System.out.println(in.readString());
    }
}
  1. This implementation use some casting (line 36, 63 etc). Is the cast dangerous?

  2. What is a better way to store encoded byte in file? I used hex string (so that encoded file is readable), but it's quite inconvenient (toHexString at the writer, and when read I have to do 2 read char).

P/S: Here is my updated version using byte IO. Please feel welcome to criticize this code.

import java.io.*;

class VigenereFilterInputStream extends FilterInputStream {
    private final byte[] key;
    private int index = 0;

    VigenereFilterInputStream(InputStream in, byte[] k) {
        super(in);
        key = k.clone();
    }

    public int read() throws IOException {
        int c = super.read();

        if (c == -1) 
            return -1;

        int out = c ^ key[index];
        index ++;
        index %= key.length;
        return out;
    }

    public int read(byte[] b) throws IOException {
        int result = in.read(b);
        for(int i = 0; i < b.length; i++) {
            b[i] = (byte) (b[i] ^ key[i % key.length]);
        }
        return result;
    }
}

class VigenereFilterOutputStream extends FilterOutputStream {
    private final byte[] key;

    VigenereFilterOutputStream(OutputStream out, byte[] k) {
        super(out);
        key = k.clone();
    }

    public void write(byte[] b) throws IOException {
        byte[] out = new byte[b.length];

        for(int i = 0; i < b.length; i++) {
            out[i] = (byte) (b[i] ^ key[i % key.length]);
        }
        super.write(out);
    }
}

class Vigenere {
    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
            throw new Exception("Missing filename");
        }

        File f = new File(args[0]);

        byte[] text = "hello testing long output!@#)!(!*!(@()asdfasdfljkasdfjlkasdfl".getBytes();
        byte[] key = "hello".getBytes();

        FileOutputStream os = new FileOutputStream(f);
        VigenereFilterOutputStream encrypt = new VigenereFilterOutputStream(os, key);

        encrypt.write(text);

        FileInputStream is = new FileInputStream(f);
        BufferedInputStream bis = new BufferedInputStream(is);
        VigenereFilterInputStream decrypt = new VigenereFilterInputStream(bis, key);

        bis.mark(text.length);
        int c;
        while((c = decrypt.read()) != -1) {
            System.out.print((char) c);
        }

        System.out.println();

        bis.reset();
        byte[] b = new byte[text.length];
        decrypt.read(b);

        for(byte d: b) {
            System.out.print((char) d);
        }
        System.out.println();
    }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ I don't see any byte arrays in your code? \$\endgroup\$ – 200_success Feb 14 '16 at 23:56
  • \$\begingroup\$ That's my goal. For now I only know how to use char[]. There's no write(byte[] b) in the FilterWriter class right? \$\endgroup\$ – Dzung Nguyen Feb 15 '16 at 0:07
2
\$\begingroup\$

Your VigenereFilterWriter doesn't work the way a Writer should. Every call to write(char[] info) starts encrypting with the beginning of the key instead of the next character in the key. That is, I would expect

encrypt.write("hello ");
encrypt.write("world");

to be equivalent encrypt.write("hello world"); — but it isn't. On the other hand, your VigenereFilterReader does correctly advance i on every successive call to read().


The Vigenère cipher, as described in Wikipedia, involves finding successors within a 26-letter alphabet. Your XOR variant is quite a bit more complex, though. Your VigenereFilterWriter actually does three things at once:

  1. Convert strings to their Unicode code points,
  2. Perform XOR,
  3. Serialize the result as hex digits.

That's a problematic design.

You're assuming that each character is one byte. That assumption falls apart for characters like α (alpha), which is U+03B1. If you try to String.format("%02X", 0x03b1), you'll get 3B1, and your decoder will see 3B and get out of sync.

The simple solution, and probably the best solution, is to restrict yourself to just the task of performing the XOR encryption. That is, make a VigenereFilterInputStream and VigenereFilterOutputStream instead. You'll need to set up a filter chain to do the job that your current VigenereWriter tries to do:

OutputStream hexOut = new HexOutputStream(System.out);
OutputStream encrypter = new VigenereFilterOutputStream(hexOut, "mySuperSecretKey".getBytes("ISO-8859-1"));
Writer encoder = new OutputStreamWriter(encrypter, "ISO-8859-1");
encoder.write("my super secret message");

… but at least the responsibility of your VigenereFilterOutputStream is clear. The other tasks, which you currently do incorrectly, can be foisted off on other classes — an OutputStreamWriter for task 1, and a HexOutputStream (which you have to write) for task 3.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.