using System; using System.Linq; using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Collections.Generic; namespace AniNIX.Crypto { public class Analysis : Cipher { private Substitution _sb; public override String Description() { return "These are analysis tools to help understand ciphers."; } public override String Command() { return "analysis"; } public Analysis(Workbench w) : base (w) { //Analysis needs to be able to read the EngCommon array from the Substition class this._sb = new Substitution(w); } /// /// Decode the user's input and relate to an existing function. /// /// The working copy of the cipher /// The original cipher /// The user input /// The modified workspace string public override String RunCommand(String workSpace, String inputText, String[] line) { if (workSpace == null || inputText == null || line == null || line.Length < 2) { Console.Error.WriteLine("Malformed!"); return workSpace; } switch (line[1]) { case "freq": Frequency(workSpace); break; case "freqinfo": FrequencyInfo(); break; case "one-to-one": OneToOneQuery(workSpace,inputText); break; case "free": FindFreeCharacters(workSpace,inputText); break; case "diff": Diff(line); break; case "charinfo": CharInfo(line); break; case "hexprint": HexPrint(workSpace); break; case "help": case "": GetHelp(); break; default: Console.Error.WriteLine("Invalid command. Type 'analysis help' for more."); break; } return workSpace; } /// /// Show this help text /// public override void GetHelp() { Console.WriteLine("Analysis tools help:\nanalysis freq -- Get frequency of characters.\nanalysis freqinfo -- Return the most common English frequencies.\nanalysis one-to-one -- See if there is a direct correspondence of characters between cipher and workspace.\nanalysis diff a b -- get the difference between two characters\nanalysis charinfo c -- get the info about a character c.\nanalysis hexprint -- print the hex and decimal value of each character in the workspace."); } /// /// Return a dictionary of letters mapped to the number of letters found in the workSpace /// /// the current workSpace /// The dictionary of frequencies public static Dictionary FindFrequencies(String workSpace) { Dictionary frequencies = new Dictionary(); // For each letter in the workSpace,... for (int i = 0; i < workSpace.Length; i++) { if (!Char.IsLetter(workSpace[i])) { continue; } String charStr = String.Format("{0}",workSpace[i]); // If the letter already exists, increment its frequency. if (frequencies.ContainsKey(charStr)) { frequencies[charStr] = frequencies[charStr] + 1; } else { // Otherwise add it with a frequency of one. frequencies.Add(charStr,1); } } return frequencies; } /// /// Return an ordered list of the most common letters in the workSpace /// /// The user workSpace /// An ordered list public static List GetMostCommonLetters(String workSpace) { // Find the frequencies. List> freqList = FindFrequencies(workSpace).ToList(); // Sort the frequencies freqList.Sort((firstPair,nextPair)=>nextPair.Value.CompareTo(firstPair.Value)); // Pull out the letters in sorted order and return. List returnL = new List(); foreach (var item in freqList) { returnL.Add(item.Key); } return returnL; } /// Find the doubles in a string /// the string to analyze /// a list of doubles public static List GetDoubles(String workSpace) { List theList = new List(); // For each character in the input, if the previous character is the same, it's a double. Add it to the list. for (int i=1; i /// Find the substrings of a given length in the workSpace. /// /// the workSpace to analyze /// the length of the substrings to look for /// the dictionary of substrings by frequency public static Dictionary GetSubstrings(String workSpace, int length) { Dictionary theList = new Dictionary(); // Start at the beginning of the string, and advance the substring window by one until the substring segment would be outside the workSpace. for (int i=1; i /// find words of a given length. /// /// The length to look for /// A string broken down by spaces /// the words of the length with frequencies public static Dictionary FindWordsOfLength(int length,String[] bySpace) { Dictionary wordsFreq = new Dictionary(); // TODO Replace this with whitespace and punctuation removal // If the word ends in punctuation and is longer than the length or is equal to the length, add it to the list and track frequency. for (int i = 0; i < bySpace.Length; i++) { if (bySpace[i].Length == length || (bySpace[i].Length == length+1 && Char.IsPunctuation(bySpace[i][length]))) { if (Char.IsPunctuation(bySpace[i][bySpace[i].Length-1])) { bySpace[i] = bySpace[i].Substring(0,bySpace[i].Length-1); } if (wordsFreq.ContainsKey(bySpace[i])) { wordsFreq[bySpace[i]] += 1; } else { wordsFreq.Add(bySpace[i],1); } } } return wordsFreq; } /// /// Get the top entries in a frequency map /// /// Frequency map /// A list of a given length with the top entries public static List Top(Dictionary theList) { List> freqList = theList.ToList(); List returnL = new List(); freqList.Sort((firstPair,nextPair)=>nextPair.Value.CompareTo(firstPair.Value)); for (int i = 0; i < 5 && i < freqList.Count; i++) { returnL.Add(freqList[i].Key); } return returnL; } /// /// Print an ordered list with the frequency /// /// the frequency map /// String header public static void PrintOrdered(Dictionary theList,String header) { List> freqList = theList.ToList(); freqList.Sort((firstPair,nextPair)=>nextPair.Value.CompareTo(firstPair.Value)); Console.Write(header); for (int i = 0; i < 5 && i < freqList.Count; i++) { Console.Write(String.Format("({0}){1} ",freqList[i].Key,freqList[i].Value)); } Console.WriteLine(""); } /// /// Print an ordered list /// /// the frequency map /// String header public static void PrintOrdered(List theList,String header) { Console.Write(header); foreach (String str in theList) { Console.Write(str); Console.Write(" "); } Console.WriteLine(); } /// /// Analyze a workspace /// /// workSpace to analyze public void Frequency(String workSpace) { //Show the individual letter frequeuncy. Console.ForegroundColor = ConsoleColor.Cyan; PrintOrdered(FindFrequencies(workSpace),"Top letters by frequency: "); //Show the doubled letters Console.ForegroundColor = ConsoleColor.Green; PrintOrdered(GetDoubles(workSpace),"The following letters are doubled in the workspace: "); Console.ForegroundColor = ConsoleColor.Yellow; PrintOrdered(GetSubstrings(workSpace,2),"Top substrings of length 2: "); Console.ForegroundColor = ConsoleColor.Magenta; PrintOrdered(GetSubstrings(workSpace,3),"Top substrings of length 3: "); String[] bySpace = workSpace.Split(' '); //Find the words of a given length Console.ForegroundColor = ConsoleColor.White; PrintOrdered(FindWordsOfLength(1,bySpace),"Words of length 1: "); Console.ForegroundColor = ConsoleColor.Yellow; PrintOrdered(FindWordsOfLength(2,bySpace),"Words of length 2: "); Console.ForegroundColor = ConsoleColor.Magenta; PrintOrdered(FindWordsOfLength(3,bySpace),"Words of length 3: "); Console.ResetColor(); } /// /// Show the statistical frequencies. /// public void FrequencyInfo() { // Thanks to http://norvig.com/mayzner.html for this info. // By letter Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine("Letters by frequency:"); for (int i=0; i < this._sb.EngCommon.Length; i++) { Console.Write(this._sb.EngCommon[i]); Console.Write(" "); } Console.Write('\n'); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Possible doubles: ll ee ss oo tt ff rr nn pp cc bb mm gg uu zz aa"); // By Substring 2,3 characters in length Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("Top sequences of N characters where N=..."); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("2: th he in er an re on at en nd"); Console.ForegroundColor = ConsoleColor.Magenta; Console.WriteLine("3: the and ing ion tio end ati for her ter"); // By word 1,2,3 chars in length Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("Top words of length..."); Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("1: I a"); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("2: of to in is it as be by on he"); Console.ForegroundColor = ConsoleColor.Magenta; Console.WriteLine("3: the and for was not are his but had you"); Console.ResetColor(); } /// /// Find out if the workSpace has a one-to-one relationship with the input. /// /// the workSpace /// the user input /// Should the program write to stdout /// A boolean if the query is one-to-one public bool OneToOneQuery(String workSpace, String inputText, bool shouldPrint=true) { Dictionary relation = new Dictionary(); //Seed the keys so that we print efficiently. StringBuilder subKey = new StringBuilder(); StringBuilder encKey = new StringBuilder(); subKey.Append("True. These are one-to-one.\n"); subKey.Append("\nFinal-to-input key:\n"); subKey.Append("sub decrypt "); encKey.Append("sub encrypt "); for (int i = 0; i < workSpace.Length; i++) { // For each non-whitespace character, if the relation is known, ... if (!Char.IsWhiteSpace(workSpace[i])) { if (relation.ContainsKey(workSpace[i])) { // if the relation doesn't match up, we found the mismatch and should return false. if (relation[workSpace[i]] != inputText[i]) { if (shouldPrint) Console.Error.WriteLine(String.Format("Character {0} repeated. These are not one-to-one.",workSpace[i])); return false; } // Otherwise add the new relation pairing. } else { if ( workSpace[i] != inputText[i] ) { relation.Add(workSpace[i],inputText[i]); encKey.Append(String.Format("{0}={1} ",inputText[i],workSpace[i])); subKey.Append(String.Format("{0}={1} ",workSpace[i],inputText[i])); } } } } // Print the keys and return true. if (shouldPrint) { subKey.Append("\nInput-to-final key:"); Console.WriteLine(subKey.ToString()); Console.WriteLine(encKey.ToString()); } return true; } /// /// Find the characters unused by the encryption key. /// /// the workSpace /// the user input public void FindFreeCharacters(String workSpace, String inputText) { // Start with a list of all the alphanum characters. List alphanum = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".ToCharArray().OfType().ToList(); // Eliminate all of the ones we can. for (int i = 0; i < workSpace.Length; i++) { if (alphanum.Contains(workSpace[i])) alphanum.Remove(workSpace[i]); } // Print the remaining elements. Console.WriteLine("Remaining characters to use in keys:"); foreach (char c in alphanum) { Console.Write(c); } Console.WriteLine(); } /// /// Show the numeric difference between two characters -- useful for identifying Caesarian ciphers /// /// The user's input /// -99 if malformated or the difference between characters. public int Diff(String[] line) { // If the number of arguments or format is wrong, return -99 if (line.Length != 4 || line[2].Length != 1 || line[3].Length != 1) { Console.Error.WriteLine("Bad formatting"); return -99; } //Otherwise return -99 char first = line[2][0]; char second = line[3][0]; Console.WriteLine(String.Format("These are different by {0}.",first-second)); return (first-second); } /// /// Printout the info of a character /// /// line to analyze public void CharInfo(String[] line) { if (line == null || line.Length != 3 || line[2].Length != 1) { Console.Error.WriteLine("Malformed"); return; } // Print the ascii value of a character. Console.WriteLine(String.Format("Character: {0}\nASCII Value: {1}",line[2][0],(int)line[2][0])); if (Char.IsLetter(line[2][0])) { // If the character is a letter, include the alphabet index Console.WriteLine(String.Format("Alphabet index: {0}",(Char.IsUpper(line[2][0])) ? (int)line[2][0] - (int)'A' : (int)line[2][0] - (int)'a')); } } public void HexPrint(String line) { Console.WriteLine("Char - Dec - Hex"); foreach (char i in line.ToCharArray()) { Console.WriteLine("{0} -- {1} -- {2}",i,(int)i,Convert.ToByte(i)); } } //Analysis doesn't handle encryption or decryption, but we want to use the same code for subscribing. public override String Encrypt(string workSpace,String ciphetText,String[] line) { return workSpace; } public override String Decrypt(string workSpace,String ciphetText,String[] line) { return workSpace; } } }