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