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; /** * Implements the Affine cipher, which is a monoalphabetic substitution cipher based on linear algebra. * This class provides methods to encode and decode strings using the Affine cipher with a specified key. * *

* The Affine cipher uses two keys: *

* */ public final class Affine{ private static final Logger logger = LoggerFactory.getLogger(Affine.class); //?Fields /** The string that needs encoded/decoded */ protected String inputString; /** The string that is output after encoding/decoding */ protected String outputString; /** The multiplicative key. Key1 must be relatively prime to 26 */ protected int key1; /** The additive key */ protected int key2; //?Settings /** Persist capitals in the output string */ protected boolean preserveCapitals; /** Persist symbols in the output string */ protected boolean preserveSymbols; /** Persist whitespace in the output string */ protected boolean preserveWhitespace; /** * Sets the multiplicative key and validates it. * * @param key1 the multiplicative key * @throws InvalidKeywordException if the key1 is not relatively prime to 26 */ 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); } /** * Sets the additive key. * * @param key2 the additive key */ 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); } /** * Sets and sanitizes the input string according to the preservation settings. * * @param inputString the string to be processed * @throws InvalidInputException if the input string is null or blank after processing */ 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 input string using the affine cipher. */ 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 input string using the affine cipher. */ 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 /** * Constructs a new {@code Affine} instance with default settings: * capitals, symbols, and whitespace are not preserved. */ public Affine(){ preserveCapitals = false; preserveSymbols = false; preserveWhitespace = false; reset(); } /** * Constructs a new {@code Affine} instance with specified settings for preserving capitals, symbols, and whitespace. * * @param preserveCapitals whether to preserve capital letters in the output * @param preserveWhitespace whether to preserve whitespace in the output * @param preserveSymbols whether to preserve symbols in the output */ public Affine(boolean preserveCapitals, boolean preserveWhitespace, boolean preserveSymbols){ this.preserveCapitals = preserveCapitals; this.preserveSymbols = preserveSymbols; this.preserveWhitespace = preserveWhitespace; reset(); } /** * Encodes the provided input string using the specified keys and returns the encoded result. * * @param key1 the multiplicative key (must be relatively prime to 26) * @param key2 the additive key * @param inputString the string to be encoded * @return the encoded string * @throws InvalidKeywordException if key1 is not relatively prime to 26 * @throws InvalidInputException if the input string is invalid */ public String encode(int key1, int key2, String inputString) throws InvalidKeywordException, InvalidInputException{ setKey1(key1); setKey2(key2); setInputString(inputString); encode(); return outputString; } /** * Decodes the provided input string using the specified keys and returns the decoded result. * * @param key1 the multiplicative key (must be relatively prime to 26) * @param key2 the additive key * @param inputString the string to be decoded * @return the decoded string * @throws InvalidKeywordException if key1 is not relatively prime to 26 * @throws InvalidInputException if the input string is invalid */ public String decode(int key1, int key2, String inputString) throws InvalidKeywordException, InvalidInputException{ setKey1(key1); setKey2(key2); setInputString(inputString); decode(); return outputString; } //?Getters /** * Returns the current input string. * * @return the input string */ public String getInputString(){ return inputString; } /** * Returns the current output string. * * @return the output string */ public String getOutputString(){ return outputString; } /** * Returns the current multiplicative key. * * @return the multiplicative key */ public int getKey1(){ return key1; } /** * Returns the current additive key. * * @return the additive key */ public int getKey2(){ return key2; } /** * Resets all fields to their default values. */ public void reset(){ logger.debug("Resetting fields"); inputString = ""; outputString = ""; key1 = 0; key2 = 0; } }