//CipherStreamJava/src/main/java/com/mattrixwv/CipherStreamJava/polySubstitution/Hill.java //Mattrixwv // Created: 01-31-22 //Modified: 04-27-23 package com.mattrixwv.cipherstream.polysubstitution; import java.util.ArrayList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mattrixwv.cipherstream.exceptions.InvalidCharacterException; import com.mattrixwv.cipherstream.exceptions.InvalidInputException; import com.mattrixwv.cipherstream.exceptions.InvalidKeyException; import com.mattrixwv.matrix.ModMatrix; import com.mattrixwv.matrix.exceptions.InvalidGeometryException; import com.mattrixwv.matrix.exceptions.InvalidScalarException; public class Hill{ protected static Logger logger = LoggerFactory.getLogger(Hill.class); protected boolean preserveCapitals; protected boolean preserveWhitespace; protected boolean preserveSymbols; protected String inputString; protected String outputString; protected char characterToAdd; protected ModMatrix key; protected void setKey(ModMatrix key) throws InvalidKeyException{ logger.debug("Setting key"); //Make sure the mod is correct logger.debug("Testing mod"); if(key.getMod() != 26){ throw new InvalidKeyException("This algorithm uses the english alphabet, so the mod for the key must be 26"); } //Make sure the matrix is square logger.debug("Testing square"); if(!key.isSquare()){ throw new InvalidKeyException("The key must be a square matrix"); } //Make sure the matrix is invertable logger.debug("Testing invertable"); try{ key.inverse(); } catch(InvalidGeometryException | InvalidScalarException error){ throw new InvalidKeyException("The key does not have an inverse mod 26"); } //Set the key logger.debug("key = {}", key); this.key = new ModMatrix(key); } protected void setInputStringEncode(String inputString) throws InvalidInputException{ logger.debug("Setting input string for encoding"); if(inputString == null){ throw new InvalidInputException("Input must not be null"); } logger.debug("Original input string '{}'", inputString); //Remove anything that needs removed if(!preserveCapitals){ logger.debug("Removing capitals"); inputString = inputString.toUpperCase(); } if(!preserveWhitespace){ logger.debug("Removing whitespace"); inputString = inputString.replaceAll("[\\s]", ""); } if(!preserveSymbols){ logger.debug("Removing symbols"); inputString = inputString.replaceAll("[^a-zA-Z\\s]", ""); } //Make sure the input is correct length logger.debug("Checking length"); this.inputString = inputString; int cleanLength = getCleanInputString().length(); int charsToAdd = (cleanLength % key.getNumRows()); StringBuilder inputStringBuilder = new StringBuilder(); inputStringBuilder.append(inputString); if(charsToAdd != 0){ charsToAdd = key.getNumRows() - charsToAdd; } logger.debug("Adding {} characters", charsToAdd); for(int cnt = 0;cnt < charsToAdd;++cnt){ inputStringBuilder.append(characterToAdd); } inputString = inputStringBuilder.toString(); logger.debug("Cleaned input string '{}'", inputString); this.inputString = inputString; //Make sure the input isn't blank if(this.inputString.isBlank() || getCleanInputString().isBlank()){ throw new InvalidInputException("Input cannot be blank"); } } protected void setInputStringDecode(String inputString) throws InvalidInputException{ logger.debug("Setting input string for decoding"); if(inputString == null){ throw new InvalidInputException("Input must not be null"); } logger.debug("Original input string '{}'", inputString); //Remove anything that needs removed if(!preserveCapitals){ logger.debug("Removing capitals"); inputString = inputString.toUpperCase(); } if(!preserveWhitespace){ logger.debug("Removing whitespace"); inputString = inputString.replaceAll("[\\s]", ""); } if(!preserveSymbols){ logger.debug("Removing symbols"); inputString = inputString.replaceAll("[^a-zA-Z\\s]", ""); } logger.debug("Cleaned input string '{}'", inputString); this.inputString = inputString; logger.debug("Checking length"); if(getCleanInputString().isBlank() || ((getCleanInputString().length() % key.getNumRows()) != 0)){ throw new InvalidInputException("Length of input string must be a multiple of the number of rows in the key"); } } protected String getCleanInputString(){ logger.debug("Cleaning inputString"); String cleanInputString = inputString.toUpperCase().replaceAll("[^A-Z]", ""); logger.debug("Clean input string '{}'", cleanInputString); return cleanInputString; } protected void setCharacterToAdd(char characterToAdd) throws InvalidCharacterException{ logger.debug("Setting character to add {}", characterToAdd); //Make sure the character is a letter if(!Character.isAlphabetic(characterToAdd)){ throw new InvalidCharacterException("Character to add must be a letter"); } //Save the characterToAdd if(!preserveCapitals){ logger.debug("Removing capitals"); characterToAdd = Character.toUpperCase(characterToAdd); } logger.debug("Cleaned character {}", characterToAdd); this.characterToAdd = characterToAdd; } protected String polishOutputString(){ logger.debug("Polishing output string"); //Add the extra characters back to the output and remove the added characters int outputCnt = 0; StringBuilder outputBuilder = new StringBuilder(); for(char ch : inputString.toCharArray()){ logger.debug("Current char {}", ch); if(Character.isUpperCase(ch)){ logger.debug("Uppercase"); outputBuilder.append(Character.toUpperCase(outputString.charAt(outputCnt++))); } else if(Character.isLowerCase(ch)){ logger.debug("Lowercase"); outputBuilder.append(Character.toLowerCase(outputString.charAt(outputCnt++))); } else{ logger.debug("Symbol"); outputBuilder.append(ch); } } String cleanString = outputBuilder.toString(); logger.debug("Polished string '{}'", cleanString); return cleanString; } protected ArrayList getInputVectors(){ logger.debug("Generating input vectors"); //Get the number of columns in the key int numCols = key.getNumCols(); //Get a clean inputString String cleanInput = getCleanInputString(); //Break the inputString up into lengths of numCols ArrayList vectors = new ArrayList<>(); for(int cnt = 0;cnt < cleanInput.length();cnt += numCols){ String subString = cleanInput.substring(cnt, cnt + numCols); int[] grid = new int[numCols]; logger.debug("Current substring '{}'", subString); //Subtract 65 from each character so that A=0, B=1, ... for(int subCnt = 0;subCnt < subString.length();++subCnt){ grid[subCnt] = subString.charAt(subCnt) - 65; } //Create a vector from the new values ModMatrix vector = new ModMatrix(26); vector.addCol(grid); logger.debug("Current vector {}", vector); //Add the vector to the array vectors.add(vector); } //Return the array of vectors return vectors; } protected String getOutputFromVectors(ArrayList outputVectors){ logger.debug("Turning vectors into a string"); //Go through each element in the vector StringBuilder outputBuilder = new StringBuilder(); for(ModMatrix vector : outputVectors){ logger.debug("Current vector {}", vector); //Add 65 to each element and add it to the string for(int cnt = 0;cnt < vector.getNumRows();++cnt){ outputBuilder.append((char)(vector.get(cnt, 0) + 65)); } } //Return the new string String convertedString = outputBuilder.toString(); logger.debug("Converted string '{}'", convertedString); return convertedString; } protected void encode(){ logger.debug("Encoding"); //Get an array of vectors that we are going to encode ArrayList inputVectors = getInputVectors(); //Multiply the key by each vector and add the result to a new vector logger.debug("Multiplying vectors"); ArrayList outputVectors = new ArrayList<>(); for(ModMatrix inputVector : inputVectors){ logger.debug("Current input vector {}", inputVector); ModMatrix outputVector = key.multiply(inputVector); logger.debug("Multiplied vector {}", outputVector); outputVectors.add(outputVector); } //Take the array of results and turn them back into letters outputString = getOutputFromVectors(outputVectors); //Add the extra characters back to the output and remove the added characters outputString = polishOutputString(); } protected void decode(){ logger.debug("Decoding"); //Get the array of vectors that we are going to decode ArrayList inputVectors = getInputVectors(); //Multiply the inverse of the key by each vector and add the result to a new vector logger.debug("Getting inverse of key"); ModMatrix inverseKey = key.inverse(); logger.debug("Inverse of key {}", inverseKey); ArrayList outputVectors = new ArrayList<>(); for(ModMatrix inputVector : inputVectors){ logger.debug("Current input vector {}", inputVector); ModMatrix outputVector = inverseKey.multiply(inputVector); logger.debug("Multiplied vector {}", outputVector); outputVectors.add(outputVector); } //Take the array of results and turn them back into letters outputString = getOutputFromVectors(outputVectors); //Add the extra characters back to the output and remove the added characters outputString = polishOutputString(); } public Hill() throws InvalidCharacterException{ preserveCapitals = false; preserveWhitespace = false; preserveSymbols = false; setCharacterToAdd('x'); reset(); } public Hill(boolean preserveCapitals, boolean preserveWhitespace, boolean preserveSymbols) throws InvalidCharacterException{ this.preserveCapitals = preserveCapitals; this.preserveWhitespace = preserveWhitespace; this.preserveSymbols = preserveSymbols; setCharacterToAdd('x'); reset(); } public Hill(boolean preserveCapitals, boolean preserveWhitespace, boolean preserveSymbols, char characterToAdd) throws InvalidCharacterException{ this.preserveCapitals = preserveCapitals; this.preserveWhitespace = preserveWhitespace; this.preserveSymbols = preserveSymbols; setCharacterToAdd(characterToAdd); reset(); } public String encode(int[][] key, String inputString) throws InvalidKeyException, InvalidInputException{ return encode(new ModMatrix(key, 26), inputString); } public String encode(ModMatrix key, String inputString) throws InvalidKeyException, InvalidInputException{ setKey(key); setInputStringEncode(inputString); encode(); return outputString; } public String decode(int[][] key, String inputString) throws InvalidKeyException, InvalidInputException{ return decode(new ModMatrix(key, 26), inputString); } public String decode(ModMatrix key, String inputString) throws InvalidKeyException, InvalidInputException{ setKey(key); setInputStringDecode(inputString); decode(); return outputString; } public void reset(){ logger.debug("Resetting fields"); inputString = ""; outputString = ""; key = new ModMatrix(26); } public String getInputString(){ return inputString; } public String getOutputString(){ return outputString; } public ModMatrix getKey(){ return key; } }