Capítulo 14: Cifrado César

Temas Tratados En Este Capítulo:

El programa de este capítulo no es realmente un juego, pero es un programa divertido. Este programa traduce texto normal a un código secreto. También puede convertir mensajes en el código secreto a texto normal. Sólo alguien que conozca este código secreto podrá entender nuestros mensajes secretos.

Como este programa manipula texto para convertirlo en mensajes secretos, aprenderás varios nuevos métodos y funciones para manipular cadenas. También aprenderás que los programas pueden hacer matemática con cadenas de texto así como lo hacen con números.

Criptografía

La ciencia de escribir códigos secretos se llama criptografía. Por miles de años la criptografía ha permitido crear mensajes secretos que sólo el emisor y el receptor podían entender, incluso en caso de que alguien capturase al mensajero y leyese el mensaje codificado. Los sistemas secretos de codificación se llaman cifrados. El cifrado que usa el programa de este capítulo se llama Cifrado César.

En criptografía, llamamos texto plano al mensaje que queremos codificar. El texto plano podría ser algo como esto:

El proceso de convertir el texto plano en el mensaje codificado se llama cifrado o encriptación. El texto cifrado también se llama criptograma. El criptograma da un aspecto de ser letras aleatorias, y no es posible entender el texto plano original simplemente mirando el criptograma. Veamos el criptograma correspondiente a la encriptación del ejemplo anterior:

Pero si conoces el sistema de cifrado usado para encriptar el mensaje, puedes desencriptar el criptograma y convertirlo en el texto plano. (Desencriptar es lo opuesto a encriptar).

Muchos cifrados también usan claves. Las claves son valores secretos que permiten desencriptar los criptogramas que fueron encriptados usando un cifrado específico. Piensa en el cifrado como si fuera la cerradura de una puerta. Sólo puedes abrirla con una llave particular.

El Cifrado César

La clave para el Cifrado César será un número entre 1 y 26. A menos que conozcas la clave (es decir, conozcas el número usado para encriptar el mensaje), no podrás desencriptar el código secreto.

El Cifrado César fue uno de los primeros sistemas de cifrado que se inventaron. Con este cifrado, para encriptar un mensaje se toma cada letra del mismo (en criptografía, estas letras se llaman símbolos porque pueden ser letras, números o cualquier otro signo) y se la reemplaza con una letra «desplazada«. Si desplazas la letra A un espacio, obtienes la letra B. Si desplazas la A dos
espacios, obtienes la letra C. La Figura 14-1 es una ilustración de letras desplazadas tres espacios.

Figura 14-1: Letras desplazadas tres espacios. Aquí B se transforma en E.

Para obtener cada letra desplazada, dibuja una fila de casilleros con cada letra del alfabeto. Luego dibuja una segunda fila de casilleros debajo de ella, pero comienza un cierto número (este número es la clave) de casilleros hacia la derecha. Luego de la última letra, vuelve a comenzar con la primera. Aquí hay un ejemplo con las letras desplazadas tres espacios.

Figura 14-2: El alfabeto completo desplazado tres espacios.

El número de espacios que te desplazas es la clave en el Cifrado César. El ejemplo anterior muestra las traducciones de cada letra para la clave 3.

Si encriptas el texto plano «Adios» con una clave 3, tendremos:

  • La “A” se convierte en “D”.
  • La letra “d” se convierte en “g”.
  • La letra “i” se convierte en “l”.
  • La letra “o” se convierte en “r”.
  • La letra “s” se convierte en “v”.

El criptograma de «Adios» con clave 3 da como resultado “Dglrv”.

Los caracteres que no correspondan a letras no serán alterados. Para desencriptar «Dglrv» con la clave 3, partimos de la fila inferior de casilleros y volvemos hacia arriba:

  • La letra “D” se convierte en “A”.
  • La letra “g” se convierte en “d”.
  • La letra “l” se convierte en “i”.
  • La letra “r” se convierte en “o”.
  • La letra “v” se convierte en “s”.

Usando ASCII, Números por Letras

