"O GRANDE IRMÃO ESTÁ TE OBSERVANDO" (George Orwell, 1984)
No Capítulo 1, usamos uma roda de cifras e um gráfico de letras e números para implementar a cifra de César. Neste capítulo, implementaremos a cifra de César em um programa de computador.
A cifra reversa que fizemos no Capítulo 4 sempre criptografa da mesma forma. Mas a cifra de César usa chaves, que criptografam a mensagem de forma diferente dependendo de qual chave é usada. As chaves para a cifra de César são os inteiros de 0 a 25. Mesmo que um criptoanalista saiba que a cifra de César foi usada, isso por si só não dá a ele informações suficientes para quebrar a cifra. Ele também deve saber a chave.
Constantes
Loop for
Instruções if, elif e else
Operadores in e not in
Método find() em strings
# --- Mensagem que será criptografada/decriptografada --- #
mensagem = 'Essa é a minha mensagem secreta.'
# --- Chave de encriptação/decriptação --- #
chave = 13
# --- Informar se o programa irá encriptar ou decriptar a mensagem --- #
modo = 'encriptar'
# --- Símbolos usados para a criptagrafia --- #
SIMBOLOS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !?.'
# --- Armazenar a mensagem criptografada/decriptografada --- #
traducao = ''
for simbolo in mensagem:
# --- Apenas os símbolos presentes em SIMBOLOS podem ser traduzidos --- #
if simbolo in SIMBOLOS:
simbolo_indice = SIMBOLOS.find(simbolo)
# --- Encriptar/decriptar a mensagem --- #
if modo == 'encriptar':
traducao_indice = simbolo_indice + chave
elif modo == 'decriptar':
traducao_indice = simbolo_indice - chave
# --- Caso a posição do símbolo traduzido seja maior ou menor que o tamanho de SIMBOLOS --- #
if traducao_indice >= len(SIMBOLOS):
traducao_indice -= len(SIMBOLOS)
elif traducao_indice < 0:
traducao_indice += len(SIMBOLOS)
# --- Montar o texto traduzido --- #
traducao += SIMBOLOS[traducao_indice]
else:
# --- Caso o símbolo não esteja em SIMBOLOS --- #
traducao += simbolo
# --- Mostrar a tradução --- #
print(traducao)
Quando você executa o programa, a saída fica assim:
R66nJéJnJzv1unJzr16ntrzJ6rp5r7nM
A saída é a string 'Essa é a minha mensagem secreta.' criptografada com a cifra de César usando uma chave de 13.
Para descriptografar a mensagem, basta colar o texto de saída como o novo valor armazenado na variável mensagem. Em seguida, altere a instrução de atribuição para armazenar a string 'decriptar' na variável modo:
mensagem = 'R66nJéJnJzv1unJzr16ntrzJ6rp5r7nM'
chave = 13
modo = 'decriptar'
Quando você executa o programa agora, a saída se parece com isso:
Essa é a minha mensagem secreta.
Temos 3 variáveis no começo do programa:
# --- Mensagem que será criptografada/decriptografada --- #
mensagem = 'Essa é a minha mensagem secreta.'
# --- Chave de encriptação/decriptação --- #
chave = 13
# --- Informar se o programa irá encriptar ou decriptar a mensagem --- #
modo = 'encriptar'
A variável mensagem armazena a string a ser criptografada ou descriptografada, e a variável chave armazena o inteiro da chave de criptografia. A variável modo armazena a string 'encriptar', que faz o código mais adiante no programa criptografar a string em mensagem, ou 'decriptar', que faz o programa descriptografar em vez de criptografar.
Constantes são variáveis cujos valores não devem ser alterados quando o programa é executado. Por exemplo, o programa de cifra de César precisa de uma string que contenha todos os caracteres possíveis que podem ser criptografados com essa cifra de César. Como essa string não deve mudar, nós a armazenamos na variável constante chamada SIMBOLOS:
# --- Símbolos usados para a criptagrafia --- #
SIMBOLOS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !?.'
Símbolo é um termo comum usado em criptografia para um único caractere que uma cifra pode criptografar ou descriptografar. Um conjunto de símbolos é todo símbolo possível que uma cifra é configurada para criptografar ou descriptografar. Como usaremos o conjunto de símbolos muitas vezes neste programa e porque não queremos digitar o valor completo da string toda vez que ele aparecer no programa (podemos cometer erros de digitação, o que causaria erros), usamos uma variável constante para armazenar o conjunto de símbolos. Entramos com o código para o valor da string uma vez e o colocamos na constante SIMBOLOS.
Note que SIMBOLOS está todo em letras maiúsculas, que é a convenção de nomenclatura para constantes. Embora pudéssemos alterar SIMBOLOS como qualquer outra variável, o nome todo em letras maiúsculas lembra o programador de não escrever código que faça isso.
Como acontece com todas as convenções, não precisamos seguir esta. Mas fazer isso torna mais fácil para outros programadores entenderem como essas variáveis são usadas. (Pode até ajudar quando você estiver olhando para seu próprio código mais tarde).
O programa armazena uma string em branco em uma variável chamada traducao que posteriormente armazenará a mensagem criptografada ou descriptografada:
# --- Armazenar a mensagem criptografada/decriptografada --- #
traducao = ''
Assim como na cifra reversa no Capítulo 4, ao final do programa, a variável traduzida conterá a mensagem completamente criptografada (ou descriptografada). Mas por enquanto ela começa como uma string em branco.
Usamos um tipo de loop chamado loop for:
for simbolo in mensagem:
Lembre-se de que um loop while fará um loop enquanto uma determinada condição for True. O loop for tem um propósito ligeiramente diferente e não tem uma condição como o loop while. Em vez disso, ele faz um loop sobre uma string ou um grupo de valores.
Cada vez que a execução do programa passa pelo loop (isto é, em cada iteração pelo loop), a variável na declaração for (que é a variável simbolo) assume o valor do próximo caractere na variável que contém uma string (que neste caso é mensagem). A declaração for é semelhante a uma declaração de atribuição porque a variável é criada e recebe um valor, exceto que a declaração for percorre diferentes valores para atribuir a variável.
Por exemplo, digite o seguinte:
for letra in 'Python':
print(f'A letra é: {letra}')
Este código faz um loop sobre cada caractere na string 'Python'. Quando isso acontece, a variável letra assume o valor de cada caractere em 'Python' um de cada vez, em ordem. Para ver isso em ação, escrevemos um código no loop que imprime o valor de letra para cada iteração.
O loop for é muito parecido com o loop while, mas quando você só precisa iterar sobre caracteres em uma string, usar um loop for é mais eficiente. Você pode fazer um loop while agir como um loop for escrevendo um pouco mais de código:
i = 0 # 1
while i < len('Python'): # 2
letra = 'Python'[i] # 3
print(f'A letra é: {letra}') # 4
i += 1 # 5
Observe que este loop while funciona da mesma forma que o loop for, mas não é tão curto e simples quanto o loop for. Primeiro, definimos uma nova variável i como 0 antes da instrução while (1). Esta instrução tem uma condição que será avaliada como True enquanto a variável i for menor que o comprimento da string 'Python' (2). Como i é um inteiro e só rastreia a posição atual na string, precisamos declarar uma variável letra separada para manter o caractere na string na posição i (3). Então, podemos imprimir o valor atual de letra para obter a mesma saída que o loop for (4). Quando o código terminar de executar, precisamos incrementar i adicionando 1 a ele para mover para a próxima posição (5).
Para entender as próximas linhas, você precisa aprender sobre as instruções if, elif e else, os operadores in e not in e o método de string find(). Veremos isso nas seções a seguir.
Há outro tipo de instrução Python: a instrução if:
if simbolo in SIMBOLOS:
Você pode ler uma declaração if como, “Se esta condição for True, execute o código no bloco a seguir. Caso contrário, se for False, pule o bloco.” Uma declaração if é formatada usando a palavra-chave if seguida por uma condição, seguida por dois pontos (:). O código a ser executado é recuado em um bloco, assim como nos loops.
Vamos tentar um exemplo de uma instrução if:
print('Digite a senha')
senha = input() # 1
if senha == 'bioinfo': # 2
print('Acesso permitido') # 3
print('Pronto') # 4
Quando você executa este programa, ele exibe o texto Digite a senha e permite que o usuário digite uma senha. A senha é então armazenada na variável senha (1). Em seguida, a instrução if verifica se a senha é igual à string 'bioinfo' (2). Se for, a execução se move para dentro do bloco após a instrução if para exibir o texto Acesso permitido para o usuário (3); caso contrário, se senha não for igual a 'bioinfo', a execução pula o bloco da instrução if. De qualquer forma, a execução continua no código após o bloco if para exibir Pronto (4).
Frequentemente, queremos testar uma condição e executar um bloco de código se a condição for True e outro bloco de código se for False. Podemos usar uma instrução else após um bloco de instrução if, e o bloco de código da instrução else será executado se a condição da instrução if for False. Para uma instrução else, você apenas escreve a palavra-chave else e dois pontos (:). Ela não precisa de uma condição porque será executada se a condição da instrução if não for True. Você pode ler o código como, "Se esta condição for True, execute este bloco, ou então, se for False, execute este outro bloco."
Modifique o programa anterior para ficar parecido com o seguinte:
print('Digite a senha')
senha = input()
if senha == 'bioinfo': # 1
print('Acesso permitido')
else:
print('Acesso negado') # 2
print('Pronto') # 3
Esta versão do programa funciona quase da mesma forma que a versão anterior. O texto Acesso permitido ainda será exibido se a condição da instrução if for True (1). Mas agora, se o usuário digitar algo diferente de 'bioinfo', a condição da instrução if será False, fazendo com que a execução entre no bloco da instrução else e exiba Acesso negado (2). De qualquer forma, a execução continuará e exibirá Pronto (3).
Outra declaração, chamada declaração elif, também pode ser pareada com if. Como uma declaração if, ela tem uma condição. Como uma declaração else, ela segue uma declaração if (ou outra declaração elif) e executa se a condição da declaração if (ou elif) anterior for False. Você pode ler declarações if, elif e else como, “Se esta condição for True, execute este bloco. Ou então, verifique se esta próxima condição é True. Ou então, apenas execute este último bloco.” Qualquer número de declarações elif pode seguir uma declaração if. Modifique o programa anterior novamente para que ele se pareça com o seguinte:
print('Digite a senha')
senha = input()
if senha == 'bioinfo': # 1
print('Acesso permitido') # 2
elif senha == 'oceano': # 3
print('A senha é uma área de biotecnologia')
elif senha == '12345': # 4
print('Essa senha é muito óbvia')
else:
print('Acesso negado')
print('Pronto')
Este código contém quatro blocos para as instruções if, elif e else. Se o usuário digitar 12345, então senha == 'bioinfo' avalia como False (1), então o primeiro bloco com print('Acesso permitido') (2) é pulado. A execução verifica a condição senha == 'oceano', que também avalia como False (3), então o segundo bloco também é pulado. A condição senha == '12345' é True (4), então a execução entra no bloco após esta instrução elif para executar o código print('Essa senha é muito óbvia') e pula quaisquer instruções elif e else restantes. Observe que um e apenas um desses blocos será executado.
Você pode ter zero ou mais instruções elif seguindo uma instrução if. Você pode ter zero ou uma, mas não várias instruções else, e a instrução else sempre vem por último porque ela só é executada se nenhuma das condições for avaliada como True. A primeira instrução com uma condição True tem seu bloco executado. O restante das condições (mesmo se também forem True) não são verificadas.
Usamos o operador in nesta parte do código:
if simbolo in SIMBOLOS:
Um operador in pode conectar duas strings, e ele será avaliado como True se a primeira string estiver dentro da segunda string ou será avaliado como False se não estiver. O operador in também pode ser pareado com not, que fará o oposto:
'olá' in 'olá mundo'
'olá' not in 'olá mundo'
'lá' in 'olá mundo'
'OLÁ' in 'olá mundo'
'' in 'Olá'
Observe que os operadores in e not in diferenciam maiúsculas de minúsculas. Além disso, uma string em branco é sempre considerada como in qualquer outra string.
Expressões que usam os operadores in e not in são úteis para usar como condições de instruções if para executar algum código se uma string existir dentro de outra string.
Retornando ao programa da aula, essa linha verifica se a string em simbolo (que o loop for definiu como um único caractere da string mensagem) está na string SIMBOLOS (o conjunto de símbolos de todos os caracteres que podem ser criptografados ou descriptografados por este programa de cifra). Se simbolo estiver em SIMBOLOS, a execução entra no bloco que se segue. Se não estiver, a execução pula este bloco e, em vez disso, entra no bloco que segue a instrução else. O programa de cifra precisa executar um código diferente, dependendo se o símbolo está no conjunto de símbolos.
Vejamos como encontrar o índice o valor de simbolo em SIMBOLOS:
simbolo_indice = SIMBOLOS.find(simbolo)
Este código inclui uma chamada de método. Métodos são como funções, exceto que são anexados a um valor com um ponto. O nome deste método é find(), e ele está sendo chamado no valor da string armazenado em SIMBOLOS.
A maioria dos tipos de dados (como strings) tem métodos. O método find() pega um argumento de string e retorna o índice inteiro de onde o argumento aparece na string do método:
'olá'.find('l')
'olá'.find('o')
s = 'olá'
s.find('á')
Você pode usar o método find() em uma string ou em uma variável contendo um valor de string. Lembre-se de que a indexação em Python começa com 0, então quando o índice retornado por find() é para o primeiro caractere na string, um 0 é retornado.
Se o argumento string não puder ser encontrado, o método find() retornará o inteiro -1:
'olá'.find('x')
'olá'.find('O')
Observe que o método find() também diferencia maiúsculas de minúsculas.
A string que você passa como argumento para find() pode ter mais de um caractere. O inteiro que find() retorna será o índice do primeiro caractere onde o argumento é encontrado:
'melancia'.find('ncia')
'melancia'.find('mel')
'melancia melancia'.find('m')
O método de string find() é como uma versão mais específica do uso do operador in. Ele não só informa se uma string existe em outra string, mas também informa onde.
Agora que você entende as instruções if, elif e else; o operador in; e o método de string find(), será mais fácil entender como o resto do programa de cifra de César funciona.
O programa de cifra só pode criptografar ou descriptografar símbolos que estejam no conjunto de símbolos:
if simbolo in SIMBOLOS:
simbolo_indice = SIMBOLOS.find(simbolo)
Então, antes de executar o código acima, o programa deve descobrir se simbolo está no conjunto de símbolos. Então, ele pode encontrar o índice em SIMBOLOS onde simbolo está localizado. O índice retornado pela chamada find() é armazenado em simbolo_indice.
Agora que temos o índice do símbolo atual armazenado em simbolo_indice, podemos fazer a matemática de criptografia ou descriptografia nele. A cifra de César adiciona o número da chave ao índice do símbolo para criptografá-lo ou subtrai o número da chave do índice do símbolo para descriptografá-lo. Este valor é armazenado em traducao_indice porque será o índice em SIMBOLOS do símbolo traduzido:
if modo == 'encriptar':
traducao_indice = simbolo_indice + chave
elif modo == 'decriptar':
traducao_indice = simbolo_indice - chave
A variável modo contém uma string que informa ao programa se ele deve criptografar ou descriptografar. Se essa string for 'encriptar', então a condição para a instrução if True, e o valor de chave será adicionada a simbolo_indice (e o bloco após a instrução elif será pulado). Caso contrário, se mode for 'decriptar', então o valor de simbolo_indice será subtraído por chave.
Quando implementávamos a cifra de César com papel e lápis no Capítulo 1, às vezes adicionar ou subtrair a chave resultava em um número maior ou igual ao tamanho do conjunto de símbolos ou menor que zero. Nesses casos, temos que adicionar ou subtrair o comprimento do conjunto de símbolos para que ele "enrole" ou retorne ao início ou fim do conjunto de símbolos. Podemos usar o código len(SIMBOLOS) para fazer isso, que retorna 66, o comprimento da string SIMBOLOS:
if traducao_indice >= len(SIMBOLOS):
traducao_indice -= len(SIMBOLOS)
elif traducao_indice < 0:
traducao_indice += len(SIMBOLOS)
Se traducao_indice for maior ou igual a 66, a condição é True e o comando é executado (e a instrução elif na é pulada). Subtrair o comprimento de SIMBOLOS de traducao_indice aponta o índice da variável de volta para o início da string SIMBOLOS. Caso contrário, o Python verificará se traducao_indice é menor que 0. Se essa condição for True, o bloco elif é executado e traducao_indice volta para o final da string SIMBOLOS.
Você pode estar se perguntando por que não usamos o valor inteiro 66 diretamente em vez de len(SIMBOLOS). Ao usar len(SIMBOLOS) em vez de 66, podemos adicionar ou remover símbolos de SIMBOLOS e o resto do código ainda funcionará.
Agora que você tem o índice do símbolo traduzido em traducao_indice, SIMBOLOS[traducao_indice] será avaliado como o símbolo traduzido. É adiciono este símbolo criptografado/descriptografado ao final da string traduzida usando concatenação de strings:
traducao += SIMBOLOS[traducao_indice]
Eventualmente, a sequência traduzida será toda a mensagem codificada ou decodificada.
A string da mensagem pode conter caracteres que não estão na string SIMBOLOS. Esses caracteres estão fora do conjunto de símbolos do programa de cifra e não podem ser criptografados ou descriptografados. Em vez disso, eles serão apenas anexados à string traduzida como está:
else:
traducao += simbolo
A declaração else tem quatro espaços de recuo. Se você olhar para o recuo das linhas acima, verá que ele está pareado com a primeira declaração if. Embora haja muito código entre essa declaração if e else, tudo pertence ao mesmo bloco de código.
Se a condição da instrução if fosse False, o bloco seria pulado, e a execução do programa entraria no bloco da instrução else. Este bloco else tem apenas uma linha. Ele adiciona a sequência de símbolos inalterada ao final da traduzida. Como resultado, símbolos fora do conjunto de símbolos, como '%' ou '(', são adicionados à sequência traduzida sem serem criptografados ou descriptografados.
A última linha do prgrama não tem recuo, o que significa que é a primeira linha após o bloco que começou com o loop for. No momento em que a execução do programa atinge essa linha, ele já percorreu cada caractere na sequência de caracteres da mensagem, criptografou (ou descriptografou) os caracteres e os adicionou ao traducao:
print(traducao)
Essa linha chama a função print() para exibir a string traducao na tela. Observe que esta é a única chamada print() em todo o programa. O computador faz muito trabalho criptografando cada letra na mensagem, manipulando o "enrolar" de SIMBOLOS e manipulando caracteres não presentes em SIMBOLOS. Mas o usuário não precisa ver isso. O usuário só precisa ver a string final.
Esse é o programa de cifra de César inteiro. Quando você o executa, observe como seu computador pode executar o programa inteiro e criptografar a string em menos de um segundo. Mesmo se você inserir uma string muito longa para armazenar na variável em mensagem, seu computador pode criptografar ou descriptografar a mensagem em um ou dois segundos. Compare isso com os vários minutos que levaria para fazer isso com uma roda de cifra.
Um problema com a cifra de César que implementamos é que ela não pode criptografar caracteres fora do seu conjunto de símbolos. Por exemplo, se você criptografar a string 'Tenha certeza de levar $$$.' com a chave 20, a mensagem será criptografada para 'ny82uQwy!.yFuQxyQ6yBu!Q$$$T'. Esta mensagem criptografada não esconde que você está se referindo a $$$. No entanto, podemos modificar o programa para criptografar outros símbolos.
Ao alterar a string armazenada em SIMBOLOS para incluir mais caracteres, o programa também os criptografará, porque o símbolo de condição em SIMBOLOS será True. O valor de simbolo_indice será o índice do símbolo nesta nova e maior variável constante SIMBOLOS. O “enrolar” precisará adicionar ou subtrair o número de caracteres nesta nova string, mas isso já foi tratado porque usamos len(SIMBOLOS) em vez de digitar 66 diretamente no código (é por isso que o programamos desta forma).
Por exemplo, você pode expandir a variável SIMBOLOS para:
SIMBOLOS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !?.`~@#$%^&*()_+-=[]{}|;:<>,/'
Tenha em mente que uma mensagem deve ser criptografada e descriptografada com o mesmo conjunto de símbolos para funcionar.
Exercício 01: Usando o programa desenvolvido na aula, criptografe as seguintes frases com as chaves fornecidas:
"Você pode mostrar que o preto é branco por meio de argumentos", disse Filby, "mas você nunca vai me convencer." (chave 8)
1234567890 (chave 21)
Exercício 02: Usando o programa desenvolvido na aula, decriptografe as seguintes frases com as chaves fornecidas:
Rctgeg?dcuvcpvg?rncwuíxgnB (chave 2)
iwASB z S S.1AB Sz S9C0z S1.wS50D5AíD18V (chave 22)
Exercício 03: O que os seguintes trechos de código exibem na tela:
1.
s = 'foo'
for i in s:
s += i
print(s)
2.
if 10 < 5:
print('Olá')
elif False:
print('Gui')
elif 5 != 5:
print('Isa')
else:
print('Tchau')
3.
print('f' not in 'foo')
4.
print('foo' in 'f')
5.
print('banana'.find('nn'))
Você aprendeu vários conceitos de programação e leu vários capítulos para chegar a este ponto, mas agora você tem um programa que implementa uma cifra secreta. E mais importante, você entende como esse código funciona.
Variáveis constantes são escritas em letras maiúsculas por convenção. Essas variáveis não devem ter seus valores alterados (embora nada impeça o programador de escrever código que faça isso). Constantes são úteis porque dão um “nome” a valores específicos em seu programa.
Métodos são funções que são anexadas a um valor de um certo tipo de dado. O método de string find() retorna um inteiro da posição do argumento de string passado a ele dentro da string em que ele é chamado.
Você aprendeu sobre várias novas maneiras de manipular quais linhas de código são executadas e quantas vezes cada linha é executada. Um loop for itera sobre todos os caracteres em um valor de string, definindo uma variável para cada caractere em cada iteração. As instruções if, elif e else executam blocos de código com base em se uma condição é True ou False.
Os operadores in e not in verificam se uma string está ou não em outra string e avaliam como True ou False.
Saber programar lhe dá a habilidade de escrever um processo como criptografar ou descriptografar com a cifra de César em uma linguagem que um computador pode entender. E uma vez que o computador entende como executar o processo, ele pode fazê-lo muito mais rápido do que qualquer humano e sem erros (a menos que haja erros na sua programação). Embora esta seja uma habilidade incrivelmente útil, acontece que a cifra de César pode ser facilmente quebrada por alguém que sabe programar. No Capítulo 6, você usará as habilidades que aprendeu para escrever um hacker de cifra de César para que possa ler o texto cifrado que outras pessoas criptografaram. Vamos prosseguir e aprender como hackear a criptografia.