Cypher: Python-Codes zum Lösen fortgeschrittener Rätsel (3-4, 4-3, 5-*, 6-5)

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

Der folgende Code zählt die Häufigkeit jedes Buchstabens. Sobald dies erledigt ist, ordnen Sie jeden Buchstaben entsprechend der Ausgabe und ETAOIN SHRDLU zu, und Sie werden die THEs identifizieren. Dann lässt sich das Problem ganz einfach mit Bleistift und Papier lösen.
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):

4-3: Frequenzanalyse

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)))

By Unvollkommenheitssatz

Hinterlasse einen Kommentar

ArabicEnglishFrenchGermanItalianJapaneseKoreanPortugueseSpanish