Entschlüsselung durch Musteranalyse

11

Sie erhalten eine verschlüsselte Zeichenfolge, die mit einer sehr einfachen Substitutionsverschlüsselung verschlüsselt wird.

Problem

Sie wissen nicht, was die Chiffre ist, aber Sie wissen, dass der Chiffretext Englisch ist und dass die häufigsten Buchstaben in Englisch etaoinshrdlucmfwypvbgkqjxz in dieser Reihenfolge sind. Die einzigen zulässigen Zeichen sind Großbuchstaben und Leerzeichen. Sie können eine grundlegende Analyse durchführen - ausgehend von einzelnen Buchstaben, aber Sie können auf eine komplexere Analyse mit mehreren Buchstaben migrieren. Beispielsweise folgt U fast immer Q, und nur bestimmte Buchstaben können zweimal hintereinander kommen.

Beispiele

clear : SUBMARINE TO ATTACK THE DOVER WAREHOUSE AND PORT ON TUESDAY SUNRISE
cipher: ZOQ DUPAEYSRYDSSDXVYSHEYNRBEUYLDUEHROZEYDANYKRUSYRAYSOEZNDMYZOAUPZE

clear : THE QUICK BROWN FOX BEING QUITE FAST JUMPED OVER THE LAZY DOG QUITE NICELY
cipher: TNAEPDHIGEMZQJLEVQBEMAHL EPDHTAEVXWTEODYUASEQKAZETNAERXFCESQ EPDHTAELHIARC

clear : BUFFALO BUFFALO BUFFALO BUFFALO BUFFALO BUFFALO BUFFALO
cipher: HV  WRPDHV  WRPDHV  WRPDHV  WRPDHV  WRPDHV  WRPDHV  WRP

Herausforderungen

Überprüfen Sie, ob Sie den Text in jeder dieser Chiffren entschlüsseln können:

  • SVNXIFCXYCFSXKVVZXIHXHERDXEIYRAKXZCOFSWHCZXHERDXBNRHCXZR RONQHXORWECFHCUH
  • SOFPTGFIFBOKJPHLBFPKHZUGLSOJPLIPKBPKHZUGLSOJPMOLEOPWFSFGJLBFIPMOLEOPXULBSIPLBP KBPBPWLIJFBILUBKHPGKISFG
  • TMBWFYAQFAZYCUOYJOBOHATMCYNIAOQW Q JAXOYCOCYCHAACOCYCAHGOVYLAOEGOTMBWFYAOBFF ACOBHOKBZYKOYCHAUWBHAXOQW XITHJOV WOXWYLYCU
  • FTRMKRGVRFMHSZVRWHRSFMFLMBNGKMGTHGBRSMKROKLSHSZMHKMMMMMRVVLVMPRKKOZRMFVDSGOFRW

Ich habe die Substitutionsmatrizen und den Klartext für jede, aber ich werde sie nur offenbaren, wenn es zu schwierig wird oder jemand es nicht herausfindet.

Die Lösung, mit der die meisten Nachrichten erfolgreich entschlüsselt werden können, ist der Gewinner. Wenn zwei Lösungen gleich gut sind, werden sie durch Stimmenzahl entschieden.

Thomas O.
quelle
3
Was macht »am elegantesten« aus? Ich denke, das ist das gleiche, was Chris bereits in 99 Flaschen beanstandet hat. Es ist ein subjektives Kriterium, das ziemlich schwer zu beurteilen ist.
Joey
@ Joey Die meisten Upvotes? Lass die Community entscheiden.
Thomas O
2
Zu "den meisten Upvotes": Ich bin unglücklich zu sehen, dass dies ein Beliebtheitswettbewerbsbeitrag wird, nicht zuletzt, weil der Beitrag ansonsten ausgezeichnet ist; Siehe meta.codegolf.stackexchange.com/questions/110/… für meine Gedanken zu der ganzen Angelegenheit.
Chris Jester-Young
2
Was bedeutet "elegant" hier? Beste Big-O-Leistung?
Gnibbler
1
@ Bass5098, nein. Es ist nur ein schwieriger Chiffretext, der verdorben wurde, um ihn widerstandsfähiger gegenüber Frequenzanalysen zu machen.
Thomas O

Antworten:

9

Python

Ich habe alle geheimen Sätze herausgefunden, aber ich werde sie hier nicht posten. Führen Sie den Code aus, wenn Sie sich interessieren.

Der Code wählt ein Leerzeichen aus, listet alle möglichen Ersetzungen für jedes Wort auf und sucht dann nach kompatiblen Ersetzungen. Es erlaubt auch einige Wörter außerhalb des Lexikons, um Rechtschreibfehler im Klartext zu behandeln :)

Ich habe ein großes Lexikon (~ 500K Wörter) von http://wordlist.sourceforge.net/ verwendet .

import sys,re

# get input
message = sys.argv[1]

# read in lexicon of words
# download scowl version 7.1
# mk-list english 95 > wordlist
lexicon = set()
roman_only = re.compile('^[A-Z]*$')
for word in open('wordlist').read().upper().split():
  word=word.replace("'",'')
  if roman_only.match(word): lexicon.add(word)

histogram={}
for c in message: histogram[c]=0
for c in message: histogram[c]+=1
frequency_order = map(lambda x:x[1], sorted([(f,c) for c,f in histogram.items()])[::-1])

