//CipherStreamJava/src/main/java/com/mattrixwv/CipherStreamJava/polySubstitution/Bifid.java //Mattrixwv // Created: 03-03-22 //Modified: 04-23-23 package com.mattrixwv.cipherstream.polysubstitution; 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.InvalidKeywordException; public class Bifid{ protected static Logger logger = LoggerFactory.getLogger(Bifid.class); //Fields protected String inputString; //The message that needs to be encoded/decoded protected String outputString; //The encoded/decoded message protected String keyword; //The keyword used to create the grid protected PolybiusSquare polybiusSquare; //Used to encode the message to numbers then back into letters protected boolean preserveCapitals; //Persist capitals in the output string protected boolean preserveWhitespace; //Persist whitespace in the output string protected boolean preserveSymbols; //Persist symbols in the output string //Strips invalid characters from the keyword and creates the grid protected void setKeyword(String keyword) throws InvalidKeywordException{ //Ensure the keyword isn't null if(keyword == null){ throw new InvalidKeywordException("Keyword cannot be null"); } logger.debug("Setting keyword '{}'", keyword); //Save the key for polybius to deal with this.keyword = keyword; } //Ensures inputString constraints protected void setInputString(String inputString) throws InvalidInputException{ //Ensure the input string isn't null if(inputString == null){ throw new InvalidInputException("Input cannot be null"); } logger.debug("Original input string '{}'", inputString); //Apply removal options if(!preserveCapitals){ logger.debug("Removing case"); 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]", ""); } //Save the string logger.debug("Cleaned input string '{}'", inputString); this.inputString = inputString; //Ensure the string isn't blank if(this.inputString.isBlank()){ throw new InvalidInputException("Input must contain at least 1 letter"); } } //Adds all non-letter characters back to the output string protected void formatOutput(String outputString){ logger.debug("Formatting output"); //Keep track of where you are in the output int outputCnt = 0; StringBuilder output = new StringBuilder(); //Check every character in the input and apply the correct rules to the output for(char ch : inputString.toCharArray()){ logger.debug("Current character {}", ch); if(Character.isUpperCase(ch)){ logger.debug("Altering uppercase"); output.append(Character.toUpperCase(outputString.charAt(outputCnt++))); } else if(Character.isLowerCase(ch)){ logger.debug("Altering lowercase"); output.append(Character.toLowerCase(outputString.charAt(outputCnt++))); } else{ logger.debug("Adding symbol"); output.append(ch); } } //Save the output this.outputString = output.toString(); logger.debug("Formatted output string '{}'", this.outputString); } //Encodes inputString using a polybius square and stores the result in outputString protected void encode() throws InvalidCharacterException, InvalidInputException{ logger.debug("Encoding"); //Get the encoded numbers from a polybius square logger.debug("Encoding Polybius"); String polybiusMessage = polybiusSquare.encode(keyword, inputString).replaceAll("\\s", ""); keyword = polybiusSquare.getKeyword(); //Save the cleaned keyword //Split the numbers into 2 rows and rejoin the rows to create a new string StringBuilder row0 = new StringBuilder(); StringBuilder row1 = new StringBuilder(); boolean firstNum = true; logger.debug("Splitting Polybius Square message"); for(char ch : polybiusMessage.toCharArray()){ logger.debug("Current character '{}'", ch); if(firstNum){ row0.append(ch); } else{ row1.append(ch); } firstNum = !firstNum; } String shuffledResult = row0.toString() + row1.toString(); //Take the new string and decode the numbers using polybius logger.debug("Decoding Polybius Square"); String letterResult = polybiusSquare.decode(keyword, shuffledResult); //Format the output formatOutput(letterResult); } //Decodes inputString using a polybius square and stores the result in outputString protected void decode() throws InvalidCharacterException, InvalidInputException{ logger.debug("Decoding"); //Get the decoded number from a polybius square logger.debug("Encoding Polybius Square"); String numberResult = polybiusSquare.encode(keyword, inputString).replaceAll("\\s", ""); keyword = polybiusSquare.getKeyword(); //Split the numbers into to rows and rejoin the rows to create a new string String row0 = numberResult.substring(0, numberResult.length() / 2); String row1 = numberResult.substring(numberResult.length() / 2); StringBuilder unshuffledResult = new StringBuilder(); logger.debug("Splitting Polybius Square message"); for(int cnt = 0;cnt < row0.length();++cnt){ logger.debug("Current characters {} {}", row0.charAt(cnt), row1.charAt(cnt)); unshuffledResult.append(row0.charAt(cnt)); unshuffledResult.append(row1.charAt(cnt)); } //Take the new string and decode the numbers using polybius logger.debug("Decoding Polybius Square"); String letterResult = polybiusSquare.decode(keyword, unshuffledResult.toString()); //Format the outputString formatOutput(letterResult); } //Constructor public Bifid() throws InvalidCharacterException{ preserveCapitals = false; preserveWhitespace = false; preserveSymbols = false; polybiusSquare = new PolybiusSquare(false, false); reset(); } public Bifid(boolean preserveCapitals, boolean preserveWhitespace, boolean preserveSymbols) throws InvalidCharacterException{ this.preserveCapitals = preserveCapitals; this.preserveWhitespace = preserveWhitespace; this.preserveSymbols = preserveSymbols; polybiusSquare = new PolybiusSquare(false, false); reset(); } //Encodes inputString using keyword and returns the result public String encode(String keyword, String inputString) throws InvalidKeywordException, InvalidInputException, InvalidCharacterException{ //Set the parameters reset(); setKeyword(keyword); setInputString(inputString); //Encode and return the message encode(); return outputString; } //Decodes inputString using keyword and returns the result public String decode(String keyword, String inputString) throws InvalidKeywordException, InvalidInputException, InvalidCharacterException{ //Set the parameters reset(); setKeyword(keyword); setInputString(inputString); //Decode and return the message decode(); return outputString; } //Gets public String getInputString(){ return inputString; } public String getOutputString(){ return outputString; } public String getKeyword(){ return keyword; } //Makes sure all variables are empty public void reset(){ logger.debug("Resetting fields"); inputString = ""; outputString = ""; keyword = ""; } }