¿Cómo implementamos este cambio de letras en un programa? Podemos hacer esto representando cada letra como un número llamado ordinal, y luego sumando o restando a este número para formar un nuevo ordinal (y una nueva letra). ASCII (que se pronuncia «asqui» y corresponde a las
siglas en inglés de Código Estándar Americano para el Intercambio de Información
) es un código que relaciona cada caracter con un número entre 32 y 126.

Las mayúsculas de la «A» a la «Z» su código ASCII sería del 65 al 90. Las minúsculas de la «a» a «z» su código ASCII sería del 97 al 122. Los caracteres numéricos del «0» a «9» su código ASCII sería desde el 48 al 57. La Tabla 14-1 muestra todos los caracteres y ordinales ASCII.

Las computadoras modernas usan UTF-8 en lugar de ASCII. Pero UTF-8 es compatible con ASCII, de modo que los ordinales UTF-8 para los caracteres ASCII son los mismos que los ordinales ASCII.

Tabla 14-1: Código ASCII

Entonces, si quisieras desplazar la «A» tres espacios, deberías hacer lo siguiente:

  • Convertir “A” en código ASCII (65).
  • Sumar 3 a 65, para obtener 68.
  • Reconvertir el ordinal 68 a la letra correspondiente (“D”).

Con las funciones chr() y ord() pueden convertir de código ASCII a carácter y viceversa.

Las Funciones chr() y ord()

La función chr() (abreviatura de «caracter») toma un número entero y
devuelve un único caracter. La función ord() (abreviatura de «ordinal») toma un
solo caracter y devuelve su valor ordinal entero (código ASCII). Intenta ingresar lo siguiente en la consola interactiva:

En la tercera línea, chr(65+8) se evalúa a chr(73). Si miras la tabla ASCII, puedes ver que 73 es el ordinal para la letra mayúscula «I«.

En la quinta línea, chr(ord(‘F’)) se evalúa a chr(70) que a su vez se evalúa a ‘F’. Las funciones ord() y chr() son opuestas entre sí.

Prueba de Ejecución de Cifrado César

Aquí hay una prueba de ejecución del programa Cifrado César, encriptando un mensaje:

Ahora ejecuta el programa y desencripta el texto que acabas de encriptar.

Si no desencriptas con la clave correcta, el texto desencriptado será basura:

Código Fuente de Cifrado César

Aquí está el código fuente para el programa Cifrado César. Después de escribir este código, guarda el archivo como cifrado.py.

Cómo Funciona el Código

Los procesos de encriptación y desencriptación son inversos el uno del otro, y aún así utilizan en gran medida el mismo código. Veamos cómo funciona cada línea.

La primera línea es simplemente un comentario. TAM_MAX_CLAVE es una constante que almacena el entero 26. TAM_MAX_CLAVE nos recuerda que en este programa, la clave usada para el cifrado debe estar comprendida entre 1 y 26.

Decidiendo si Encriptar o Desencriptar

La función obtenerModo() permite al usuario elegir si quieren entrar al modo de cifrado o descifrado del programa. El valor devuelto de input() y lower() se almacena en la variable modo. La condición de la sentencia if comprueba si la cadena almacenada en la variable modo existe en la lista
devuelta por ‘encriptar e desencriptar d’.split().

Esta lista es [‘encriptar’, ‘e’, ‘desencriptar’, ‘d’], pero es más fácil para el
programador escribir ‘encriptar e desencriptar d’.split() y no tener que escribir todas esas comas y comillas. Usa la forma que sea más fácil para tí; ambas son evaluadas al mismo valor de lista.

Esta función devolverá la cadena almacenada en modo siempre que modo sea igual a ‘encriptar’, ‘e’, ‘desencriptar’ o ‘d’. Entonces, obtenerModo() devolverá la cadena ‘e’ o la cadena ‘d’ (pero el usuario puede escribir “e”, “encriptar”, “d” o “desencriptar.)

Obteniendo el Mensaje del Jugador

La función obtenerMensaje() simplemente obtiene el mensaje a encriptar o desencriptar del usuario y devuelve este valor.

Obteniendo la Clave del Jugador

