C# :: Aufgabe #120 :: Lösung #2
2 Lösungen

#120
Substitutionschiffren knacken
Fortgeschrittener - C#
von eulerscheZhl
- 09.03.2016 um 21:41 Uhr
Bei einer Substitutionschiffre werden Buchstaben durch andere Buchstaben ersetzt.
Dadurch bleiben die Häufigkeitsverteilungen der Buchstaben allerdings erhalten, weshalb etwa ein 'e' leicht erkannt werden kann.
Für diese Aufgabe soll aber ein Wörterbuch (z.B. von der Uni Kiel, benötigt aber etwas Nachbearbeitung) verwendet werden, um die ursprüngliche Nachricht zu erhalten.
So ist es zwar schwer, eine komplett richtige Dekodierung zu erhalten (da nicht alle Wörter im Wörterbuch enthalten sind), aber man kann lesbare Ergebnisse erzielen.
Im Anhang befinden sich Texte aus zufälligen Artikeln der deutschsprachigen Wikipedia. Es ist jeweils mindestens die Hälfte der vorkommenden Wörter im verlinkten Wörterbuch enthalten.
Dadurch bleiben die Häufigkeitsverteilungen der Buchstaben allerdings erhalten, weshalb etwa ein 'e' leicht erkannt werden kann.
Für diese Aufgabe soll aber ein Wörterbuch (z.B. von der Uni Kiel, benötigt aber etwas Nachbearbeitung) verwendet werden, um die ursprüngliche Nachricht zu erhalten.
So ist es zwar schwer, eine komplett richtige Dekodierung zu erhalten (da nicht alle Wörter im Wörterbuch enthalten sind), aber man kann lesbare Ergebnisse erzielen.
Im Anhang befinden sich Texte aus zufälligen Artikeln der deutschsprachigen Wikipedia. Es ist jeweils mindestens die Hälfte der vorkommenden Wörter im verlinkten Wörterbuch enthalten.
#2

von eulerscheZhl (5230 Punkte)
- 04.06.2016 um 16:41 Uhr

