"Os estudiosos árabes... inventaram a criptoanálise, a ciência de decifrar uma mensagem sem o conhecimento da chave." (Simon Singh, O Livro dos Códigos)
Podemos hackear a cifra de César usando uma técnica criptoanalítica chamada força bruta. Um ataque de força bruta tenta todas as chaves de descriptografia possíveis para uma cifra. Nada impede um criptoanalista de adivinhar uma chave, descriptografar o texto cifrado com essa chave, olhar a saída e então passar para a próxima chave se não encontrar a mensagem secreta. Como a técnica de força bruta é tão eficaz contra a cifra de César, você não deve realmente usar a cifra de César para criptografar informações secretas.
Idealmente, o texto cifrado nunca cairia nas mãos de ninguém. Mas o princípio de Kerckhoffs (nomeado em homenagem ao criptógrafo do século XIX Auguste Kerckhoffs) afirma que uma cifra ainda deve ser segura mesmo que todos saibam como a cifra funciona e outra pessoa tenha o texto cifrado. Este princípio foi reafirmado pelo matemático do século XX Claude Shannon como a máxima de Shannon: "O inimigo conhece o sistema". A parte da cifra que mantém a mensagem em segredo é a chave, e para a cifra de César esta informação é muito fácil de encontrar.
Princípio de Kerckhoff e máxima de Shannon;
A técnica da força bruta;
A função range() e;
Formatação de string (interpolação de string).
mensagem = 'R66nJéJnJzv1unJzr16ntrzJ6rp5r7nM'
SIMBOLOS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !?.'
# --- Iterar sobre cada possível chave --- #
for chave in range(len(SIMBOLOS)):
# --- Importante configurar uma variável em branco para cada nova chave --- #
traducao = ''
# --- Iterar sobre cada símbolo presente na mensagem --- #
for simbolo in mensagem:
if simbolo in SIMBOLOS:
simbolo_indice = SIMBOLOS.find(simbolo)
traducao_indice = simbolo_indice - chave
# --- "Enrolar" --- #
if traducao_indice < 0:
traducao_indice += len(SIMBOLOS)
# --- Adicionar o símbolo decriptografado --- #
traducao += SIMBOLOS[traducao_indice]
# --- Repetir o símbolo da mensagem caso não esteja em SIMBOLOS --- #
else:
traducao += simbolo
# --- Mostrar cada possível decriptação --- #
print(f'Chave #{chave}: {traducao}')
Observe que boa parte desse código é o mesmo que o código no programa original de cifra de César. Isso ocorre porque o programa hacker de cifra de César usa os mesmos passos para decifrar a mensagem.
O programa para hackear de cifra de César quebra o texto cifrado R66nJéJnJzv1unJzr16ntrzJ6rp5r7nM descriptografando o texto cifrado com todas as 66 chaves possíveis.
Como a saída descriptografada para a chave 13 é em português, sabemos que a chave de criptografia original deve ter sido 13.
O programa hacker criará uma variável mensagem que armazena a sequência de texto cifrado que o programa tenta decifrar. A variável constante SIMBOLOS contém todos os caracteres que a cifra pode criptografar:
mensagem = 'R66nJéJnJzv1unJzr16ntrzJ6rp5r7nM'
SIMBOLOS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !?.'
O valor para SIMBOLOS precisa ser o mesmo que o valor para SIMBOLOS usado no programa de cifra de César que criptografou o texto cifrado que estamos tentando hackear; caso contrário, o programa hacker não funcionará. Observe que há um único espaço entre 0 e ! no valor da string.
Aqui o loop for não itera sobre um valor de string, mas itera sobre o valor de retorno de uma chamada para a função range():
for chave in range(len(SIMBOLOS)):
A função range() recebe um argumento inteiro e retorna um valor do tipo de dado range. Valores range podem ser usados em loops for para fazer um loop um número específico de vezes de acordo com o inteiro que você dá à função. Vamos tentar um exemplo:
for i in range(3):
print('olá')
O loop for executará três loops porque passamos o inteiro 3 para range().
Mais especificamente, o valor do intervalo retornado da chamada da função range() definirá a variável do loop for para os inteiros de 0 a (mas não incluindo) o argumento passado para range(). Por exemplo:
for i in range(6):
print(i)
Este código define a variável i para os valores de 0 a (mas não incluindo) 6. A do primeiro for define a variável chave com os valores de 0 a (mas não incluindo) 66. Em vez de codificar o valor 66 diretamente em nosso programa, usamos o valor de retorno de len(SIMBOLOS) para que o programa ainda funcione se modificarmos SIMBOLOS.
Na primeira vez que a execução do programa passa por esse loop, a chave é definida como 0, e o texto cifrado em mensagem é descriptografado com a chave 0. (Claro, se 0 não for a chave real, a mensagem apenas “descriptografa” será um absurdo.) O código dentro do loop for, que veremos a seguir, é semelhante ao programa de cifra de César original e faz a descriptografia. Na próxima iteração do loop for, a chave é definida como 1 para a descriptografia.
Embora não o utilizemos neste programa, você também pode passar dois argumentos inteiros para a função range() em vez de apenas um. O primeiro argumento é onde o intervalo deve começar, e o segundo argumento é onde o intervalo deve parar (até, mas não incluindo, o segundo argumento). Os argumentos são separados por uma vírgula:
for i in range(2, 6):
print(i)
A variável i assumirá o valor de 2 (incluindo 2) até o valor 6 (mas não incluindo 6).
O código de descriptografia nas próximas linhas adiciona o texto descriptografado ao final da string em translated. A variável traducao é definido como uma string em branco:
# --- Iterar sobre cada possível chave --- #
for chave in range(len(SIMBOLOS)):
# --- Importante configurar uma variável em branco para cada nova chave --- #
traducao = ''
É importante redefinirmos traducao para uma string em branco no início deste loop for; caso contrário, o texto que foi descriptografado com a chave atual será adicionado ao texto descriptografado na traducao da última iteração do loop.
As linhas a seguir são quase as mesmas do código do programa de cifra de César no Capítulo 5, mas são um pouco mais simples porque esse código só precisa ser decifrado:
# --- Iterar sobre cada símbolo presente na mensagem --- #
for simbolo in mensagem:
if simbolo in SIMBOLOS:
simbolo_indice = SIMBOLOS.find(simbolo)
Fazemos um loop por cada símbolo na sequência de texto cifrado armazenada em mensagem. Em cada iteração desse loop, a linha seguinte verifica se simbolo existe na variável constante SIMBOLOS e, se sim, o descriptografa. A chamada do método find()localiza o índice onde simbolo está em SIMBOLOS e o armazena em uma variável chamada simbolo_indice. Então, subtraímos a chave de simbolo_indice para descriptografar:
traducao_indice = simbolo_indice - chave
# --- "Enrolar" --- #
if traducao_indice < 0:
traducao_indice += len(SIMBOLOS)
Esta operação de subtração pode fazer com que o traducao_indice se torne menor que zero e exija que façamos um "enrolar" da constante SIMBOLOS quando encontrarmos a posição do caractere em SIMBOLOS para descriptografar. A linha com o if verifica esse caso, e a linha seguinte adiciona 66 (que é o que len(SIMBOLOS) retorna) se o traducao_indice for menor que 0.
Agora que o traducao_indice foi modificado, SIMBOLOS[traducao_indice] será avaliado como o símbolo descriptografado. O símbolo ao final da string é armazenada em traducao:
# --- Adicionar o símbolo decriptografado --- #
traducao += SIMBOLOS[traducao_indice]
# --- Repetir o símbolo da mensagem caso não esteja em SIMBOLOS --- #
else:
traducao += simbolo
Adicionamos apenas o simbolo não modificado ao final de traducao se o valor não foi encontrado no conjunto SIMBOLOS.
Usando formatação de string para exibir a chave e as mensagens descriptografadas
Embora haja apenas uma única chamada de função print() em nosso programa hacker de cifra de César, ela executará várias linhas porque é chamada uma vez por iteração do loop for:
# --- Mostrar cada possível decriptação --- #
print(f'Chave #{chave}: {traducao}')
O argumento para a chamada da função print() é um valor de string que usa formatação de string (também chamada de interpolação de string).
Exercício 01: Quebre o texto cifrado a seguir, descriptografando uma linha de cada vez, pois cada linha tem uma chave diferente:
mOe9GIeG?OeA9NCHBI, 34 (Eu amo meu gatinho,)
5IYoKEXMRLSoQIoEQE, 44 (Meu gatinho me ama,)
Q2u1vzDzvtvzD1ãvDmlsp7lzDx2hu1vDwvkltvzDzly, 7 (Juntos somos tão felizes quanto podemos ser, )
kE8GJ7cEAF.7c978 ç7cL F.7cKMKH AL7K, 32 (Embora minha cabeça tenha suspeitas, )
0ZJpJZpLZFWITpIJGFNcTpITpRJZpHMFUéZ, 45 (Que eu guardo debaixo do meu chapéu, )
PH4pHp6Hpynzwsp44pHl5éHzH5lxlyszHopH6xH3l5zJ 11 (E se eu encolhesse até o tamanho de um rato? )
Tjn,!fmb!qspwbwfmnfouf!nf!dpnfsjbA 1 (Sim, ela provavelmente me comeria. )
A fraqueza crítica da cifra de César é que não há muitas chaves possíveis que podem ser usadas para criptografar. Qualquer computador pode facilmente descriptografar com todas as 66 chaves possíveis, e um criptoanalista leva apenas alguns segundos para olhar as mensagens descriptografadas para encontrar a que está em português. Para tornar nossas mensagens mais seguras, precisamos de uma cifra que tenha mais chaves potenciais. A cifra de transposição discutida no Capítulo 7 pode fornecer essa segurança para nós.