La función obtenerClave() permite al jugador escribir la clave que desea usar para encriptar o desencriptar el mensaje. El bucle while asegura que la función se mantenga en un bucle hasta que el usuario ingrese una clave válida.

Una clave válida es aquella que está comprendida entre los valores enteros 1 y 26 (recuerda que TAM_MAX_CLAVE tendrá siempre el valor 26 porque es constante). La función devuelve entonces esta clave. La línea 22 establece la clave como la versión entera de lo que el jugador haya escrito, de modo que obtenerClave() devuelve un entero.

Encriptar o Desencriptar el Mensaje con la Clave Dada

La función obtenerMensajeTraducido() realiza la encriptación y desencriptación. Se compone de tres parámetros:

  1. modo.- Elige entre los modos de encriptación y desencriptación.
  2. mensaje.- Es el texto plano (o criptograma) a encriptar (o desencriptar).
  3. clave.- Es la clave numérica a usar para este cifrado.

La línea 27 comprueba si la primera letra en la variable modo es la cadena ‘d’. En ese caso, el programa entra en modo de desencriptación. La única diferencia entre los modos de desencriptación y encriptación es que para desencriptar un mensaje se usa la versión negativa de la clave. Si clave fuera el entero 22, entonces en modo de desencriptación clave se transforma en -22. Explicaremos la razón de esto más adelante.

La variable traduccion es la cadena que contiene al resultado, es decir, el criptograma (ei estás encriptando) o el texto plano (si estás desencriptando). Comienza como una cadena vacía a cuyo final se van añadiendo caracteres encriptados o desencriptados.

El Método de Cadena isalpha()

El método de cadena isalpha() devolverá True si la cadena es una letra mayúscula o minúscula entre A y Z. Si la cadena contiene algún caracter no alfabético, entonces isalpha() devolverá False. Prueba ingresar lo siguiente en la consola interactiva:

Como puedes observar, ‘Cuarenta y dos’.isalpha() devuelve False porque ‘Cuarenta y dos’ incluye dos espacios, los cuales son caracteres no alfabéticos. ‘Cuarentaydos’.isalpha() devuelve True porque no contiene espacios.

’42’.isalpha() devuelve False porque ni ‘4‘ ni ‘2‘ son letras. isalpha() sólo devuelve True si la cadena no está vacía y está compuesta únicamente por letras.
El método isalpha() se usa en las siguientes líneas del programa.

El bucle for de la línea 31 itera sobre cada letra (en criptografía se llaman símbolos) de la cadena del mensaje. En cada iteración sobre este bucle, simbolo tendrá el valor de una letra en el mensaje.

La línea 32 está presente porque sólo las letras serán encriptadas o desencriptadas. Los números, signos de puntuación y todo lo demás conservará su forma original. La variable num almacenará el valor ordinal entero de la letra en la variable simbolo. La línea 34desplaza” el valor de num en el número de casilleros correspondiente a la clave.

Los Métodos de Cadena isupper() e islower()

Los métodos de cadena isupper() e islower() (los cuales utilizamos en las líneas 36 y 41) funcionan de forma similar a los métodos isdigit() e isalpha().

El método isupper() devuelve True si la cadena sobre la cual es llamado contiene al menos una letra mayúscula y ninguna minúscula. Mientras que el método islower() devuelve True si la cadena sobre la cual es llamado
contiene al menos una letra minúscula y ninguna mayúscula. De otro modo estos métodos devuelven False.

Prueba ingresar lo siguiente en la consola interactiva:

Encriptando o Desencriptando Cada Letra

La línea 36 comprueba si el símbolo es una letra mayúscula. Si lo es, hay dos casos especiales a tener en cuenta. Qué ocurriría si el símbolo fuese ‘Z‘ y la clave 4? En este caso, el valor de num aquí sería el caracter ‘^’ (El ordinal de ‘^‘ es 94). Pero ^ no es ninguna letra. Y nosotros queremos que el criptograma «reinicie la vuelta» por el principio del alfabeto.

