Following the tutorial about Swap and Rotate here is another one using the XOR operator.
As usual a console application named Xor.java is provided.
As is the previous tutorial a class CharAndBits.jave is provided for printing (both at the console and in the GUI) the bits representation of every byte.
This version of CharAndBits has some improvments compare the previous version coming from Secret Code V. You can take it and overload the previous copy you had if you cut & pasted the code from the previous tutorial, our code from SwapAndRotate will still works.
As usual we present a console version:
- Xor.java which has in its main() method unit test
- XorGui.java that uses the preceeding one and dynamically show the encryption/decryption processes
CharAndBits.java
/** * A class for easy representation of all ASCII character on the console * and on GUIs. * This class contains the binary representation of the char it represents * If the char is prinatble it returns its ASCII representation else it returns '.' */ public class CharAndBits implements Comparable<CharAndBits> { // the character by itself private char theChar; // the char to display (will be '.' if not printable private char toAscii; // it's int value used for bitwise operation private int intValue; // its 8 bits representation as a String private String binaryStr; // its 8 bits representation as 8 char containing '0' or '1' private char[] binaryChar; // its 8 bits representation as 8 int containing 0 or 1 private int[] binaryInt; /** * Constructor that receives the char as parameter */ public CharAndBits(char theChar) { // save it this.theChar = theChar; // test if it is printable if it is the case use it else use '.' if(isPrintable()) toAscii = theChar; else toAscii = '.'; // get it's int value intValue = theChar; intValue &= 0xFF; // ok we just support the 255 Ascii characters // convert to binary char and int binaryChar = new char[8]; binaryInt = new int[8]; int temp = intValue; for(int i = 7; i >= 0; i--) { binaryInt[i] = temp & 1; binaryChar[i] = (char) binaryInt[i]; binaryChar[i] += '0'; temp >>>= 1; } // and the whole String binaryStr = new String(binaryChar); } /** * return true of false depending if the char is printable (GUI or console) */ public boolean isPrintable() { return !Character.isISOControl(theChar); } /** * Returns the binary representation of this char */ public String toBinaryString() { return binaryStr; } /** * returns a printable version of an encoded String */ public static String toAsciiString(String encoded) { // convert the String to an array of CharAndBits CharAndBits[] array = newCharAndBitsArray(encoded); // prepare an array of char[] of the same length char[] digit = new char[encoded.length()]; // get the printable version of every char for(int i = 0; i < encoded.length(); i++) digit[i] = array[i].toAscii; // return a String out of it return new String(digit); } /** * Get the printable version of this char */ public char getPrintableChar() { return toAscii; } /** * Returns the int value of this char */ public int getIntValue() { return intValue; } /** * Getter for the binary char */ public char[] getBinaryChar() { return binaryChar; } /** * To test 2 EasyCharacter for equality */ public boolean equals(CharAndBits other) { return compareTo(other) == 0; } /** * Used to sort an array of EasyCharacter */ public int compareTo(CharAndBits other) { return intValue - other.intValue; } /** * A static method to get an array of EasyCharacter from a String * (so the caller does not have to perform the loop himself) */ public static CharAndBits[] newCharAndBitsArray(String str) { // if str is null return a 0 length array of EasyCharacter if(str == null) return new CharAndBits[0]; // convert String received as parameter as an array of char char[] digit = str.toCharArray(); CharAndBits[] array = new CharAndBits[digit.length]; for(int i = 0; i < digit.length; i++) array[i] = new CharAndBits(digit[i]); return array; } /** * A static method to get a printable String from an array of EasyCharacter[] * to display at the console or in a GUI */ public static String getMsgString(CharAndBits[] array) { StringBuilder sb = new StringBuilder(array.length); // copy our EasyCharacter into the buffer for(CharAndBits ea : array) sb.append(ea.toAscii); // printable version // return the StringBuilder as a String return sb.toString(); } /** * To perform the Xor between 2 arrays of EasyCharcter * and return an String with the 2 arrays XORed * The first parameter is the message, the second the key * The array returned will have the size of the message * if the key is smaller than the message a wrapAround will occur */ public static String xorArray(CharAndBits[] msg, CharAndBits[] key) { // check for null or no length message if(msg == null || msg.length == 0) return ""; // return String of 0 length // create the digit to hold the xored value int msgLen = msg.length; StringBuilder sb = new StringBuilder(msgLen); // check for null or empty key in that case just return a copy of our message if(key == null || key.length == 0) { for(CharAndBits ea : msg) sb.append(ea.theChar); return sb.toString(); } // get length of the key and create new array int keyLen = key.length; // loop to perform the XOR between each element of the msg array and the key with wrap around for(int i = 0; i < msgLen; i++) { int val = msg[i].intValue ^ key[i % keyLen].intValue; sb.append((char) val); } // convert the StringBuilder array to a String return sb.toString(); } /** * static methods that return a String representation in binary of an array of char */ public static String toBinaryString(String str) { return toBinaryString(str.toCharArray()); } public static String toBinaryString(char[] digit) { // use a StringBuilder to append the binary represenation StringBuilder sb = new StringBuilder(digit.length * 9); // * 9 for the blank space for(char c : digit) { CharAndBits ea = new CharAndBits(c); // append the 01010101010 sb.append(ea.toBinaryString()); sb.append(' '); } // return as a String return sb.toString(); } }
The console version of the encoding/decoding algorithm using XOR
import java.util.Scanner; /** * Secret Code VI * * If you have played with tutorial Secret Code V (SwapAndRotate) you will have seen as message can now * be considered as a big number instead of different digits or even symbols. * * Before going to the complete RSA encoding/decoding system let us play with more basic encoding/decoding * mechanisms using binary code. * * Ok so what's so special with characters on a computer ? It is because characters are represented by * a serie of bits. We will stay with plain Ascii for now for simplicity. * * Imagine the message "Hello". The Ascii representation of Hello in 8 bits bytes is * H e l l o * 01001000 01100101 01101100 01101100 01101111 * * In the Secret Code V tutorial we have seen how to swap and rotate bits in a message. * * Now most encryptions rely on the bitwise operator XOR property that says that * if b and c are bits fields * a = b XOR c * a XOR c gives back b and * a XOR b gives back c * The XOR operator in Java is ^ and can be applied to integer. * * Let us verify this assertion with all possible versions of 0 and 1 * Message: 1100 * Key: 1010 * ---- * XOR Msg and Key: 0110 this is the encrypted message * * Now let's XOR the encrypted message with the key * Encrypted message: 0110 * Key: 1010 * ---- * XOR 1100 back to the original message * * One big thing about this mechanism is that the process to encode is exactly the same * as the one to decode * we just XOR with the key both the message to encode and the message to decode. * No need for a Encode() method and a Decode() method. The same one is used * and the method does not need to know if it is actually encoding or decoding. * * In this tutorial we will just play with this XOR feature to encode/decode messages * For that we will use a key if the key is smaller than the message we just repeat it * So the encode "Hello world" with the key "Dave" we will use as key * "DaveDaveDav" * The following console application uses this technique * As in the previous tutorial we will used the CharAndBits class to output as a series of 0 and 1 * the bits contained in a character. * * If you have already the CharAndBits.java class/file from the previous tutotial, take this * new one, it has new functionnalities. The new version still support the code from * tutorial V so you can erase the old one, take the new one, and the code of SwapAndRotate will * still work. * */ public class Xor { // the key used for encript/decript private String key; /** * Constructor that receives the key as parameter */ public Xor(String key) { // call common method to set the initial key or change it setKey(key); } /** * Method to set the original key and permit to change it on the fly */ public void setKey(String key) { // avoid null key if(key == null) key = ""; // save it this.key = key; } /** * Method that encode/decode a message based on the registered key * Contrary to other coding mechanisms seen in the previous tutorials * the mechanism to encode and decode is the same wo we do not need * an encode and a decode method. The same method can be used for * both operations */ public String encodeDecode(String msg) { // validate that the message is not null or length == 0 // if it is the case, just return the original message if(msg == null || msg.length() == 0) return msg; // if the key is "" we return the original message if(key.length() == 0) return msg; // make an array of CharAndBits from both the message and the key CharAndBits[] m = CharAndBits.newCharAndBitsArray(msg); CharAndBits[] k = CharAndBits.newCharAndBitsArray(key); // and call the method that performs the XOR operation String encodeDecodeValue = CharAndBits.xorArray(m, k); return encodeDecodeValue; } /** * A quick and dirty method to return the key duplicated enough times * so it will have the length of the message. * This is just for printing purpose only both in the main() method and in the GUI. * The method is not involved in the encoding/decoding process itself */ public String dupKey(int msgLen) { // if the key is invalid no if(key.length() == 0) return ""; String dup = key; while(dup.length() < msgLen) dup += key; return dup.substring(0, msgLen); } /** * To test the class */ public static void main(String[] args) { //-------- unit tests to see that the whole thing works -------- String msg = "DreamInCode"; String key = "dave"; // create the Xor object Xor xor = new Xor(key); // for print out purpose only get the key used (it will be as log as the message) String dupKey = xor.dupKey(msg.length()); System.out.println("The original message is: \"" + msg + "\" the key used will be \"" + dupKey + "\""); // call the utility method for binary representation of the message String msgInBin = CharAndBits.toBinaryString(msg); System.out.println(msgInBin); // the repeated key in binary String keyInBin = CharAndBits.toBinaryString(dupKey); System.out.println(keyInBin); // build a series of ------- char[] dash = new char[msgInBin.length()]; for(int i = 0; i < msgInBin.length(); i++) dash[i] = '-'; // print the serie of ------ System.out.println(new String(dash)); // encode the message which is the result of the XOR String encoded = xor.encodeDecode(msg); // display the encoded bits String encodedInBinary = CharAndBits.toBinaryString(encoded); System.out.println(encodedInBinary); // display what is printable out of it System.out.println("The encrypted message is: \"" + CharAndBits.toAsciiString(encoded) + "\""); // now the reverse process System.out.println(); System.out.println("The encoded message XORed with the key"); System.out.println(encodedInBinary); // encoded message System.out.println(keyInBin); // key in binary System.out.println(new String(dash)); // the ------------ // decode the encoded message calling the SAME method String decoded = xor.encodeDecode(encoded); // display the decoded message System.out.println(CharAndBits.toBinaryString(decoded)); System.out.println("The decoded message is \"" + decoded + "\" is it the same as \"" + msg + "\": " + msg.equals(decoded)); // ----------------------------- end of unit tests --------------------------------- // Now prompting the user Scanner scan = new Scanner(System.in); String userKey; // get a key of length > 0 from the user System.out.println(); do { System.out.print("Enter the key to use: "); userKey = scan.nextLine(); } while(userKey.length() == 0); // build the Xor object Xor userXor = new Xor(userKey); // get the message to encode/decode System.out.print("Enter message to encode: "); String userMsg = scan.nextLine(); // generate the key that will be used for print out purpose only String userDupKey = userXor.dupKey(userMsg.length()); System.out.println("The original message is: \"" + userMsg + "\" the key used will be \"" + userDupKey + "\""); // call the utility method for binary representation of the message String userMsgInBin = CharAndBits.toBinaryString(userMsg); System.out.println(userMsgInBin); // the repeated key in binary String userKeyInBin = CharAndBits.toBinaryString(userDupKey); System.out.println(userKeyInBin); // build a series of ------- char[] userDash = new char[userMsgInBin.length()]; for(int i = 0; i < userMsgInBin.length(); i++) userDash[i] = '-'; // print the serie of ------ System.out.println(new String(userDash)); // encode the message which is the result of the XOR String userEncoded = userXor.encodeDecode(userMsg); // display the encoded bits String userEncodedInBinary = CharAndBits.toBinaryString(userEncoded); System.out.println(userEncodedInBinary); // display what is printable out of it System.out.println("The encrypted message is: \"" + CharAndBits.toAsciiString(userEncoded) + "\""); // now the reverse process System.out.println(); System.out.println("The encoded message XORed with the key"); System.out.println(userEncodedInBinary); // encoded message System.out.println(userKeyInBin); // key in binary System.out.println(new String(userDash)); // the ------------ // decode the encoded message calling the SAME method String userDecoded = userXor.encodeDecode(userEncoded); // display the decoded message System.out.println(CharAndBits.toBinaryString(userDecoded)); System.out.println("The decoded message is \"" + userDecoded + "\" is it the same as \"" + userMsg + "\": " + userMsg.equals(userDecoded)); } }
And now the GUI XorGui.java
This GUI is also a good example on how to use JTable with variable number of columns
import java.awt.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.table.*; /** * A GUI that use the Xor class to encode/decode messages * In this GUI we cannot prompt for a String to decode as XOring bits * in byte may generate characters that may (most probably) not be displayable * * The GUI display the binary form of each letter in the message * their coded representaion after the XOR operation * This GUI is greatly inspired from the SwapAndRotateGui of Secret Code V tutorial * * Note that if a letter of the message and the corresponding letter of the key are equal * the encrypted letter is 00000000 * If message is ABC and key 123 the encrypted versions should be the same :-) */ public class XorGui extends JFrame { private static final long serialVersionUID = 1L; // the Xor class to encode/decode private Xor xor; // the key to use private JTextField keyText; // the letter of the key for every letter in the key private char[] longKey; // The message to encode private JTextField clearTextIn; // the original message private char[] msgChar = new char[0]; // the encoded messages private char[] msgEncoded; // the decoded messages that should be the same as msgChar private char[] msgDecoded; // The JTable shown in the CENTER region private JTable table; private MyModel myModel; private TableColumnModel colModel; // Its panel private JPanel centerPanel; // the first column of the jTable private static final String[] firstCol = {"Message", "Msg Bin", "Key Bin", "XOR Encoded", "Encoded Ascii", "Key Bin", "XOR Decoded", "Decoded Ascii"}; // mnemonic for more descriptive values in the AbstractModel private static final int ORIG = 0, ORIG_BIN = 1, KEY1_BIN = 2, CRYPTED_BIN = 3, CRYPTED = 4, KEY2_BIN = 5, DECRYPTED_BIN = 6, DECRYPTED = 7; /** * Constructor */ XorGui() { super("XOR encoding/decoding"); // we will use a BorderLayout to store our GUI component setLayout(new BorderLayout()); // the Xor object init the key to "" xor = new Xor(""); longKey = xor.dupKey(0).toCharArray(); // the Listener the key changes DocumentListener dc = new KeyListener(); // The NORTH region will contain a JPanel where the key and the message can be entererd JPanel north = new JPanel(new GridLayout(5, 1, 2, 2)); // the key north.add(createCenteredLabel("The Key")); keyText = new JTextField(50); keyText.getDocument().addDocumentListener(dc); north.add(keyText); // the message north.add(createCenteredLabel("Enter the message to encode here")); clearTextIn = new JTextField(50); clearTextIn.getDocument().addDocumentListener(new ClearListener()); north.add(clearTextIn); // a gap north.add(new JLabel(" ")); // add this panel to the top of the screen add(north, BorderLayout.NORTH); // in the CENTER region of the frame we will put a JTable containing all the // encrypted/decripted bits myModel = new MyModel(); table = new JTable(myModel); table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); colModel = table.getColumnModel(); centerPanel = new JPanel(new BorderLayout()); centerPanel.add(new JScrollPane(table), BorderLayout.CENTER); add(centerPanel, BorderLayout.CENTER); // standard operation to show the JFrame this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); setBounds(30, 30, 700, 320); setVisible(true); // to set the size of column 1 updateStringToEncode(); } /** * A method to create a JLabel with foreground color Blue and with text centered */ private JLabel createCenteredLabel(String text) { JLabel label = new JLabel(text); label.setHorizontalAlignment(SwingConstants.CENTER); label.setForeground(Color.BLUE); return label; } /** * The key has changed */ private void updateKeyString() { // update key in the Xor object from the key JTextField xor.setKey(keyText.getText()); // update the encoding process updateStringToEncode(); } /** * To update the string to be coded */ private void updateStringToEncode() { // get the text of the message to encode from the JTextField String line = clearTextIn.getText(); // make the char[] array out of it to be displayed in the JTable msgChar = line.toCharArray(); // generate (for display purpose only) the key that will be used for every character longKey = xor.dupKey(line.length()).toCharArray(); // build the encrypted message String encoded = xor.encodeDecode(line); // in a char[] for display purpose msgEncoded = encoded.toCharArray(); // build the decrypted char[] msgDecoded = xor.encodeDecode(encoded).toCharArray(); // inform the model that the table contain changed myModel.fireTableStructureChanged(); myModel.fireTableDataChanged(); // set the size of the column when a new column is added lets set it's size int actualColumnCount = msgChar.length + 1; for(int i = 0; i < actualColumnCount; i++) { TableColumn tc = colModel.getColumn(i); tc.setPreferredWidth(100); tc.setMinWidth(100); } } /** * To start the GUI */ public static void main(String[] args) { new XorGui(); } /** * A listener to be informed whenever the JTextField of the clear text is changed */ private class ClearListener implements DocumentListener { @Override public void changedUpdate(DocumentEvent arg0) { updateStringToEncode(); } @Override public void insertUpdate(DocumentEvent arg0) { updateStringToEncode(); } @Override public void removeUpdate(DocumentEvent arg0) { updateStringToEncode(); } } /** * A listener to be informed whenever the JTextField of the SWAP or ROTATE key is changed */ private class KeyListener implements DocumentListener { @Override public void changedUpdate(DocumentEvent arg0) { updateKeyString(); } @Override public void insertUpdate(DocumentEvent arg0) { updateKeyString(); } @Override public void removeUpdate(DocumentEvent arg0) { updateKeyString(); } } /** * A class that extends AbstractTableModel to povide the binary representation * of every cell of the JTable in the center panel */ private class MyModel extends AbstractTableModel { private static final long serialVersionUID = 1L; // the number of columns is the length of the message + the first column public int getColumnCount() { return msgChar.length + 1; } // name of each colum (first one is empty) public String getColumnName(int column) { // if column 0 we return the hardcode "Steps" if(column == 0) return "Steps"; // skip first column --column; // verify that we have data for this column if(column >= msgChar.length || column >= longKey.length) return ""; // OK generate its title which is the letter of the key used return "#" + (column+1) + " Key: \"" + longKey[column] + "\""; } // return the row count public int getRowCount() { // it is the length of our first column return firstCol.length; } // the JTable want's to know what to print there public Object getValueAt(int row, int col) { // for the first column we just return the header if(col == 0) return firstCol[row]; --col; // validate first the col it should be contained in the mesage if(col >= msgChar.length) return ""; // the CharAndBits to display init to null we will check it later CharAndBits cab = null; // depending of the column display the correct text field switch(row) { // the original message we juts return the character of that row case ORIG: return " >" + msgChar[col] + "<"; // the binary stransaltion of that letter case ORIG_BIN: cab = new CharAndBits(msgChar[col]); break; // the binary representation of the key (same one at both row) case KEY1_BIN: case KEY2_BIN: if(col >= longKey.length) return ""; cab = new CharAndBits(longKey[col]); break; // the binary representation of the encrypted letter of the message case CRYPTED_BIN: cab = new CharAndBits(msgEncoded[col]); break; // the ASCII representation (if it exists) of this letter of the message case CRYPTED: CharAndBits c = new CharAndBits(msgEncoded[col]); if(c.isPrintable()) return " >" + c.getPrintableChar() + "<"; else return " -"; // the decrypted message back should be the same of ORIG_BIN case DECRYPTED_BIN: if(col >= msgDecoded.length) break; cab = new CharAndBits(msgDecoded[col]); break; // the letter of the message back into Ascii case DECRYPTED: return " >" + msgDecoded[col] + "<"; } // common formatting the binary string followed by the printable version of the char // unless not defined yet due to the way the GUI refresh the JTable if(cab == null) return ""; return cab.toBinaryString(); } } }
Have fun