Marcel Proust und Markov entschlüsseln die T9-Texte des Sicherheitsdienstes

11

Als ob diese Herausforderung im Geiste pythonesker sein könnte ... Es sind keine Vorkenntnisse in Markov-Ketten oder Verschlüsselungstechniken erforderlich.

Sie sind ein Spion, der wichtige Informationen vom britischen Sicherheitsdienst M1S erhalten muss. Die Agenten von M1S sind sich bewusst, dass ihre Wi-Fi-Signale abgefangen, ihre Sicherheitslücken in Android / iOS ausgenutzt usw. werden können. Daher verwenden alle Nokia 3310s, um Textinformationen zu übertragen, die mit der automatischen Vervollständigung des T9 eingegeben wurden .

Sie hatten zuvor die Telefone gehackt, die an den Geheimdienst geliefert werden sollten, und Keylogger unter ihren prächtigen Plastiktastaturen installiert. Jetzt erhalten Sie Zahlenfolgen, die den eingegebenen Buchstaben entsprechen, sodass „ der Adler das Nest verlassen hat, um die Agenten zu alarmieren

84303245304270533808430637802537808430243687

Aber warte! Einige T9-Sequenzen sind mehrdeutig ("6263" könnte "Name", "Mähne" oder "Oboe" sein; je dunkler, desto verdächtiger wird es!). Was machen Sie also? Sie wissen, dass die einzige Aufnahmeprüfung, die M1S verwendet, darin besteht, Marcel Prousts Meisterwerk „Remembrance of Things Past“ in 15 Sekunden zusammenzufassen. Sie möchten also das Wort, das nach dem vorherigen kommt, anhand seiner Häufigkeitsverteilung im gesamten Chef-d 'auswählen. œuvre of Proust!

Können Sie den Code knacken und die ursprüngliche Nachricht erhalten?

Das Prinzip von T9

Von den Agenten verwendete Nokia 3310-Tastaturen

Der T9-Mechanismus zur automatischen Vervollständigung kann wie folgt beschrieben werden. Es ordnet alphabetische Zeichen Zahlen zu, wie im obigen Bild gezeigt.

abc     -> 2
def     -> 3
ghi     -> 4
jkl     -> 5
mno     -> 6
pqrs    -> 7
tuv     -> 8
wxyz    -> 9
<space> -> 0
<other> -> <is deleted>

Der T9-Entschlüsseler empfängt eine Folge von Ziffern und versucht, das Wort zu erraten, das mit diesen Tastendrücken eingegeben werden könnte. Es könnte eine Standardfrequenztabelle verwenden, aber wir gehen noch einen Schritt weiter und sagen das nächste Wort mithilfe einer Markov-Kette voraus!

Lernbeispiel

Der Korpus ist diese stark gestrippt Version von Prousts „ nach dem verlorenen Zeit“ ( s/-/ /g, s/['’]s //gund s/[^a-zA-Z ]//g- begone verwirrend besitzergreifend 's!) , Die ursprünglich auf der veröffentlichte University of Adelaide Webseite (der Text dieser Arbeit ist in der Public Domain in Australien).

Der gesamte Text muss als eine Zeichenfolge, als ein langer Satz, als ein langer Vektor von Wörtern (je nachdem, was für Ihre Sprache am bequemsten ist) analysiert , von Zeilenumbrüchen befreit und an Leerzeichen in Wörter aufgeteilt werden . (Ich liefere keine Datei mit einem Absatz, da diese möglicherweise von Github-Tools verpönt wird.)

Wie lese ich den gesamten Text als eine Zeichenfolge / einen Satz? Ein Beispiel in R :

p_raw  <- read.table("proust.txt", sep="\t") # Because there are no tabs
p_vec  <- as.character(p_raw$V1)       # Conversion to character vector
p_str  <- paste(p_vec, collapse=" ")   # One long string with spaces
p_spl  <- strsplit(p_str, split=" ")[[1]] # Vector of 1360883 words
proust <- p_spl[p_spl!=""]           # Remove empty entries — 1360797

Aufgabe

Geben Sie bei einer gegebenen Folge von Ziffern als Zahl eine mögliche Textzeichenfolge zurück, die mit den entsprechenden T9-Tasten unter Verwendung einer Wahrscheinlichkeitskette eingegeben werden kann, um das nächste Wort X basierend auf diesem als ein langer Satz behandelten Trainingstext vorherzusagen .

Wenn X das erste T9-Wort des Textes ist und es mehrere Vermutungen gibt, wählen Sie eine zufällig aus, andernfalls die einzig mögliche.

Für alle nachfolgenden T9-Wörter X (i), denen ein bereits entschlüsseltes Wort w (i-1) vorangestellt ist :

  1. Wenn ein T9-Wort X auf einzigartige Weise in ein normales Wort x umgewandelt werden kann, tun Sie dies.
  2. Wenn für X mehrere Konvertierungsoptionen verfügbar sind, z . B. x1, x2, ... , suchen Sie das vorhergehende erratene Wort w nach .
    • Wenn auf w niemals etwas folgt, das in der Originalarbeit von Proust X zugeordnet ist , wählen Sie zufällig eines der möglichen x1, x2, ... aus .
    • Wenn w X im Original immer w x1 entspricht und es keine gleichzeitigen xi gibt , die auf X abgebildet werden könnten , wählen Sie x1 .
    • Wenn w X in w x1 , w x2 , ... konvertiert werden kann, die sich im Korpus befinden, zählen Sie alle möglichen xi , die w folgen, und ordnen Sie sie X im Korpus zu, und wählen Sie xi mit der Wahrscheinlichkeit xi / (x1) aus + x2 + ...) .

