Wie ich einige fortgeschrittene Rätsel (3-4, 4-3, 5-1, 5-2, 5-3, 6-5) mit Python gelöst habe
Einleitung
Diese Anleitung zeigt, wie ich einige der fortgeschrittenen Rätsel mit Hilfe von Python gelöst habe. Ursprünglich löste ich die Rätsel mit Bleistift und druckte Screenshots (wie das Bild unten für 3-3; klicken Sie nicht zum Zoomen, wenn Sie nicht gespoilert werden möchten), aber für spätere Probleme hatte ich das Ausprobieren satt. Also habe ich Python dazu gebracht.
Hinweis: Um meine Codes in der Steam-Community zu posten, musste ich die Variablen für Listenindizes von i in x umbenennen, damit sie nicht als Markup-Tags geparst werden (meiner Meinung nach sollten die Markups innerhalb eines Code-Elements ignoriert werden).
3-4: Frequenzanalyse
from collections import Counter text = """ AJLPNYRJZJFLZYASGSKQGSMME JKEJPFSVLPJLKKEJELNNSPZKY NNYGNSGASGYZJGAKEYZYGASVW NJKSWSKECNLUJZKEJZEYCYZWS VOEKLGAHYKKJAZEJNYJZLKLGU ESPPJLAFHSPZJLFSVGJRJPYTL OYGJALZMJJKJPZUESSGJPLUEY NATYOEKZLYNEJPKMSEVGAPJAK SGZGLTJEYZCLGYSNL """.replace("\n", "") print(Counter(text))
Das Ergebnis (nicht zoomen, wenn Sie nicht gespoilert werden wollen):
Um eine polyalphabetische Substitutions-Chiffre (Vigenère-Chiffre) zu lösen, muss zunächst die Länge des Schlüssels ermittelt werden. Laut dem Hinweis im Spiel gibt es (mindestens) zwei sich wiederholende 3-Buchstaben-Sequenzen: DUF und LUE. Indem wir die Erscheinungsspannen für jeden von ihnen zählen und dann ihren größten gemeinsamen Teiler nehmen, erhalten wir die wahrscheinlichste Länge des Schlüssels: 3.
Sobald die Länge des Schlüssels identifiziert ist, können wir die Technik der Frequenzanalyse wie bei monoalphabetischen Substitutions-Chiffren verwenden. Der folgende Code unterteilt den Chiffretext in „Phasen“, je nachdem, welcher Buchstabe des Schlüssels angewendet wird, und zählt die Häufigkeit der Buchstaben innerhalb der Phase.
from collections import Counter text = """\ LAFLUIWOYWPADUFHSNBVSWVNDZQDUF RBPLUYQPLWLPHZRLUEDUBSYMIPRDIJ HTYQUCUZYLKFRSKHZBUHULUEKPQFOY LYSSAMWOCWHZOLGDTDDPPOFDDTGOPY UDGWOYOSDRYKVVDVLAULRZYGWPLJZY QKYPTWVLJIAFHHSWOMUVDDAPLMJLUE PVLRNPDWFXWMQAFHZSEQCFAGQDFLJF LHLDSWCLMQLFXUBULBDUBVPVWFQHWY UHRHJGSOCUZZXAGFVLILQVAFDARKPQ LZCQAGULJBUCZAMPL\ """.replace("\n", "") phase1 = "".join(text[x] for x in range(len(text)) if x % 3 == 0) phase2 = "".join(text[x] for x in range(len(text)) if x % 3 == 1) phase3 = "".join(text[x] for x in range(len(text)) if x % 3 == 2) triplets = (text[x - 2] + text[x - 1] + text[x] for x in range(2, len(text), 3)) print(Counter(phase1)) print(Counter(phase2)) print(Counter(phase3)) print(Counter(triplets))
Der Code versucht auch, sich wiederholende 3-Buchstaben-Sequenzen wie DUF und LUE zu finden. Ich dachte, dass es nicht notwendig ist, die Sequenzen in den anderen Phasen als 1-2-3 zu finden. Leider hat das nicht gereicht, um im Klartext zu finden, was „DAS“ ist.
4-3: Brute-Force-Angriff
Nachdem ich einige Verschiebungen gemäß der Frequenzanalyse ausprobiert hatte, entschied ich mich, meinen Computer ausprobieren zu lassen.
Der Code gibt mehrere Kandidaten für die Lösung aus, also müssen wir den richtigen manuell auswählen.
from string import ascii_uppercase as alphabets from itertools import product text = """\ LAFLUIWOYWPADUFHSNBVSWVNDZQDUF RBPLUYQPLWLPHZRLUEDUBSYMIPRDIJ HTYQUCUZYLKFRSKHZBUHULUEKPQFOY LYSSAMWOCWHZOLGDTDDPPOFDDTGOPY UDGWOYOSDRYKVVDVLAULRZYGWPLJZY QKYPTWVLJIAFHHSWOMUVDDAPLMJLUE PVLRNPDWFXWMQAFHZSEQCFAGQDFLJF LHLDSWCLMQLFXUBULBDUBVPVWFQHWY UHRHJGSOCUZZXAGFVLILQVAFDARKPQ LZCQAGULJBUCZAMPL\ """.replace("\n", "") def char_to_num(char): return alphabets.index(char) + 1 def vigenere_sub(char1, char2): return alphabets[(char_to_num(char1) - char_to_num(char2) - 1) % 26] for key_chars in product(alphabets, repeat=3): decryption = "" for x in range(len(text)): decryption += vigenere_sub(text[x], key_chars[x % 3]) if "THE" in decryption and "AND" in decryption: print(key_chars) print(decryption) print()
Wie der obere rechte Teil des Bildes vermuten lässt, wusste ich infolge des Brute-Force-Angriffs, dass ich das Rätsel durch die Frequenzanalyse fast gelöst hatte, gab es aber auf, nicht zuversichtlich genug zu sein, um fortzufahren. (Zoomen Sie das Bild nicht, wenn Sie nicht gespoilert werden möchten.)
5-1, 5-2, 5-3
Für mechanisierte Kryptographie-Rätsel können wir einen Code schreiben, um die Funktionalitäten von Enigma zu emulieren. Wir können den gleichen Code verwenden, um alle zu lösen, außer:
- Für das zweite Rätsel brauchen wir etwas Trial-and-Error oder ein bisschen Brute-Force, um die anfängliche Drehung der Scheibe zu bestimmen.
- Für das letzte Rätsel brauchen wir die Funktion, um einige Buchstabenpaare zu vertauschen.
from string import ascii_uppercase as keyboard scrambler1 = (keyboard, "UWYGADFPVZBECKMTHXSLRINQOJ") scrambler2 = (keyboard, "AJPCZWRLFBDKOTYUQGENHXMIVS") scrambler3 = (keyboard, "TAGBPCSDQEUFVNZHYIXJWLRKOM") reflector = (keyboard, "YRUHQSLDPXNGOKMIEBFZCWVJAT") def scramble(position, disk, backwards=False): row1, row2 = disk[::-1] if backwards else disk char = row1[position] return row2.index(char) def rotate(disk, n): return (disk[0][n:] + disk[0][:n], disk[1][n:] + disk[1][:n]) def decrypt(char, *disks): x = keyboard.index(char) for disk in disks: x = scramble(x, disk) for disk in disks[-2::-1]: x = scramble(x, disk, True) return keyboard[x] def decrypt_text(text, *disks): decryption = "" disk1 = disks[0] for char in text: disk1 = rotate(disk1, 1) decryption += decrypt(char, disk1, *disks[1:]) return decryption def swap(text, key): result = "" for char in text: result += key.replace(char, "") if char in key else char return result def swap_keys(text, keys): for key in keys: text = swap(text, key) return text def puzzle_5_1(): print(decrypt_text("ZYDNI", scrambler1, reflector)) def puzzle_5_2(): for i in range(26): scrambler = rotate(scrambler1, i) text = decrypt_text("QHSGUWIG", scrambler, reflector) if text[:2] == "XV": print(text) def puzzle_5_3(): text = "GYHRVFLRXY" keys = ("AB", "SZ", "UY", "GH", "LQ", "EN") text = swap_keys(text, keys) d1 = rotate(scrambler2, scrambler2[0].index("A")) d2 = rotate(scrambler1, scrambler1[0].index("E")) d3 = rotate(scrambler3, scrambler3[0].index("B")) text = decrypt_text(text, d1, d2, d3, reflector) text = swap_keys(text, keys) print(text) puzzle_5_1() puzzle_5_2() puzzle_5_3()
6-5
Das Rätsel ist einfach – den Algorithmus einfach rückwärts ausführen. Wenn Sie dies jedoch von Hand lösen, kann ein kleiner Fehler alles durcheinander bringen. Aus diesem Grund habe ich Python verwendet (oder musste ich gestehen), um dies zu lösen.
Beachten Sie, dass ich den Algorithmus neu interpretiert habe: Anstatt das Zeilentauschen sowohl auf den Text als auch auf den Schlüssel anzuwenden und sie dann mit XOR zu verknüpfen, habe ich die Anweisung so interpretiert, dass sie zuerst mit XOR verknüpft und dann die Zeilen ausgetauscht wurden – die Ergebnisse sind das Gleiche.
def block(bitstr): return (bitstr[:4], bitstr[4:8], bitstr[8:12], bitstr[12:]) def swap(rows): return tuple(row[1] + row[0] + row[3] + row[2] for row in rows) def xor(rows, key): keybits = "".join(bin(ord(char))[2:].zfill(8) for char in key) if len(keybits) != 16: raise AssertionError textbits = "".join(rows) val = "".join("0" if keybits[x] == textbits[x] else "1" for x in range(16)) return block(val) def shift(rows): result = [] for n in range(len(rows)): row = rows[n] result.append(row[n:] + row[:n]) return tuple(result) def decrypt(cipher, key): return xor(swap(shift(cipher)), key) ciphertext = block("1001011110110101") first = decrypt(ciphertext, "BX") second = decrypt(first, "YS") print(chr(int(second[0] + second[1], 2))) print(chr(int(second[2] + second[3], 2)))