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

2 Lösungen Lösungen öffentlich
#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.
#2
vote_ok
von eulerscheZhl (5230 Punkte) - 04.06.2016 um 16:41 Uhr
Quellcode ausblenden C#-Code
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:
Quellcode ausblenden C#-Code
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

Bitte melden Sie sich an um eine Kommentar zu schreiben.
Kommentar schreiben