Beispiel 2a. Wenn die Nachricht ist 76630489, wo 489könnte guyoder ivy(sie kommen mindestens einmal im Korpus vor), 7663kann sie als some(ein sehr wahrscheinliches erstes Wort) entschlüsselt werden . Wenn someniemals etwas folgt, das 489im Korpus abgebildet ist, wählen Sie guyoder ivyzufällig mit einer Wahrscheinlichkeit von 0,5.

Beispiel 2b. Wenn die Nachricht ist 766302277437, wo 2277437könnte barrieroder kann carrier, 7663kann als entschlüsselt werden some. Wenn Proust immer verwendet hat some carrierund nie some barrier, dann wählen Sie some carrier.

Beispiel 2c. Angenommen, Sie möchten die Sequenz entschlüsseln 536307663. 5363wurde vorhergesagt als lend. 7663eine dieser sein könnte: pond, roofund some. Sie zählen die Vorkommen des folgenden Wortes lendim Beispielkorpus. Angenommen, Sie erhalten so etwas (nur zur Veranschaulichung):

        T9  Word following lend  Occurrences
      7663  some                           7
      7663  pond                           2
      7663  roof                           1

Wenn 7663also vorangestellt wird lend, gibt es eine 7/(7+2+1)=70%Wahrscheinlichkeit, 7663die für some20% pondund 10% steht roof. Ihr Algorithmus sollte lend somein 70% der Fälle, lend pondin 20% der Fälle usw. produzieren.