Comprobamos si num tiene un valor mayor que el valor ordinal de “Z”. Si es así, restamos 26 a num (porque hay 26 letras en total). Después de hacer esto, el valor de num sería 68. 68 es el valor ordinal correcto ya que corresponde a ‘D‘.

Si el símbolo es una letra minúscula, el programa ejecuta un código que es similar a las líneas 36 a 40. la única diferencia es que utiliza ord(‘z’) y ord(‘a’) en lugar de ord(‘Z’) y ord (‘A’).

En modo desencriptación, la clave es negativa. El caso especial sería si num -= 26 es menor que el valor ASCII de “a”. En ese caso, sumamos 26 a num para que “reinicie la vuelta” al final del alfabeto.

La línea 47 concatena el caracter encriptado/desencriptado hacia la cadena traducida.

Si el símbolo no es una letra mayúscula o minúscula, la línea 49 concatena el símbolo original a la cadena traducida. Por lo tanto, espacios, números, signos de puntuación y otros caracteres no serán encriptados o desencriptados.

La última línea en la función obtenerMensajeTraducido() devuelve la cadena traducida.

El Inicio del Programa

El comienzo del programa llama a cada una de las tres funciones definidas anteriormente para obtener el modo, el mensaje y la clave del usuario. Estos tres valores son pasados a obtenerMensajeTraducido(), cuyo valor de retorno (la cadena traducida) es mostrada en la pantalla al usuario.

Fuerza Bruta

Eso es todo con respecto al Cifrado César. Sin embargo, a pesar de que este cifrado puede engañar a gente que no entiende criptografía, no será suficiente para alguien que sepa de criptoanálisis. Así como criptografía es la ciencia de crear códigos, criptoanálisis es la ciencia de descifrarlos.

La idea central de la criptografía es que si alguien más consigue apoderarse del mensaje encriptado, no consiga obtener la información del mensaje original sin encriptar. Hagamos a la idea que somos descifradores de códigos y todo lo que tenemos es el texto encriptado:

Fuerza bruta es la técnica de probar cada todas las claves posibles hasta encontrar la correcta. Como hay sólo 26 claves posibles, sería fácil para un criptoanalista escribir un programa que desencriptara con todas las claves posibles. Luego podría fijarse cuál de las claves resulta en un mensaje en Español. Agreguemos un modo de fuerza bruta a nuestro programa.

Agregando el Modo de Fuerza Bruta

Primero, cambiamos las líneas 7, 9 y 12 (que están dentro de la función obtenerModo()) para convertirlas en lo siguiente (los cambios están en negrita):

Este código permitirá al usuario elegir «fuerza bruta» como un modo. Modifica y agrega los siguientes cambios a la parte principal del programa:

Estos cambios piden una clave al usuario si no se encuentra en el modo de «fuerza bruta«. Se efectúa entonces la llamada original a obtenerMensajeTraducido() y se muestra la cadena traducida.

Sin embargo, si el usuario está en el modo de «fuerza bruta» entonces
obtenerMensajeTraducido() se ejecuta en un bucle que recorre todos los valores entre 1 y TAM_MAX_CLAVE (que es 26). Recuerda que la función range() devuelve una lista de enteros hasta el segundo parámetro pero sin incluirlo, por lo que agregamos + 1 a la expresión. Este programa imprimirá en la pantalla cada posible traducción del mensaje (incluyendo el número de clave
usado para la traducción
). Aquí hay una prueba de ejecución del programa modificado:

Si examinamos cada columna, puedes ver que la línea 8 del mensaje no es basura, sino texto en español. El criptoanalista puede deducir que la clave original de este mensaje encriptado debe haber sido 8. Este método de fuerza bruta habría sido difícil de emplear en los tiempos del César y del imperio romano, pero hoy en día tenemos computadoras que pueden examinar millones de claves rápidamente.

Resumen

Las computadoras son muy efectivas para hacer operaciones matemáticas. Cuando creamos un sistema para traducir fragmentos de información a números (así como hacemos con texto y ordinales o con información espacial y sistemas de coordenadas), los programas de computadora pueden procesar estos números en forma rápida y eficiente.

