Cypher: códigos Python para resolver quebra-cabeças avançados (3-4, 4-3, 5-*, 6-5)

Como resolvi alguns quebra-cabeças avançados (3-4, 4-3, 5-1, 5-2, 5-3, 6-5) usando Python

 

Introdução

Este guia mostra como resolvi alguns quebra-cabeças avançados com a ajuda do Python. Originalmente eu estava resolvendo os quebra-cabeças com um lápis e capturas de tela impressas (como a imagem abaixo para 3-3; não clique para ampliar se não quiser ser estragado), mas para problemas posteriores me cansei de tentativas e erros. Então eu tenho Python para fazer isso.

Nota: Para postar meus códigos na Comunidade Steam, tive que renomear as variáveis ​​para os índices da lista de i para x para evitar que fossem analisados ​​como tags de marcação (na minha opinião, as marcações devem ser ignoradas dentro de um elemento de código).

3-4: Análise de frequência

O código abaixo conta a frequência de cada letra. Feito isso, atribua cada letra de acordo com a saída e ETAOIN SHRDLU, e você identificará os THE's. Então o problema é facilmente resolvido com lápis e papel.
from collections import Counter

text = """
AJLPNYRJZJFLZYASGSKQGSMME
JKEJPFSVLPJLKKEJELNNSPZKY
NNYGNSGASGYZJGAKEYZYGASVW
NJKSWSKECNLUJZKEJZEYCYZWS
VOEKLGAHYKKJAZEJNYJZLKLGU
ESPPJLAFHSPZJLFSVGJRJPYTL
OYGJALZMJJKJPZUESSGJPLUEY
NATYOEKZLYNEJPKMSEVGAPJAK
SGZGLTJEYZCLGYSNL
""".replace("\n", "")

print(Counter(text))


O resultado (não amplie se você não quer ser estragado):

4-3: Análise de frequência

Para resolver uma cifra de substituição polialfabética (cifra de Vigenère), a primeira coisa a fazer é descobrir o comprimento da chave. De acordo com a dica do jogo, existem (pelo menos) duas sequências de 3 letras repetidas: DUF e LUE. Contando os intervalos de aparência para cada um deles e, em seguida, tomando seu máximo divisor comum, obtemos o comprimento mais provável da chave: 3.

Uma vez identificado o comprimento da chave, podemos usar a técnica de análise de frequência como nas cifras de substituição monoalfabéticas. O código abaixo divide o texto cifrado em “fases” dependendo de qual letra da chave é aplicada e conta a frequência das letras dentro da fase.

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


O código também tenta encontrar sequências de 3 letras repetidas como DUF e LUE. Achei que não era necessário encontrar as sequências nas fases diferentes de 1-2-3. Infelizmente, isso não foi suficiente para encontrar o que é “THE” no texto simples.

4-3: Ataque de Força Bruta

Depois de tentar alguns turnos de acordo com a análise de frequência, decidi testar meu computador.

O código gera vários candidatos para a solução, então temos que escolher o correto manualmente.

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


Como a parte superior direita da imagem pode sugerir, como resultado do ataque de força bruta, eu sabia que quase havia resolvido o quebra-cabeça pela análise de frequência, mas desisti de não estar confiante o suficiente para prosseguir. (Não amplie a imagem se não quiser estragar.)

5-1, 5-2, 5-3

Para quebra-cabeças de criptografia mecanizada, podemos escrever um código para emular as funcionalidades do Enigma. Podemos usar o mesmo código para resolver todos eles, exceto:

  • Para o segundo quebra-cabeça, precisamos de um pouco de tentativa e erro, ou um pouco de força bruta, para determinar a rotação inicial do disco.
  • Para o último quebra-cabeça, precisamos da função para trocar alguns pares de letras.
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

O quebra-cabeça é simples - basta executar o algoritmo de trás para frente. No entanto, resolver isso manualmente, um pequeno erro pode estragar tudo. Por isso usei (ou tive que usar, confesso) Python para resolver isso.

Observe que eu reinterpretei o algoritmo: em vez de aplicar a troca de linha ao texto e à chave e depois fazer o XOR, interpretei a instrução como primeiro fazendo o XOR e depois trocando as linhas - os resultados sendo o mesmo.

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 unvollstaendigkeitssatz

Deixe um comentário

ArabicEnglishFrenchGermanItalianJapaneseKoreanPortugueseSpanish