Sie können davon ausgehen, dass die Agenten nur az-Buchstaben und Leerzeichen verwenden (keine Satzzeichen, keine Possessiv- 'sund keine Zahlen).

Sie können auch davon ausgehen, dass die Agenten von M1S niemals Wörter außerhalb des Bereichs „Erinnerung an vergangene Dinge“ verwenden (was ein unglaubliches Vokabular von 29.237 Wörtern ist!).

Die T9-Funktionalität wurde in dieser Herausforderung implementiert , sodass Sie sie sich ansehen können.

Wenn Sie Hilfe benötigen, wurden probabilistische Ketten in dieser , jener und den folgenden Herausforderungen herrlich gezähmt , aber Sie müssen nicht einmal das Prinzip solcher Ketten kennen: Alles ist in der Aufgabe angegeben.

Testfälle

--Inputs--
20784250276960369
20784250276960369
84303245304270533808430637802537808430243687
94280343084306289072908608430262780482737
94280343084306289072908608430262780482737

--Possible outputs--
c quick brown fox
a stick crown fox
the eagle gas left the nest blest vie agents
what did the navy pay to the coast guards
what did the navy raz un the coast guards

Regeln:

  • Es gelten Standardlücken .
  • Sie kennen die ursprüngliche Nachricht nicht. Sie erhalten lediglich eine Folge von Ziffern und die Datei proust.txt , die Sie nur in den Speicher / Arbeitsbereich / was auch immer laden müssen. Es besteht keine Notwendigkeit, etwas in sich geschlossenes zu haben; Angenommen, proust.txtist immer zugänglich.
  • Ihr Algorithmus muss in der Lage sein, unterschiedliche Ausgaben mit entsprechenden Wahrscheinlichkeiten zu erzeugen, wenn je nach Korpus mehr als eine Entschlüsselungsoption wahrscheinlich ist (siehe Beispiel 2c).

Sie müssen so diskret wie möglich bleiben, damit der kürzeste Code gewinnt!

PS Der offensichtliche Vorteil dieses probabilistischen Algorithmus ist die Tatsache, dass die Wahrscheinlichkeit, dass Sie eine echte Originalzeichenfolge für eine mehrdeutige entschlüsselte Zeichenfolge erhalten, zu eins tendiert - warten Sie einfach ...

PPS Siehe auch Vorhersage durch partielle Übereinstimmung .

Andreï Kostyrka
quelle
Peter Taylors Bemerkungen aus dem Sandkasten wurden berücksichtigt. Leider haben in der Woche, in der es dort veröffentlicht wurde, trotz mehrfacher Updates nur sehr wenige Personen geantwortet. Vorschläge sind daher willkommen! Übrigens, das ist meine erste Herausforderung!
Andreï Kostyrka
Ich vermute, dass ein wichtiger Grund dafür, dass Sie nicht viele Antworten erhalten haben, das fortgeschrittene Wissen ist, das zum Verständnis dieses Problems erforderlich ist. Wenn Sie sich dieser Herausforderung zu Appell an ein größeres Publikum zu wollen, würde ich auch einige frühere Beispiele empfehlen, die die Markow - Kette bei der Arbeit zeigen :)
Nathan Merrill
@ NathanMerrill OK, ich habe 3 Links zu Beispielherausforderungen hinzugefügt. Ein Benutzer muss Markov-Ketten jedoch überhaupt nicht kennen, da die Aufgabe im Fragetext so algorithmisch wie möglich beschrieben wird: Wenn X, mache Y mit Wahrscheinlichkeiten, die durch Berechnung von Z in dieser Lernstichprobe erhalten werden. Ich habe versucht, es so autark wie möglich zu machen ...
Andreï Kostyrka
Oh, ich verstehe. Wenn Sie es nicht erklärt hätten, hätte ich dafür gestimmt, es zu schließen. Es sieht nur so aus, als ob es fortgeschrittenes Wissen braucht :)
Nathan Merrill
1
Ich mag diese Herausforderung, aber ich hatte noch keine Zeit, mich hinzusetzen und eine Lösung zu entwickeln / Golf zu spielen. Hoffentlich passiert das bald.
Mego

Antworten:

1

R-Lösung, nicht konkurrierende Darstellung dessen, was getan werden kann

Zunächst laden wir die Wortfolge in den Speicher:

p_raw  <- read.table("proust.txt", sep="\t") # Because there are no tabs
p_vec  <- as.character(p_raw$V1)       # Conversion to character vector
p_str  <- paste(p_vec, collapse=" ")   # One long string with spaces
p_spl  <- strsplit(p_str, split=" ")[[1]] # Vector of 1360883 words
proust <- p_spl[p_spl!=""]           # Remove empty entries — 1360797

Zweitens brauchen wir eine Funktion, die T9-ifies jeden Text enthält:

t9 <- function (x) {
  x <- chartr(paste(c(letters, " "), collapse=""), "222333444555666777788899990", tolower(x))
  x <- gsub("[^0-9]", "", x, perl = TRUE) # Safety check
  x <- x[x!=""] # Also for safety because... you know...
  x
}

Dann haben wir T9-ify Proust:

p9 <- t9(proust)

Letzte Vorbereitung: Wir teilen die Eingabezeichenfolge mit einer von uns aufgerufenen Funktion auf Null prep):

prep <- function (x) {
  x <- chartr("0", " ", x)
  x <- strsplit(x, " ")[[1]]
  x <- x[x!=""] # Boil the empty strings for safety
  x
}