Pero aunque nuestro programa de cifrado César puede encriptar mensajes y mantenerlos secretos para gente que sólo tiene a disposición papel y lápiz, no conseguirá ocultarlos a gente que sepa cómo hacer que una computadora procese información por ellos. (Nuestro modo de fuerza bruta lo comprueba).

Una parte fundamental del proceso de escribir un programa es entender cómo representar la información que queremos manipular utilizando valores que Python puede comprender.

El próximo capítulo presentará Reversi (también conocido como Othello). La IA que maneja este juego es mucho más avanzada que la IA que diseñamos para el Ta Te Ti en el capítulo 9. De hecho, la IA es tan buena que… ¡te vencerá en casi todas las partidas!

Listado del programa cifrado.py

# Cifrado César

TAM_MAX_CLAVE = 26

def obtenerModo():
    while True:
        print('¿Deseas encriptar o desencriptar un mensaje?')
        modo = input().lower()
        if modo in 'encriptar e desencriptar d'.split():
            return modo
        else:
            print('Ingresa "encriptar" o "e" o "desencriptar" o "d"')

def obtenerMensaje():
    print('Ingresa tu mensaje:')
    return input()

def obtenerClave():
    clave = 0
    while True:
        print('Ingresa el número de clave (1-%s)' % (TAM_MAX_CLAVE))
        clave = int(input())
        if (clave >= 1 and clave <= TAM_MAX_CLAVE):
            return clave

def ObtenerMensajeTraducido(modo, mensaje, clave):
    if modo[0] == 'd':
        clave = -clave
    traduccion = ''

    for simbolo in mensaje:
        if simbolo.isalpha():
            num = ord(simbolo)
            num += clave

            if simbolo.isupper():
                if num > ord('Z'):
                    num -= 26
                elif num < ord('A'):
                    num += 26
            elif simbolo.islower():
                if num > ord('z'):
                    num -= 26
                elif num < ord('a'):
                    num += 26
            
            traduccion += chr(num)
        else:
            traduccion += simbolo
    return traduccion

modo = obtenerModo()
mensaje = obtenerMensaje()
clave = obtenerClave()

print('Tu texto traducido es:')
print(ObtenerMensajeTraducido(modo, mensaje, clave))
 

Listado cifrado.py modificado para fuerza bruta

# Cifrado César

TAM_MAX_CLAVE = 26

def obtenerModo():
    while True:
        print('¿Deseas encriptar, desencriptar o descifrar por fuerza bruta un mensaje?')
        modo = input().lower()
        if modo in 'encriptar e desencriptar d bruta b'.split():
            return modo
        else:
            print('Ingresa "encriptar" o "e" o "desencriptar" o "d" o "bruta" o "b".')

def obtenerMensaje():
    print('Ingresa tu mensaje:')
    return input()

def obtenerClave():
    clave = 0
    while True:
        print('Ingresa el número de clave (1-%s)' % (TAM_MAX_CLAVE))
        clave = int(input())
        if (clave >= 1 and clave <= TAM_MAX_CLAVE):
            return clave

def ObtenerMensajeTraducido(modo, mensaje, clave):
    if modo[0] == 'd':
        clave = -clave
    traduccion = ''

    for simbolo in mensaje:
        if simbolo.isalpha():
            num = ord(simbolo)
            num += clave

            if simbolo.isupper():
                if num > ord('Z'):
                    num -= 26
                elif num < ord('A'):
                    num += 26
            elif simbolo.islower():
                if num > ord('z'):
                    num -= 26
                elif num < ord('a'):
                    num += 26
            
            traduccion += chr(num)
        else:
            traduccion += simbolo
    return traduccion

modo = obtenerModo()
mensaje = obtenerMensaje()
if modo[0] != 'b':
    clave = obtenerClave()

print('Tu texto traducido es:')
if modo[0] != 'b':
    print(ObtenerMensajeTraducido(modo, mensaje, clave))
else:
    for clave in range(1, TAM_MAX_CLAVE + 1):
        print(clave, ObtenerMensajeTraducido('desencriptar', mensaje, clave))