//CipherStreamJava/src/main/java/com/mattrixwv/cipherstream/polysubstitution/Affine.java //Mattrixwv // Created: 01-26-22 //Modified: 05-04-23 package com.mattrixwv.cipherstream.monosubstitution; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mattrixwv.NumberAlgorithms; import com.mattrixwv.cipherstream.exceptions.InvalidInputException; import com.mattrixwv.cipherstream.exceptions.InvalidKeywordException; public class Affine{ private static final Logger logger = LoggerFactory.getLogger(Affine.class); //Fields protected String inputString; //The string that needs encoded/decoded protected String outputString; //The string that is output after encoding/decoding protected int key1; //The multiplicative key. Key1 must be relatively prime to 26 protected int key2; //The additive key //Settings protected boolean preserveCapitals; //Persist capitals in the output string protected boolean preserveSymbols; //Persist symbols in the output string protected boolean preserveWhitespace; //Persist whitespace in the output string //Ensures key1 constraints protected void setKey1(int key1) throws InvalidKeywordException{ logger.debug("Setting key1 {}", key1); //Mod 26 to ensure no overflow key1 %= 26; //If the key is negative change it to possitive if(key1 < 0){ key1 += 26; } //Make sure the key is relatively prime to 26 (The number of letters) if(NumberAlgorithms.gcd(key1, 26) != 1){ throw new InvalidKeywordException("Key 1 must be relatively prime to 26"); } //Save the key this.key1 = key1; logger.debug("Cleaned key1 {}", key1); } //Ensures key2 constraints protected void setKey2(int key2){ logger.debug("Setting key2 {}", key2); //Mod 26 to ensure no overflow key2 %= 26; //If the key is negative change it to possitive if(key2 < 0){ key2 += 26; } //Save the key this.key2 = key2; logger.debug("Cleaned key2 {}", key2); } //Ensures inputString constraints protected void setInputString(String inputString) throws InvalidInputException{ if(inputString == null){ throw new InvalidInputException("Input must not be null"); } logger.debug("Original input string '{}'", inputString); if(!preserveCapitals){ logger.debug("Removing case"); inputString = inputString.toLowerCase(); } if(!preserveWhitespace){ logger.debug("Removing whitespace"); inputString = inputString.replaceAll("\\s", ""); } if(!preserveSymbols){ logger.debug("Removing symbols"); inputString = inputString.replaceAll("[^a-zA-Z\\s]", ""); } this.inputString = inputString; logger.debug("Cleaned input string '{}'", inputString); if(this.inputString.isBlank()){ throw new InvalidInputException("Input cannot be blank"); } } //Encodes the inputString and stores the result in outputString protected void encode(){ logger.debug("Encoding"); //Step through every character in the input and encode it if needed StringBuilder output = new StringBuilder(); for(char ch : inputString.toCharArray()){ logger.debug("Current char {}", ch); //Encode all letters if(Character.isUpperCase(ch)){ //Change the character to a number int letter = ch - 65; //Encode the number letter = ((key1 * letter) + key2) % 26; //Change the new number back to a character and append it to the output char newChar = (char)(letter + 'A'); output.append(newChar); logger.debug("Encoded char {}", newChar); } else if(Character.isLowerCase(ch)){ //Change the character to a number int letter = ch - 97; //Encode the number letter = ((key1 * letter) + key2) % 26; //Change the new number back to a character and append it to the output char newChar = (char)(letter + 'a'); output.append(newChar); logger.debug("Encoded char {}", newChar); } //Pass all other characters through else{ output.append(ch); } } //Save and return the output outputString = output.toString(); logger.debug("Saving output string '{}'", outputString); } //Decodes the inputString and stores the result in outputString protected void decode(){ logger.debug("Decoding"); //Find the multiplicative inverse of key1 int key1I = 1; while(((key1 * key1I) % 26) != 1){ ++key1I; } logger.debug("Key1 inverse {}", key1I); //Step through every character in the input and decode it if needed StringBuilder output = new StringBuilder(); for(char ch : inputString.toCharArray()){ logger.debug("Current char {}", ch); //Encode all letters if(Character.isUpperCase(ch)){ //Change the letter to a number int letter = ch - 65; //Encode the number letter = (key1I * (letter - key2)) % 26; if(letter < 0){ letter += 26; } //Change the new number back to a character and append it to the output char newChar = (char)(letter + 65); output.append(newChar); logger.debug("Decoded char {}", newChar); } else if(Character.isLowerCase(ch)){ //Change the letter to a number int letter = ch - 97; //Encode the number letter = (key1I * (letter - key2)) % 26; if(letter < 0){ letter += 26; } //Change the new number back to a character and append it to the output char newChar = (char)(letter + 97); output.append(newChar); logger.debug("Decoded char {}", newChar); } //Pass all other characters through else{ output.append(ch); } } //Save and return the output outputString = output.toString(); logger.debug("Saving output string '{}'", outputString); } //Constructor public Affine(){ preserveCapitals = false; preserveSymbols = false; preserveWhitespace = false; reset(); } public Affine(boolean preserveCapitals, boolean preserveWhitespace, boolean preserveSymbols){ this.preserveCapitals = preserveCapitals; this.preserveSymbols = preserveSymbols; this.preserveWhitespace = preserveWhitespace; reset(); } //Encodes inputString using key1 and key2 and returns the result public String encode(int key1, int key2, String inputString) throws InvalidKeywordException, InvalidInputException{ setKey1(key1); setKey2(key2); setInputString(inputString); encode(); return outputString; } //Decodes inputString using key1 and key2 and returns the result public String decode(int key1, int key2, String inputString) throws InvalidKeywordException, InvalidInputException{ setKey1(key1); setKey2(key2); setInputString(inputString); decode(); return outputString; } //Getters public String getInputString(){ return inputString; } public String getOutputString(){ return outputString; } public int getKey1(){ return key1; } public int getKey2(){ return key2; } //Makes sure all of the variables are empty public void reset(){ logger.debug("Resetting fields"); inputString = ""; outputString = ""; key1 = 0; key2 = 0; } }