From 7987df7635cf8ae6c2b28540b094001f1abcd0a7 Mon Sep 17 00:00:00 2001 From: Mattrixwv Date: Tue, 4 Jan 2022 22:47:28 -0500 Subject: [PATCH] Added Polybius Square implementation --- .../CipherStreamJava/PolybiusSquare.java | 346 ++++++++++++++++++ .../CipherStreamJava/TestPolybiusSquare.java | 320 ++++++++++++++++ 2 files changed, 666 insertions(+) create mode 100644 src/main/java/mattrixwv/CipherStreamJava/PolybiusSquare.java create mode 100644 src/test/java/mattrixwv/CipherStreamJava/TestPolybiusSquare.java diff --git a/src/main/java/mattrixwv/CipherStreamJava/PolybiusSquare.java b/src/main/java/mattrixwv/CipherStreamJava/PolybiusSquare.java new file mode 100644 index 0000000..6dd6d3f --- /dev/null +++ b/src/main/java/mattrixwv/CipherStreamJava/PolybiusSquare.java @@ -0,0 +1,346 @@ +//CipherStreamJava/src/main/java/mattrixwv/CipherStreamJava/PolybiusSquare.java +//Mattrixwv +// Created: 01-04-21 +//Modified: 01-04-21 +package mattrixwv.CipherStreamJava; + +import java.util.StringJoiner; + +public class PolybiusSquare{ + //An exception to indicate a bad number was given in the string + //TODO: Move this to a stand alone class + public class InvalidCharacterException extends Exception{ + public InvalidCharacterException(){ + super(); + } + public InvalidCharacterException(String message){ + super(message); + } + public InvalidCharacterException(Throwable error){ + super(error); + } + public InvalidCharacterException(String message, Throwable error){ + super(message, error); + } + } + //A class representing the location of a character in the grid + private class CharLocation{ + private int x; + private int y; + public CharLocation(int x, int y){ + this.x = x; + this.y = y; + } + public int getX(){ + return x; + } + public int getY(){ + return y; + } + } + + //Variables + private String inputString; //The message that needs to be encoded/decoded + private String outputString; //The encoded/decoded message + private String keyword; //The keyword used to create the grid + private char[][] grid; //The grid used to encode/decode the message + private char replaced; //The letter that will need to be replaced in the grid and any input string or keyword + private char replacer; //The letter that replaces replaced in the input string or keyword + private boolean leaveWhitespace; //Whether to respect whitespace in the output string + private boolean leaveSymbols; //Whether to respect symbols in the output string + + //Create the grid from the keyword + public void createGrid(){ + for(int row = 0;row < 5;++row){ + for(int col = 0;col < 5;++col){ + char letter = keyword.charAt((5 * row) + col); + grid[row][col] = letter; + } + } + } + //Strips invalid characters from the string that needs encoded/decoded + public void setInputStringEncoding(String inputString) throws InvalidCharacterException{ + //Make sure the string doesn't contain any numbers + for(char ch = '0';ch <= '9';++ch){ + if(inputString.contains(Character.toString(ch))){ + throw new InvalidCharacterException("Inputs for encoding cannot contain numbers"); + } + } + + //Change to upper case + inputString = inputString.toUpperCase(); + + //Remove any whitespace if selected + if(!leaveWhitespace){ + inputString = inputString.replaceAll("\\s+", ""); + } + + //Remove any symbols if selected + if(!leaveSymbols){ + inputString = inputString.replaceAll("[^a-zA-Z0-9\\s]", ""); + } + + if(!leaveWhitespace && !leaveSymbols){ + //Add whitespace after every character for the default look + StringJoiner spacedString = new StringJoiner(" "); + for(int cnt = 0;cnt < inputString.length();++cnt){ + spacedString.add(Character.toString(inputString.charAt(cnt))); + } + inputString = spacedString.toString(); + } + + //Replace any characters that need replaced + inputString = inputString.replaceAll(Character.toString(replaced), Character.toString(replacer)); + + //Save the string + this.inputString = inputString; + } + public void setInputStringDecoding(String inputString) throws InvalidCharacterException{ + //Make sure the string contains an even number of digits and no letters + int numberOfDigits = 0; + for(int cnt = 0;cnt < inputString.length();++cnt){ + char ch = inputString.charAt(cnt); + if(Character.isDigit(ch)){ + ++numberOfDigits; + } + else if(Character.isAlphabetic(ch)){ + throw new InvalidCharacterException("Inputs for decoding cannot contains letters"); + } + } + if((numberOfDigits % 2) != 0){ + throw new InvalidCharacterException("There must be an even number of digits in an encoded string"); + } + + //Remove any whitespace if selected + if(!leaveWhitespace){ + inputString = inputString.replaceAll("\\s+", ""); + } + + //Remove any symbols if selected + if(!leaveSymbols){ + inputString = inputString.replaceAll("[^0-9\\s]", ""); + } + + //Save the string + this.inputString = inputString; + } + //Returns the input string ready for encoding + public String getPreparedInputStringEncoding(){ + String cleanString = inputString.toUpperCase(); + cleanString = cleanString.replaceAll("[^A-Z]", ""); + return cleanString; + } + public String getPreparedInputStringDecoding(){ + String cleanString = inputString.replaceAll("[^0-9]", ""); + return cleanString; + } + //Strips invalid characters from the keyword and creates the grid + public void setKeyword(String key){ + //Change everything to uppercase + key = key.toUpperCase(); + + //Remove everything except capital letters + key = key.replaceAll("[^A-Z]", ""); + + //Add all letters in the alphabet to the key + key += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + //Replace all replaced characters + key = key.replaceAll(Character.toString(replaced), Character.toString(replacer)); + + //Remove all duplicate characters + StringBuilder uniqueKey = new StringBuilder(); + key.chars().distinct().forEach(c -> uniqueKey.append((char)c)); + keyword = uniqueKey.toString(); + + //Create the grid from the sanitized keyword + createGrid(); + } + //Returns the location of the given charcter in the grid + public CharLocation findChar(char letter) throws Exception{ + for(int row = 0;row < grid.length;++row){ + for(int col = 0;col < grid[row].length;++col){ + if(grid[row][col] == letter){ + return new CharLocation(row, col); + } + } + } + //If it was not found something went wrong + throw new Exception("That character was not found in the grid. ERROR"); + } + //Adds characters that aren't letters to the output + public void addCharactersToCleanStringEncode(String cleanString){ + int outputCnt = 0; + StringBuilder fullOutput = new StringBuilder(); + for(int inputCnt = 0;inputCnt < inputString.length();++inputCnt){ + //Add both numbers of any letters to the output + if(Character.isAlphabetic(inputString.charAt(inputCnt))){ + fullOutput.append(cleanString.charAt(outputCnt++)); + fullOutput.append(cleanString.charAt(outputCnt++)); + } + //Add any other characters that appear to the output + else{ + fullOutput.append(inputString.charAt(inputCnt)); + } + } + outputString = fullOutput.toString(); + } + public void addCharactersToCleanStringDecode(String cleanString){ + int outputCnt = 0; + StringBuilder fullOutput = new StringBuilder(); + for(int inputCnt = 0;inputCnt < inputString.length();++inputCnt){ + //Add the letter to the output and skip the second number + if(Character.isDigit(inputString.charAt(inputCnt))){ + fullOutput.append(cleanString.charAt(outputCnt++)); + ++inputCnt; + } + //Add any other characters that appear to the output + else{ + fullOutput.append(inputString.charAt(inputCnt)); + } + } + outputString = fullOutput.toString(); + } + //Encodes inputString using the Playfair cipher and stores the result in outputString + private String encode() throws Exception{ + StringBuilder output = new StringBuilder(); + String cleanString = getPreparedInputStringEncoding(); + for(int cnt = 0;cnt < cleanString.length();++cnt){ + //Get the next character to be encoded + char ch = cleanString.charAt(cnt); + + //Find the letter in the grid + CharLocation location = findChar(ch); + + //Add the grid location to the output + output.append(location.getX() + 1); + output.append(location.getY() + 1); + } + + //Add other characters to the output string + addCharactersToCleanStringEncode(output.toString()); + + //Return the output string + return outputString; + } + //Decodes inputString using the Playfair cipher and stores the result in outputString + private String decode(){ + StringBuilder output = new StringBuilder(); + String cleanString = getPreparedInputStringDecoding(); + for(int cnt = 0;cnt < cleanString.length();){ + //Get the digits indicationg the location of the next character + char firstDigit = cleanString.charAt(cnt++); + char secondDigit = cleanString.charAt(cnt++); + + //Get the next character + char letter = grid[Integer.valueOf(Character.toString(firstDigit)) - 1][Integer.valueOf(Character.toString(secondDigit)) - 1]; + + //Add the new letter to the output + output.append(letter); + } + + //Add other characters to the output + addCharactersToCleanStringDecode(output.toString()); + + //Return the output string + return outputString; + } + + public PolybiusSquare() throws InvalidCharacterException{ + reset(); + setReplaced('J'); + setReplacer('I'); + leaveWhitespace = false; + leaveSymbols = false; + } + public PolybiusSquare(boolean leaveWhitespace, boolean leaveSymbols) throws InvalidCharacterException{ + reset(); + setReplaced('J'); + setReplacer('I'); + this.leaveWhitespace = leaveWhitespace; + this.leaveSymbols = leaveSymbols; + } + public PolybiusSquare(boolean leaveWhitespace, boolean leaveSymbols, char replaced, char replacer) throws InvalidCharacterException{ + reset(); + setReplaced(replaced); + setReplacer(replacer); + this.leaveWhitespace = leaveWhitespace; + this.leaveSymbols = leaveSymbols; + } + //Sets the keyword and inputString and encodes the message + public String encode(String inputString) throws InvalidCharacterException, Exception{ + return encode("", inputString); + } + public String encode(String keyword, String inputString) throws InvalidCharacterException, Exception{ + reset(); + setKeyword(keyword); + setInputStringEncoding(inputString); + return encode(); + } + //Sets the keyword and inputString and decodes the message + public String decode(String inputString) throws InvalidCharacterException{ + return decode("", inputString); + } + public String decode(String keyword, String inputString) throws InvalidCharacterException{ + reset(); + setKeyword(keyword); + setInputStringDecoding(inputString); + return decode(); + } + + //Makes sure all variables are empty + public void reset(){ + grid = new char[5][5]; + inputString = ""; + outputString = ""; + keyword = ""; + } + //Gets + public char getReplaced(){ + return replaced; + } + public void setReplaced(char replaced) throws InvalidCharacterException{ + if(!Character.isAlphabetic(replaced)){ + throw new InvalidCharacterException("The replaced character must be a letter"); + } + + if(replaced == replacer){ + throw new InvalidCharacterException("The replaced letter cannot be the same as the replacing letter"); + } + + this.replaced = Character.toUpperCase(replaced); + } + public char getReplacer(){ + return replacer; + } + public void setReplacer(char replacer) throws InvalidCharacterException{ + if(!Character.isAlphabetic(replacer)){ + throw new InvalidCharacterException("The replacer character must be a letter"); + } + + if(replaced == replacer){ + throw new InvalidCharacterException("The replacer letter cannot be the same as the replaced letter"); + } + + this.replacer = replacer; + } + public String getKeyword(){ + return keyword; + } + public String getInputString(){ + return inputString; + } + public String getOutputString(){ + return outputString; + } + public String getGrid(){ + StringBuilder gridString = new StringBuilder(); + for(char[] row : grid){ + for(char col : row){ + gridString.append(col); + } + } + + return gridString.toString(); + } +} diff --git a/src/test/java/mattrixwv/CipherStreamJava/TestPolybiusSquare.java b/src/test/java/mattrixwv/CipherStreamJava/TestPolybiusSquare.java new file mode 100644 index 0000000..c37aef0 --- /dev/null +++ b/src/test/java/mattrixwv/CipherStreamJava/TestPolybiusSquare.java @@ -0,0 +1,320 @@ +//CipherStreamJava/src/test/java/mattrixwv/CipherStreamJava/TestPolybiusSquare.java +//Mattrixwv +// Created: 01-04-21 +//Modified: 01-04-21 +package mattrixwv.CipherStreamJava; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import mattrixwv.CipherStreamJava.PolybiusSquare.InvalidCharacterException; + + +public class TestPolybiusSquare{ + @Test + public void testDecode() throws InvalidCharacterException{ + PolybiusSquare cipher = new PolybiusSquare(true, true); + + //Test simple decoding + String inputString = "121144"; + String keyword = ""; + String correctOutput = "BAT"; + String output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed simple decoding.", correctOutput, output); + + //Test whitespace decoding + inputString = "12 11 44"; + keyword = ""; + correctOutput = "B A T"; + output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed whitespace decoding.", correctOutput, output); + + //Test symbol decoding + inputString = "12@11+44-"; + keyword = ""; + correctOutput = "B@A+T-"; + output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed symbol decoding.", correctOutput, output); + + //Test whitespace, symbol decoding + inputString = "12 11-44"; + keyword = ""; + correctOutput = "B A-T"; + output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed whitespace, symbol decoding.", correctOutput, output); + //Test whitespace, symbol decoding with mangled keyword + inputString = "15 14-52"; + keyword = "Z Y+ X-"; + correctOutput = "B A-T"; + output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed whitespace, symbol decoding with mangled keyword.", correctOutput, output); + } + @Test + public void testNoWhitespaceDecode() throws InvalidCharacterException{ + PolybiusSquare cipher = new PolybiusSquare(false, true); + + //Test simple decoding + String inputString = "121144"; + String keyword = ""; + String correctOutput = "BAT"; + String output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed no whitespace simple decoding.", correctOutput, output); + + //Test whitespace decoding + inputString = "12 11 44"; + keyword = ""; + correctOutput = "BAT"; + output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed no whitespace whitespace decoding.", correctOutput, output); + + //Test symbol decoding + inputString = "12@11+44-"; + keyword = ""; + correctOutput = "B@A+T-"; + output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed no whitespace symbol decoding.", correctOutput, output); + + //Test whitespace, symbol decoding + inputString = "12 11-44"; + keyword = ""; + correctOutput = "BA-T"; + output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed no whitespace whitespace, symbol decoding.", correctOutput, output); + //Test whitespace, symbol decoding with mangled keyword + inputString = "15 14-52"; + keyword = "Z Y+ X-"; + correctOutput = "BA-T"; + output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed no whitespace whitespace, symbol decoding with mangled keyword.", correctOutput, output); + } + @Test + public void testNoSymbolDeocde() throws InvalidCharacterException{ + PolybiusSquare cipher = new PolybiusSquare(true, false); + + //Test simple decoding + String inputString = "121144"; + String keyword = ""; + String correctOutput = "BAT"; + String output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed no symbol simple decoding.", correctOutput, output); + + //Test whitespace decoding + inputString = "12 11 44"; + keyword = ""; + correctOutput = "B A T"; + output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed no symbol whitespace decoding.", correctOutput, output); + + //Test symbol decoding + inputString = "12@11+44-"; + keyword = ""; + correctOutput = "BAT"; + output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed no symbol symbol decoding.", correctOutput, output); + + //Test whitespace, symbol decoding + inputString = "12 11-44"; + keyword = ""; + correctOutput = "B AT"; + output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed no symbol whitespace, symbol decoding.", correctOutput, output); + //Test whitespace, symbol decoding with mangled keyword + inputString = "15 14-52"; + keyword = "Z Y+ X-"; + correctOutput = "B AT"; + output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed no symbol whitespace, symbol decoding with mangled keyword.", correctOutput, output); + } + @Test + public void testNoWhitespaceSymbolDecode() throws InvalidCharacterException{ + PolybiusSquare cipher = new PolybiusSquare(false, false); + + //Test simple decoding + String inputString = "121144"; + String keyword = ""; + String correctOutput = "BAT"; + String output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed secure simple decoding.", correctOutput, output); + + //Test whitespace decoding + inputString = "12 11 44"; + keyword = ""; + correctOutput = "BAT"; + output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed secure whitespace decoding.", correctOutput, output); + + //Test symbol decoding + inputString = "12@11+44-"; + keyword = ""; + correctOutput = "BAT"; + output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed secure symbol decoding.", correctOutput, output); + + //Test whitespace, symbol decoding + inputString = "12 11-44"; + keyword = ""; + correctOutput = "BAT"; + output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed secure whitespace, symbol decoding.", correctOutput, output); + //Test whitespace, symbol decoding with mangled keyword + inputString = "15 14-52"; + keyword = "Z Y+ X-"; + correctOutput = "BAT"; + output = cipher.decode(keyword, inputString); + assertEquals("PolybiusSquare failed secure whitespace, symbol decoding with mangled keyword.", correctOutput, output); + } + @Test + public void testEncode() throws InvalidCharacterException, Exception{ + PolybiusSquare cipher = new PolybiusSquare(true, true); + + //Test simple encoding + String inputString = "BAT"; + String keyword = ""; + String correctOutput = "121144"; + String output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed simple encoding.", correctOutput, output); + + //Test whitespace encoding + inputString = "B A T"; + keyword = ""; + correctOutput = "12 11 44"; + output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed whitespace encoding.", correctOutput, output); + + //Test symbol encoding + inputString = "B@A+T-"; + keyword = ""; + correctOutput = "12@11+44-"; + output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed symbol encoding.", correctOutput, output); + + //Test whitespace, symbol decoding + inputString = "B A-T"; + keyword = ""; + correctOutput = "12 11-44"; + output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed whitespace, symbol encoding.", correctOutput, output); + //Test whitespace, symbol decoding with mangled keyword + inputString = "B A-T"; + keyword = "Z Y+ X-"; + correctOutput = "15 14-52"; + output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed whitespace, symbol encoding with mangled keyword.", correctOutput, output); + } + @Test + public void testNoWhitespaceEncode() throws InvalidCharacterException, Exception{ + PolybiusSquare cipher = new PolybiusSquare(false, true); + + //Test simple encoding + String inputString = "BAT"; + String keyword = ""; + String correctOutput = "121144"; + String output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed no whitespace simple encoding.", correctOutput, output); + + //Test whitespace encoding + inputString = "B A T"; + keyword = ""; + correctOutput = "121144"; + output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed no whitespace whitespace encoding.", correctOutput, output); + + //Test symbol encoding + inputString = "B@A+T-"; + keyword = ""; + correctOutput = "12@11+44-"; + output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed no whitespace symbol encoding.", correctOutput, output); + + //Test whitespace, symbol decoding + inputString = "B A-T"; + keyword = ""; + correctOutput = "1211-44"; + output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed no whitespace whitespace, symbol encoding.", correctOutput, output); + //Test whitespace, symbol decoding with mangled keyword + inputString = "B A-T"; + keyword = "Z Y+ X-"; + correctOutput = "1514-52"; + output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed no whitespace whitespace, symbol encoding with mangled keyword.", correctOutput, output); + } + @Test + public void testNoSymbolEncode() throws InvalidCharacterException, Exception{ + PolybiusSquare cipher = new PolybiusSquare(true, false); + + //Test simple encoding + String inputString = "BAT"; + String keyword = ""; + String correctOutput = "121144"; + String output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed no symbol simple encoding.", correctOutput, output); + + //Test whitespace encoding + inputString = "B A T"; + keyword = ""; + correctOutput = "12 11 44"; + output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed no symbol whitespace encoding.", correctOutput, output); + + //Test symbol encoding + inputString = "B@A+T-"; + keyword = ""; + correctOutput = "121144"; + output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed no symbol symbol encoding.", correctOutput, output); + + //Test whitespace, symbol decoding + inputString = "B A-T"; + keyword = ""; + correctOutput = "12 1144"; + output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed whitespace, symbol encoding.", correctOutput, output); + //Test whitespace, symbol decoding with mangled keyword + inputString = "B A-T"; + keyword = "Z Y+ X-"; + correctOutput = "15 1452"; + output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed no symbol whitespace, symbol encoding with mangled keyword.", correctOutput, output); + } + @Test + public void testNoWhitespaceSymbolEncode() throws InvalidCharacterException, Exception{ + PolybiusSquare cipher = new PolybiusSquare(false, false); + + //Test simple encoding + String inputString = "BAT"; + String keyword = ""; + String correctOutput = "12 11 44"; + String output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed secure simple encoding.", correctOutput, output); + + //Test whitespace encoding + inputString = "B A T"; + keyword = ""; + correctOutput = "12 11 44"; + assertEquals("PolybiusSquare failed secure whitespace encoding.", correctOutput, output); + output = cipher.encode(keyword, inputString); + + //Test symbol encoding + inputString = "B@A+T-"; + keyword = ""; + correctOutput = "12 11 44"; + output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed secure symbol encoding.", correctOutput, output); + + //Test whitespace, symbol decoding + inputString = "B A-T"; + keyword = ""; + correctOutput = "12 11 44"; + output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed secure whitespace, symbol encoding.", correctOutput, output); + //Test whitespace, symbol decoding with mangled keyword + inputString = "B A-T"; + keyword = "Z Y+ X-"; + correctOutput = "15 14 52"; + output = cipher.encode(keyword, inputString); + assertEquals("PolybiusSquare failed secure whitespace, symbol encoding with mangled keyword.", correctOutput, output); + } +}