# returns true if the two maps are compatible.
# they are compatible if the mappings agree wherever they are defined,
# and no two different args map to the same value.
def mergeable_maps(map1, map2):
  agreements = 0
  for c in map1:
    if c in map2:
      if map1[c] != map2[c]: return False
      agreements += 1
  return len(set(map1.values() + map2.values())) == len(map1) + len(map2) - agreements

def merge_maps(map1, map2):
  m = {}
  for (c,d) in map1.items(): m[c]=d
  for (c,d) in map2.items(): m[c]=d
  return m

def search(map, word_maps, outside_lexicon_allowance, words_outside_lexicon):
  cleartext = ''.join(map[x] if x in map else '?' for x in message)
  #print 'trying', cleartext

  # pick a word to try next
  best_word = None
  best_score = 1e9
  for (word,subs) in word_maps.items():
    if word in words_outside_lexicon: continue
    compatible_subs=0
    for sub in subs:
      if mergeable_maps(map, sub): compatible_subs += 1
    unassigned_chars = 0
    for c in word:
      if c not in map: unassigned_chars += 1  #TODO: duplicates?
    if compatible_subs == 0: score = 0
    elif unassigned_chars == 0: score = 1e9
    else: score = 1.0 * compatible_subs / unassigned_chars   # TODO: tweak?
    if score < best_score:
      best_score = score
      best_word = word
  if not best_word:  # no words with unset characters, except possibly the outside lexicon ones
    print cleartext,[''.join(map[x] if x in map else '?' for x in word) for word in words_outside_lexicon]
    return True

  # use all compatible maps for the chosen word
  r = False
  for sub in word_maps[best_word]:
    if not mergeable_maps(map, sub): continue
    r |= search(merge_maps(map, sub), word_maps, outside_lexicon_allowance, words_outside_lexicon)

  # maybe this word is outside our lexicon
  if outside_lexicon_allowance > 0:
    r |= search(map, word_maps, outside_lexicon_allowance - 1, words_outside_lexicon + [best_word])
  return r

for outside_lexicon_allowance in xrange(3):
  # assign the space character first
  for space in frequency_order:
    words = [w for w in message.split(space) if w != '']
    if reduce(lambda x,y:x|y, [len(w)>20 for w in words]): continue  # obviously bad spaces

    # find all valid substitution maps for each word
    word_maps={}
    for word in words:
      n = len(word)
      maps = []
      for c in lexicon:
        if len(c) != n: continue
        m = {}
        ok = 1
        for i in xrange(n):
          if word[i] in m:                      # repeat letter
            if m[word[i]] != c[i]: ok=0; break  # repeat letters map to same thing
          elif c[i] in m.values(): ok=0; break  # different letters map to different things
          else: m[word[i]]=c[i]
        if ok: maps.append(m);
      word_maps[word]=maps

    # look for a solution
    if search({space:' '}, word_maps, outside_lexicon_allowance, []): sys.exit(0)

print 'I give up.'
Keith Randall
quelle
1

PHP (unvollständig)

Dies ist eine unvollständige PHP-Lösung, die die Buchstabenhäufigkeitsinformationen in der Frage sowie ein Wörterbuch mit Wörtern verwendet, die mit regulären Ausdrücken auf der Grundlage der zuverlässigsten Buchstaben im angegebenen Wort übereinstimmen.

Derzeit ist das Wörterbuch recht klein, aber mit der entsprechenden Erweiterung gehe ich davon aus, dass sich die Ergebnisse verbessern werden. Ich habe die Möglichkeit von Teilübereinstimmungen in Betracht gezogen, aber mit dem aktuellen Wörterbuch führt dies eher zu einer Verschlechterung als zu einer Verbesserung der Ergebnisse.

Selbst mit dem aktuellen, kleinen Wörterbuch kann ich ziemlich sicher sagen, was die vierte Nachricht codiert.

#!/usr/bin/php
<?php

    if($argv[1]) {

        $cipher = $argv[1];

        // Dictionary
        $words = explode("/", "the/to/on/and/in/is/secret/message");
        $guess = explode("/", "..e/t./o./a../i./.s/.e..et/.ess..e");

        $az = str_split("_etaoinshrdlucmfwypvbgkqjxz");

        // Build table
        for($i=0; $i<strlen($cipher); $i++) {
            $table[$cipher{$i}]++;
        }
        arsort($table);

        // Do default guesses
        $result = str_replace("_", " ", str_replace(array_keys($table), $az, $cipher));

        // Apply dictionary
        $cw = count($words);
        for($i=0; $i<$cw*2; $i++) {
            $tokens = explode(" ", $result);
            foreach($tokens as $t) {
                if(preg_match("/^" . $guess[$i%$cw] . "$/", $t)) {
                    $result = deenc($words[$i%$cw], $t, $result);
                    echo $t . ' -> ' . $words[$i%$cw] . "\n";
                    break;
                }
            }
        }

        // Show best guess
        echo $result . "\n";

    } else {

        echo "Usage: " . $argv[0] . " [cipher text]\n";

    }

    // Quick (non-destructive) replace tool
    function deenc($word, $enc, $string) {
        $string = str_replace(str_split($enc), str_split(strtoupper($word)), $string);
        $string = str_replace(str_split($word), str_split($enc), $string);
        return strtolower($string);
    }

?>
jtjacques
quelle
Versuchen Sie es mit / usr / share / dict / words, wenn Sie sich auf einem System befinden, auf dem es vorhanden ist.
Keith Randall