using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; //Flags zum Programmaufruf using Mono.Options; //https://github.com/mono/mono/blob/master/mcs/class/Mono.Options/Mono.Options/Options.cs namespace Substitionschiffre { class Chiffre { static Dictionary<string, string[]> cipher; static int bestFit; static string input; static void Main(string[] args) { string dictFile = null; string chiffreFile = null; string outputFile = null; bool scramble = false; OptionSet p = new OptionSet () { { "d|dict=", "the dictionary filename.", v => dictFile = v }, { "c|chiffre=", "the chiffre filename.", v => chiffreFile = v }, { "o|output=", "the output filename.", v => outputFile = v }, { "s|scrable", "scramble a text, instead of decoding", v => { if (v != null) scramble = true; } }, }; p.Parse (args); if (dictFile == null || chiffreFile == null) { Console.WriteLine ("usage: dict=<dictFile> chiffre=<chiffreFile>"); return; } //einlesen string[] dict = File.ReadAllLines (dictFile).Where (line => !line.StartsWith ("#")). Select (x => x.Split (" \t".ToCharArray (), StringSplitOptions.RemoveEmptyEntries) [0].ToLower()). Distinct ().ToArray (); input = File.ReadAllText (chiffreFile); string[] words = input.ToLower ().Split (" .:!?,();-'\"1234567890\n".ToCharArray (), StringSplitOptions.RemoveEmptyEntries).Distinct ().ToArray (); FindCandidates (words, dict); if (scramble && outputFile != null) { Scramble.ScrambleText (chiffreFile, outputFile); Console.WriteLine ("known words: " + words.Count(x => dict.Contains(x)) + "/" + words.Length); return; } bestFit = cipher.Count * 1 / 2; //mindestens die Hälfte der Wörter muss bekannt sein Console.WriteLine ("starting with " + bestFit + " words"); //buchstabenhäufigkeit Dictionary<char, int> letterDict = new Dictionary<char, int> (); string alphabet = new string (input.ToCharArray ().Distinct ().ToArray()); foreach (string word in words) { foreach (char c in word) { if (!alphabet.Contains (c)) continue; if (!letterDict.ContainsKey (c)) letterDict.Add (c, 0); letterDict [c]++; } } List<KeyValuePair<char, int>> letterCount = letterDict.OrderBy (x => -x.Value).Where (x => x.Value > 0).ToList (); char[] nextLetter = (new string (letterCount.Select (x => x.Key).ToArray ()) + "abcdefghijklmnopqrstuvwxyzäöüß").Distinct ().ToArray (); Dictionary<char, char> letters = new Dictionary<char, char> (); Dictionary<char,bool> used = new Dictionary<char, bool> (); foreach (char c in nextLetter) { used.Add (c, false); } FindLetters (letters, used, nextLetter, 0); } static void FindCandidates(string[] words, string[] dict) { cipher = new Dictionary<string, string[]> (); foreach (string word in words) { List<string> matches = new List<string> (); foreach (string cand in dict) { if (cand.Length != word.Length) continue; bool canAdd = true; Dictionary<char, char> subst = new Dictionary<char, char> (); for (int i = 0; i < word.Length; i++) { if (!subst.ContainsKey (word [i])) { if (subst.ContainsValue (cand [i])) { canAdd = false; break; } subst.Add (word [i], cand [i]); } if (subst [word [i]] != cand [i]) { canAdd = false; break; } } if (canAdd) matches.Add (cand); } cipher.Add (word, matches.ToArray()); } } static void FindLetters(Dictionary<char, char> letters, Dictionary<char, bool> used, char[] nextLetter, int index) { int fit = CountFit (letters); if (fit < bestFit) return; if (index == nextLetter.Length) { //Alle Buchstaben zugeteilt, Bilanz ziehen und gegebenenfalls Ergebnis ausgeben bestFit = fit; foreach (string word in cipher.Keys) { StringBuilder sb = new StringBuilder (); foreach (char c in word) { sb.Append (letters.ContainsKey (c) ? letters [c] : '?'); } Console.Write (sb.ToString().PadRight(10)); } Console.Write ("\n" + fit + " bekannte Worte\n" + String.Join (", ", letters.Select (x => x.Key + "=" + x.Value))); char[] message = input.ToCharArray(); for (int i = 0; i < message.Length; i++) { if (letters.ContainsKey (message [i])) message [i] = letters [message [i]]; } Console.WriteLine ("\ndecoded message: " + new string (message) + "\n\n"); return; } List<KeyValuePair<char, int>> fitCount = new List<KeyValuePair<char, int>> (); foreach (char c in used.Where(x => !x.Value).Select(x=>x.Key)) { Dictionary<char, char> newDict = new Dictionary<char, char> (letters); newDict.Add (nextLetter [index], c); fitCount.Add (new KeyValuePair<char, int>(c, CountFit (newDict))); } fitCount = fitCount.OrderBy (x => -x.Value).ToList(); foreach(KeyValuePair<char, int> pair in fitCount) { if (pair.Value < bestFit) return; Dictionary<char, char> newDict = new Dictionary<char, char> (letters); newDict.Add (nextLetter [index], pair.Key); Dictionary<char, bool> newUsed = new Dictionary<char, bool> (used); newUsed [pair.Key] = true; FindLetters (newDict, newUsed, nextLetter, index + 1); } } private static int CountFit(Dictionary<char, char> letters) { int result = 0; foreach (KeyValuePair<string, string[]> pair in cipher) { bool match = false; foreach (string cand in pair.Value) { bool currentMatch = true; for (int i = 0; i < pair.Key.Length; i++) { if (letters.ContainsKey (pair.Key [i]) && letters [pair.Key [i]] != cand [i]) { currentMatch = false; break; } } if (currentMatch) { match = true; break; } } if (match) result++; } return result; } } }
Und zum Zerwürfeln:

using System; using System.Collections.Generic; using System.IO; namespace Substitionschiffre { public static class Scramble { public static void ScrambleText(string inputFile, string outputFile) { string alphabet = "abcdefghijklnmopqrstuvwxyzöäüß"; char[] scramble = alphabet.ToCharArray (); Random r = new Random (); for (int i = 0; i < scramble.Length; i++) { int index = r.Next (i, scramble.Length); char tmp = scramble [index]; scramble [index] = scramble [i]; scramble [i] = tmp; } Dictionary<char,char> dict = new Dictionary<char, char> (); for (int i = 0; i < alphabet.Length; i++) { dict.Add (alphabet [i], scramble [i]); } char[] text = File.ReadAllText (inputFile).ToLower().ToCharArray (); for (int i = 0; i < text.Length; i++) { if (dict.ContainsKey (text [i])) text [i] = dict [text [i]]; } File.WriteAllText (outputFile, new string(text)); } } }
Kommentare:
Für diese Lösung gibt es noch keinen Kommentar
Seite 1 von 0
1