Und jetzt schlage ich eine Funktion vor, die eine beliebige Eingabezeichenfolge von Zahlen verwendet prepund die Wörter einzeln entschlüsselt:

decip <- function(x, verbose = FALSE) {
  x <- prep(x)
  l <- length(x)
  decrypted <- rep(NA, l)
  tb <- table(proust[which(p9 == x[1])])
  decrypted[1] <- sample(names(tb), 1, prob=tb/sum(tb))
  if (verbose) print(decrypted[1])
  for (i in 2:l) {
    mtchl <- p9 == x[i]
    mtch <- which(mtchl)  # Positions that matched
    pmtch <- proust[mtch] # Words that matched
    tb <- table(pmtch)    # Count occurrences that matched
    if (length(tb)==1) {  # It is either 1 or >1
      decrypted[i] <- names(tb)[1]
      if (verbose) print(paste0("i = ", i, ", case 1: unique decryption"))
      } else {  # If there are more than one ways to decipher...
      preced <- proust[mtch-1] 
      s <- sum(preced==decrypted[i-1])
      if (s==0) {
        decrypted[i] <- sample(names(tb), 1)
        if (verbose) print(paste0("i = ", i, ", case 2a: multiple decryption, collocation never used, picking at random"))
        } else {
        tb2 <- table(pmtch[preced==decrypted[i-1]])
        if (length(tb2)==1) {
          decrypted[i] <-  names(tb2)[1]
          if (verbose) print(paste0("i = ", i, ", case 2b: multiple decryption, only one collocation found, using it"))
        } else {
          decrypted[i] <- sample(names(tb2), 1, prob = tb2/sum(tb2))
          if (verbose) print(paste0("i = ", i, ", case 2c: multiple decryption, ", length(tb2), " choices"))
          }
      }
    }
    if(verbose) print(decrypted[i])
  }
  decrypted
}

Und jetzt, was es tatsächlich tut:

decip("20784250276960369", verbose=TRUE)
----
[1] "a"
[1] "i = 2, case 2c: multiple decryption, 2 choices"
[1] "quick"
[1] "i = 3, case 2a: multiple decryption, collocation never used, picking at random"
[1] "brown"
[1] "i = 4, case 1: unique decryption"
[1] "fox"
[1] "a"     "quick" "brown" "fox" 

Zweites Beispiel:

decip("84303245304270533808430637802537808430243687", verbose=TRUE)
----
[1] "what"
[1] "i = 2, case 2b: multiple decryption, only one collocation found, using it"
[1] "did"
[1] "i = 3, case 2b: multiple decryption, only one collocation found, using it"
[1] "the"
[1] "i = 4, case 1: unique decryption"
[1] "navy"
[1] "i = 5, case 2a: multiple decryption, collocation never used, picking at random"
[1] "raz"
[1] "i = 6, case 2a: multiple decryption, collocation never used, picking at random"
[1] "um"
[1] "i = 7, case 2a: multiple decryption, collocation never used, picking at random"
[1] "the"
[1] "i = 8, case 2b: multiple decryption, only one collocation found, using it"
[1] "coast"
[1] "i = 9, case 1: unique decryption"
[1] "guards"
[1] "what"   "did"    "the"    "navy"   "raz"    "um"     "the"    "coast"  "guards"

Bitte kommentieren Sie nicht, dass dies Golf gespielt werden kann. Es scheint, dass nur wenige Menschen aufgrund meiner schrecklichen Ausführlichkeit an dieser Herausforderung interessiert sind. Deshalb habe ich diese Antwort gepostet, um zu zeigen, wie ein mögliches Programm aussehen könnte. Sie müssen diese Antwort nicht positiv / negativ bewerten.

Andreï Kostyrka
quelle
1

Python 3, 316 Bytes

from random import*
from collections import*
def d(s,f):
 D=defaultdict(Counter);p=q=''
 for w in open(f).read().split():D[w.translate({97+c:(c-(c>17)-(c>24))//3+50for c in range(26)})].update([w]);D[p].update([w]);p=w
 for c in s.split('0'):q=choice([*(len(D[c])>1and D[c]&D[q]or D[c]).elements()]);print(q,end=' ')
RootTwo
quelle