Search code examples
javabinaryreadfilexorwritefile

xOr decryption replaces correct letter with wrong letter throughout the whole text file


Java: I'm encrypting and decrypting a text file using a key of any two ASCII characters on the keyboard. I have them working correctly, except when I read the encrypted file to a string for decryption. It replaces some specific letter with a different incorrect letter, but not all of correct letters are replaced. Some t's are replaced with s's for example. I've also seen some b's be replaced with e's when I use a different key.

I've already looked through my encrypted/decryption algorithm. I copy and pasted the encrypted text file into my code and ran the algorithm again, it came out perfect. The only time the letters are replaced is when the encrypted algorithm is read from a text file to be decrypted.

public static String readFileToString(string filePath) {
    StringBuilder builder = new StringBuilder();
    try (Stream<String> stream = Files.get(filePath), StandardCharsets.UTF_8)){
        stream.forEach(s->builder.append(s).append("\n");
    }
    catch(IOException e){
        e.printStackTrace();
    }
    return builder.toString();
}

public static void writeFile(String crypt) throws IOException {
    Scanner sc = new Scanner(System.in);
    System.out.println("New file name: ");
    String fileName = sc.nextLine();
    String writtenString = crypt;
    String userHome = System.getProperty("user.home");
    File textFile = new File(userHome, fileName + ".txt");
    BufferedWriter out = new BufferedWriter(new FileWriter(textFile));
    out.write(writtenString);
    out.close();

//Converts string and key into binary characters for 1-to-1 xOr to prevent any possible translation errors.
public static String crypt(String input, String key) throws UnsupportedEncodingException {
    if (input.length() % 2 == 1) {
        input = input + " ";
    }
    int n = input.length() / 2;
    key = new String(new char[n]).replace("\0", key);
    byte[] a = input.getBytes();
    byte[] c = key.getBytes();
    StringBuilder binaryBuilder = new StringBuilder();
    StringBuilder binaryKeyBuilder = new StringBuilder();
    //Creates a StringBuilder of bits using the file text
    for(byte b: a) {
        int value = b;
        for(int i = 0; i < 8; i++) {
            binaryBuilder.append((value & 128) == 0 ? 0 : 1);
            value <<= 1;
        }
        binaryBuilder.append(' ');
    }
    //Converts binary StringBuilder to String
    String binary = binaryBuilder.toString();
    //Creates a StringBuilder of bits using the provided key
    for(byte d: c) {
        int keyValue = d;
        for(int j = 0; j < 8; j++) {
            binaryKeyBuilder.append((keyValue & 128) == 0 ? 0 : 1);
            keyValue <<= 1;
        }
        binaryKeyBuilder.append(' ');
    }
    //Converts binaryKey StringBuilder to String
    String binaryKey = binaryKeyBuilder.toString();
    //Creates StringBuilder of bits using the provided key
    StringBuilder xOr = new StringBuilder();
    for(int q = 0; q < binary.length();q++) {
        xOr.append(binary.charAt(q) ^ binaryKey.charAt(q));
    }
    String xOrResult = xOr.toString();
    String cryptedString = "";
    char next;
    //Iterates through binary string to convert to ASCII characters 8 bits at a time.
    for(int k = 0; k <= xOrResult.length()-8; k+=9) {
        next = (char)Integer.parseInt(xOrResult.substring(k,k+8), 2);
        cryptedString += next;
    }
    return cryptedString;
}

When I use the key "ty"

"Four score and seven years ago our fathers brought forth on this" is the correct phrasing. However, I'm getting: "Four score and seven years ago our fashers broughs forth on this"


Solution

  • I would use binary file for encrypted text. It will save you from dealing with UTF-8 encoding/decoding some unusual code points. For example - when you xor 't' and 't' you get character with code 0.

    You can also get unexpected new line characters. You actually replace all of them with '\n', but there are other options - '\r', or even two characters in sequence "\r\n". All of them will be replaced with '\n' in your code, and lead to mistakes after decryption.

    What happened here:

    Binary ASCII (or UTF-8) code for t is 01110100, and for y it is 01111001. When character y from key meets character t from text you get 01110100 xor 01111001 = 00001101 = 0x0D = '\r'. This character is written to file. When you read that file line by line, this '\r' is skipped as line separator. You replace it with '\n'=00001010 in line

    stream.forEach(s->builder.append(s).append("\n");
    

    When decrypting that text we get 00001010 (\n) xor 01111001 (y) = 01110011 (s).