Python en Español

Cifrado Cesar en Python (Tutorial de Cifrado de Texto)

La criptografía se ocupa de encriptar o codificar un trozo de información (en un texto plano) en una forma que parece un galimatías y que tiene poco sentido en el lenguaje ordinario.

Este mensaje codificado (también llamado texto cifrado) puede luego ser descodificado de nuevo en un texto plano por el destinatario previsto mediante una técnica de descodificación (a menudo junto con una clave privada) comunicada al usuario final.

El Cifrado César es una de las técnicas de cifrado más antiguas en las que nos centraremos en este tutorial, e implementaremos la misma en Python.
Aunque el Cifrado César es una técnica de encriptación muy débil y rara vez se utiliza hoy en día, estamos haciendo este tutorial para introducir a nuestros lectores, especialmente a los recién llegados, a la encriptación.

Considere esto como el “Hola Mundo” de la Criptografía.

 

¿Qué es el cifrado Cesar?

El Cifrado Cesar es un tipo de Cifrado de sustitución, en el que cada letra del texto simple es reemplazada por otra letra en algunas posiciones fijas de la letra actual del alfabeto.

Por ejemplo, si desplazamos cada letra en tres posiciones a la derecha, cada una de las letras de nuestro texto plano será reemplazada por una letra en tres posiciones a la derecha de la letra del texto plano.

Veamos esto en acción – encriptemos el texto “HELLO WORLD” usando un desplazamiento a la derecha de 3.

 

Regla de codificación de cifrado César para desplazamiento a la derecha de 3

Así que la letra H será reemplazada por la K, la E será reemplazada por la H, y así sucesivamente. El último mensaje encriptado para HELLO WORLD será KHOOR ZRUOG. Ese galimatías no tiene sentido, ¿verdad?

Note que las letras del borde, es decir, X, Y, Z, se enrollan y son reemplazadas por A, B, C, respectivamente, en caso de que el turno sea el correcto. Del mismo modo, las letras del principio – A, B, C, etc. – se envolverán en caso de que el turno sea a la izquierda.

La regla de cifrado del cifrado César puede ser expresada matemáticamente como:

c = (x + n) % 26

Donde c es el carácter codificado, x es el carácter real, y n es el número de posiciones en las que queremos desplazar el carácter x. Estamos tomando mod con 26 porque hay 26 letras en el alfabeto inglés

El Cifrado Cesar en Python

Antes de que nos sumerjamos en la definición de las funciones para el proceso de encriptación y desencriptación de César Cifrado en Python, primero veremos dos importantes funciones que usaremos extensamente durante el proceso – chr() y ord().

Es importante darse cuenta de que el alfabeto tal y como lo conocemos, se almacena de forma diferente en la memoria de un ordenador. El ordenador no entiende por sí mismo el alfabeto de nuestro idioma inglés ni otros caracteres.

Cada uno de estos caracteres se representa en la memoria de la computadora usando un número llamado código ASCII (o su extensión – el Unicode) del carácter, que es un número de 8 bits y codifica casi todos los caracteres, dígitos y puntuaciones del idioma inglés.

Por ejemplo, la “A” mayúscula está representada por el número 65, la “B” por el 66, y así sucesivamente. Del mismo modo, la representación de los caracteres en minúscula comienza con el número 97.

A medida que surgió la necesidad de incorporar más símbolos y caracteres de otros idiomas, el 8 bit no fue suficiente, por lo que se adoptó un nuevo estándar – Unicode – que representa todos los caracteres utilizados en el mundo utilizando 16 bits.

El ASCII es un subconjunto del Unicode, por lo que la codificación ASCII de los caracteres sigue siendo la misma en el Unicode. Eso significa que la ‘A’ seguirá siendo representada usando el número 65 en Unicode.

Tengan en cuenta que los caracteres especiales como el espacio ” “, tabulaciones “\t”, nuevas líneas “\N”, etc. también están representados en la memoria por su Unicode.

Veremos dos funciones incorporadas en Python que se usan para encontrar la representación Unicode de un personaje y viceversa.

La función ord()

Puedes usar el método ord() para convertir un carácter a su representación numérica en Unicode. Acepta un solo carácter y devuelve el número que representa su Unicode. Veamos un ejemplo.

c_unicode = ord("c")

A_unicode = ord("A")

print("Unicode of 'c' =", c_unicode)

print("Unicode of 'A' =", A_unicode)

Salida:
Salida del ejemplo del método ord ()

La función chr()

Al igual que cuando convertimos un carácter en su Unicode numérico usando el método ord(), hacemos lo inverso, es decir, encontramos el carácter representado por un número usando el método chr().

El método chr() acepta un número que representa el Unicode de un carácter y devuelve el carácter real correspondiente al código numérico.

Veamos primero algunos ejemplos:

character_65 = chr(65)

character_100 = chr(100)

print("Unicode 65 represents", character_65)

print("Unicode 100 represents", character_100)

character_360 = chr(360)

print("Unicode 360 represents", character_360)

Salida:

Salida del ejemplo del método chr ()

Fíjate en cómo la letra alemana Ü (U umlaut) también está representada en Unicode por el número 360.

También podemos aplicar una operación encadenada (ord seguida de chr) para recuperar el carácter original.

c = chr(ord("Ũ"))

print(c)

Salida: Ũ

 

Encriptación para Letras Mayúsculas

Ahora que entendemos los dos métodos fundamentales que usaremos, implementemos la técnica de encriptación para las mayúsculas en Python. Encriptaremos sólo los caracteres en mayúscula en el texto y dejaremos los restantes sin modificar.

Veamos primero el proceso paso a paso de cifrado de las mayúsculas:

  1. Definir el valor de desplazamiento, es decir, el número de posiciones que queremos desplazar de cada personaje.
  2. Iterar sobre cada carácter del texto simple:
    1. Si el carácter está en mayúsculas:
      1. Calcular la posición/índice del carácter en el rango de 0-25.
      2. Realice el desplazamiento positivo utilizando la operación de módulo.
      3. Encontrar el carácter en la nueva posición.
      4. Reemplazar la actual letra mayúscula por este nuevo carácter.
    2. Si no, Si el carácter no está en mayúsculas, guárdelo sin cambios.

Veamos ahora el código:

shift = 3 # defining the shift count

text = "HELLO WORLD"

encryption = ""

for c in text:

    # check if character is an uppercase letter
    if c.isupper():

        # find the position in 0-25
        c_unicode = ord(c)

        c_index = ord(c) - ord("A")

        # perform the shift
        new_index = (c_index + shift) % 26

        # convert to new character
        new_unicode = new_index + ord("A")

        new_character = chr(new_unicode)

        # append to encrypted string
        encryption = encryption + new_character

    else:

        # since character is not uppercase, leave it as it is
        encryption += c
        
print("Plain text:",text)

print("Encrypted text:",encryption)

Salida:

Salida de cifrado de mayúsculas

Como vemos, el texto encriptado de “HELLO WORLD” es “KHOOR ZRUOG”, y coincide con el que llegamos manualmente en la sección de Introducción.

Además, este método no cifra el carácter del espacio, y sigue siendo un espacio en la versión cifrada.

Descifrado para las Letras Mayúsculas

Ahora que hemos descubierto el cifrado de las mayúsculas del texto plano usando Cifrado Cesar, veamos cómo desencriptaremos el texto cifrado en texto plano.

Anteriormente, vimos la formulación matemática del proceso de encriptación. Ahora comprobemos lo mismo para el proceso de desencriptación.

x = (c - n) % 26

El significado de las notaciones sigue siendo el mismo que en la fórmula anterior.

Si algún valor se vuelve negativo después de la sustracción, el operador del módulo se encargará de ello, y lo envolverá.

Veamos la implementación paso a paso del proceso de desencriptación, que será más o menos lo contrario de la encriptación:

  • Definir el número de turnos
  • Iterar sobre cada carácter del texto encriptado:
    1. Si el carácter es una letra mayúscula:
      1. Calcule la posición/índice del carácter en el rango de 0-25.
      2. Realice el desplazamiento negativo utilizando la operación de módulo.
      3. Encuentra el carácter en la nueva posición.
      4. Sustituir la letra cifrada actual por este nuevo carácter (que también será una letra mayúscula).
      5. Si no, si el carácter no es mayúscula, manténgalo sin cambios.

Escribamos el código para el procedimiento anterior:

shift = 3 # defining the shift count

encrypted_text = "KHOOR ZRUOG"

plain_text = ""

for c in encrypted_text:

    # check if character is an uppercase letter
    if c.isupper():

        # find the position in 0-25
        c_unicode = ord(c)

        c_index = ord(c) - ord("A")

        # perform the negative shift
        new_index = (c_index - shift) % 26

        # convert to new character
        new_unicode = new_index + ord("A")

        new_character = chr(new_unicode)

        # append to plain string
        plain_text = plain_text + new_character

    else:

        # since character is not uppercase, leave it as it is
        plain_text += c

print("Encrypted text:",encrypted_text)

print("Decrypted text:",plain_text)

Salida:

Salida de descifrado de mayúsculas

Fíjense cómo hemos recuperado con éxito el texto original “HELLO WORLD” de su forma encriptada.

Encriptar números y puntuación

Ahora que hemos visto cómo podemos codificar y decodificar las letras mayúsculas del alfabeto inglés utilizando el Cesar Cipher, se plantea una pregunta importante – ¿Qué pasa con los otros caracteres?

¿Qué hay de los números? ¿Qué hay de los caracteres especiales y la puntuación?

Bueno, el algoritmo original de Cesar Cipher no debía tratar nada más que las 26 letras del alfabeto, ni en mayúsculas ni en minúsculas.

Así que un típico Cifrado César no encriptaría la puntuación o los números y convertiría todas las letras en minúsculas o mayúsculas y codificaría sólo esos caracteres.

Así que intentaremos codificar los caracteres en mayúsculas y minúsculas de la manera en que lo hicimos en la sección anterior, ignoraremos las puntuaciones por ahora, y luego también codificaremos los números en el texto.

Para los números, podemos hacer la codificación de una de las dos maneras:

  1. Desplazar el valor de los dígitos en la misma cantidad que se desplazan las letras del alfabeto, es decir, para un desplazamiento de 3 – el dígito 5 se convierte en 8, 2 se convierte en 5, 9 se convierte en 2, y así sucesivamente.
  2. Haga que los números formen parte del alfabeto, es decir, la z o la Z irán seguidos del 0,1,2. hasta el 9, y esta vez nuestro divisor para el funcionamiento del módulo será 36 en lugar de 26.

Implementaremos nuestra solución usando la primera estrategia. También, esta vez, implementaremos nuestra solución como una función que acepta el valor de desplazamiento (que sirve como clave en Cifrado Cesar ) como parámetro.

Implementaremos 2 funciones: cipher_encrypt() y cipher_decrypt()

¡Vamos a ensuciarnos las manos!

La Solución

# The Encryption Function
def cipher_encrypt(plain_text, key):

    encrypted = ""

    for c in plain_text:

        if c.isupper(): #check if it's an uppercase character

            c_index = ord(c) - ord('A')

            # shift the current character by key positions
            c_shifted = (c_index + key) % 26 + ord('A')

            c_new = chr(c_shifted)

            encrypted += c_new

        elif c.islower(): #check if its a lowecase character

            # subtract the unicode of 'a' to get index in [0-25) range
            c_index = ord(c) - ord('a') 

            c_shifted = (c_index + key) % 26 + ord('a')

            c_new = chr(c_shifted)

            encrypted += c_new

        elif c.isdigit():

            # if it's a number,shift its actual value 
            c_new = (int(c) + key) % 10

            encrypted += str(c_new)

        else:

            # if its neither alphabetical nor a number, just leave it like that
            encrypted += c

    return encrypted

# The Decryption Function
def cipher_decrypt(ciphertext, key):

    decrypted = ""

    for c in ciphertext:

        if c.isupper(): 

            c_index = ord(c) - ord('A')

            # shift the current character to left by key positions to get its original position
            c_og_pos = (c_index - key) % 26 + ord('A')

            c_og = chr(c_og_pos)

            decrypted += c_og

        elif c.islower(): 

            c_index = ord(c) - ord('a') 

            c_og_pos = (c_index - key) % 26 + ord('a')

            c_og = chr(c_og_pos)

            decrypted += c_og

        elif c.isdigit():

            # if it's a number,shift its actual value 
            c_og = (int(c) - key) % 10

            decrypted += str(c_og)

        else:

            # if its neither alphabetical nor a number, just leave it like that
            decrypted += c

    return decrypted

Ahora que hemos definido nuestras dos funciones, usemos primero la función de encriptación para cifrar un mensaje secreto que un amigo está compartiendo a través de un mensaje de texto a su amigo.

plain_text = "Mate, the adventure ride in Canberra was so much fun, We were so drunk we ended up calling 911!"

ciphertext = cipher_encrypt(plain_text, 4)

print("Plain text message:\n", plain_text)

print("Encrypted ciphertext:\n", ciphertext)

Salida:

Salida de encriptación de minúsculas, mayúsculas y números

Fíjate en cómo todo, excepto la puntuación y los espacios, ha sido encriptado.

Ahora veamos un texto cifrado que el Coronel Nick Fury estaba enviando en su buscapersonas: ‘Sr xli gsyrx sj 7, 6, 5 – Ezirkivw Ewwiqfpi!’

Resulta que es el texto cifrado de César y afortunadamente, tenemos en nuestras manos la llave de este texto cifrado.

Veamos si podemos desenterrar el mensaje oculto.

ciphertext = "Sr xli gsyrx sj 7, 6, 5 - Ezirkivw Ewwiqfpi!"

decrypted_msg = cipher_decrypt(ciphertext, 4)

print("The cipher text:\n", ciphertext)

print("The decrypted message is:\n",decrypted_msg)

Salida:

Salida de descifrado de minúsculas, mayúsculas y números

¡Bien hecho, Vengadores!

Utilizando una tabla de búsqueda

En esta etapa, hemos comprendido el proceso de encriptación y desencriptación del Cifrado César, y hemos implementado el mismo en Python.

Ahora veremos cómo se puede hacer más eficiente y más flexible.

Específicamente, nos centraremos en cómo podemos evitar los repetidos cálculos de las posiciones cambiadas para cada letra del texto durante el proceso de encriptación y desencriptación, construyendo una tabla de búsqueda con antelación.

También veremos cómo podemos acomodar cualquier conjunto de símbolos definidos por el usuario y no sólo las letras del alfabeto en nuestro proceso de cifrado.

También fusionaremos el proceso de encriptación y desencriptación en una función y aceptaremos como parámetro cuál de los dos procesos el usuario quiere ejecutar.

¿Que es una tabla de búsqueda?

Una tabla de búsqueda es simplemente un mapeo de los caracteres originales y los caracteres a los que deben traducirse en forma encriptada.

Hasta ahora, hemos estado iterando sobre cada una de las letras de la cadena y calculando sus posiciones cambiadas.

Esto es ineficiente porque nuestro conjunto de caracteres es limitado, y la mayoría de ellos ocurren más de una vez en la cadena.

Así que computar su equivalencia encriptada cada vez que ocurren no es eficiente, y se vuelve costoso si estamos encriptando un texto muy largo con cientos de miles de caracteres en él.

Podemos evitarlo computando las posiciones cambiadas de cada uno de los caracteres de nuestro conjunto de caracteres sólo una vez antes de iniciar el proceso de encriptación.

Así que si hay 26 letras mayúsculas y 26 minúsculas, necesitaríamos sólo 52 cálculos una vez y algo de espacio en la memoria para almacenar este mapeo.

Entonces durante el proceso de encriptación y desencriptación, todo lo que tendríamos que hacer es realizar una “búsqueda” en esta tabla – una operación que es más rápida que realizar una operación de módulo cada vez.

Crear una tabla de búsqueda

El módulo de cadenas de Python proporciona una forma fácil no sólo de crear una tabla de búsqueda, sino también de traducir cualquier cadena nueva basada en esta tabla.

Tomemos un ejemplo en el que queremos crear una tabla de las primeras cinco letras minúsculas y sus índices en el alfabeto.

Luego usaríamos esta tabla para traducir una cadena donde cada una de las ocurrencias de “a”, “b”, “c”, “d” y “e” son reemplazadas por “0”, “1”, “2”, “3” y “4” respectivamente; y los caracteres restantes no son tocados.

Utilizaremos la función maketrans() del módulo str para crear la tabla.

Este método acepta como primer parámetro una cadena de caracteres para la que se necesita una traducción, y otro parámetro de la misma longitud que contiene los caracteres mapeados para cada carácter de la primera cadena.

Vamos a crear una tabla para un ejemplo sencillo.

table = str.maketrans("abcde", "01234")

La tabla es un diccionario Python que tiene los valores Unicode de los personajes como claves, y sus correspondientes mapeos como valores.

Ahora que tenemos nuestra tabla lista, podemos traducir cadenas de cualquier longitud usando esta tabla.

Afortunadamente, la traducción también es manejada por otra función en el módulo de str, llamada translate.

Usemos este método para convertir nuestro texto usando nuestra tabla.

text = "Albert Einstein, born in Germany, was a prominent theoretical physicist."

translated = text.translate(table)

print("Original text:/n", text)

print("Translated text:/n", translated)

Salida:

traducción utilizando resultados de ejemplo de tabla de búsqueda

Como pueden ver, cada instancia de las primeras cinco letras minúsculas ha sido reemplazada por sus índices relativos.

Ahora usaremos la misma técnica para crear una tabla de búsqueda para César Cipher, basada en la clave proporcionada.

Implementar el cifrado

Vamos a crear una función caesar_cipher() que acepte una cadena para ser encriptada/desencriptada, el ‘juego de caracteres’ mostrando qué caracteres de la cadena deben ser encriptados (esto por defecto será en minúsculas),

la clave, y un valor booleano que muestra si se ha realizado el descifrado o no (cifrado).

import string

def cipher_cipher_using_lookup(text,  key, characters = string.ascii_lowercase, decrypt=False):

    if key < 0:

        print("key cannot be negative")

        return None

    n = len(characters)

    if decrypt==True:

        key = n - key

    table = str.maketrans(characters, characters[key:]+characters[:key])
    
    translated_text = text.translate(table)
    
    return translated_text

¡Esa es una función muy poderosa!

Toda la operación de cambio se ha reducido a una operación de rebanado.

Además, estamos usando el atributo string.ascii_lowercase – es una cadena de caracteres de la ‘a’ a la ‘z’.

Otra característica importante que hemos logrado aquí es que la misma función logra tanto el cifrado como el descifrado; esto se puede hacer cambiando el valor del parámetro ‘key’.

La operación de corte junto con esta nueva clave asegura que el conjunto de caracteres ha sido desplazado a la izquierda – algo que hacemos en el descifrado de un texto cifrado César de desplazamiento a la derecha.

Validemos si esto funciona usando un ejemplo anterior.

Cifraremos sólo las letras mayúsculas del texto y le daremos lo mismo al parámetro “caracteres”.

Cifraremos el texto: “¡HOLA MUNDO! ¡Bienvenido al mundo de la Criptografía!”

text = "HELLO WORLD! Welcome to the world of Cryptography!"

encrypted = cipher_cipher_using_lookup(text, 3, string.ascii_uppercase, decrypt=False)

print(encrypted)

Salida:

cifrado de letras mayúsculas con salida de ejemplo de tabla de búsqueda

Comprueba cómo la parte de “KHOOR ZRUOG” coincide con la encriptación de “HELLO WORLD” con la clave 3 en nuestro primer ejemplo.

También, noten que estamos especificando que el conjunto de caracteres sean letras mayúsculas usando string.ascii_uppercase

Podemos comprobar si el desencriptado funciona correctamente usando el mismo texto encriptado que obtuvimos en nuestro resultado anterior.

Si podemos recuperar nuestro texto original, significa que nuestra función funciona perfectamente.

text = "KHOOR ZRUOG! Zelcome to the world of Fryptography!"

decrypted = cipher_cipher_using_lookup(text, 3, string.ascii_uppercase, decrypt=True)

print(decrypted)

Output:

descifrado de letras mayúsculas usando la salida de ejemplo de la tabla de búsqueda

Fíjate en cómo hemos establecido el parámetro “desencriptar” de nuestra función en True.

¡Ya que hemos recuperado nuestro texto original, es una señal de que nuestro algoritmo de encriptación-desencriptación usando una tabla de búsqueda funciona bien!

Veamos ahora si podemos extender el conjunto de caracteres para incluir no sólo caracteres en minúsculas y mayúsculas sino también dígitos y puntuaciones.

character_set = string.ascii_lowercase + string.ascii_uppercase + string.digits + " "+ string.punctuation

print("Extended character set:\n", character_set)

plain_text = "My name is Dave Adams. I am living on the 99th street. Please send the supplies!"

encrypted = cipher_cipher_using_lookup(plain_text, 5, character_set, decrypt=False)

print("Plain text:\n", plain_text)

print("Encrypted text:\n", encrypted)

Salida:

cifrado con juego de caracteres personalizado usando la tabla de búsqueda

Aquí incluimos todos los caracteres que hemos discutido hasta ahora (incluyendo el carácter espacial) en el conjunto de caracteres a codificar.

¡Como resultado, todo (incluso los espacios) en nuestro texto simple ha sido reemplazado por otro símbolo!

La única diferencia aquí es que la envoltura no ocurre individualmente para los caracteres en minúsculas o mayúsculas, sino que ocurre como un todo para el conjunto de caracteres.

Esto significa que la “Y” con un desplazamiento de 3 no se convertirá en “B”, sino que se codificará en “1”.

Desplazamiento Negativo

Hasta ahora hemos hecho cambios “positivos” o “correctos” de los caracteres en el proceso de encriptación. Y el proceso de desencriptación para el mismo implicaba hacer un desplazamiento “negativo” o “izquierdo” de los caracteres.

¿Pero qué pasa si queremos realizar el proceso de encriptación con un desplazamiento negativo? ¿Cambiaría nuestro algoritmo de encriptación-desencriptación?

Sí, lo hará, pero sólo ligeramente. El único cambio que necesitamos para un desplazamiento a la izquierda es hacer que el signo de la clave sea negativo, el resto del proceso seguirá siendo el mismo y logrará el resultado de un desplazamiento a la izquierda en la encriptación y un desplazamiento a la derecha en el proceso de desencriptación.

Intentemos esto modificando nuestra función anterior añadiendo un parámetro más – ‘shift_type’ a nuestra función cipher_cipher_using_lookup().

import string

def cipher_cipher_using_lookup(text, key, characters = string.ascii_lowercase, decrypt=False, shift_type="right"):

    if key < 0:

        print("key cannot be negative")

        return None

    n = len(characters)

    if decrypt==True:

        key = n - key

    if shift_type=="left":

        # if left shift is desired, we simply inverse they sign of the key
        key = -key

    table = str.maketrans(characters, characters[key:]+characters[:key])

    translated_text = text.translate(table)

    return translated_text

Vamos a probar este metodo modificado en un texto simple:

text = "Hello World !"

encrypted = cipher_cipher_using_lookup(text, 3, characters = (string.ascii_lowercase + string.ascii_uppercase), decrypt = False, shift_type="left")

print("plain text:", text)

print("encrypted text with negative shift:",encrypted)

Salida:

cifrado de cifrado César con salida de desplazamiento negativo

Fíjense en cómo cada uno de los caracteres de nuestro texto simple se ha desplazado tres posiciones hacia la izquierda.

Comprobemos ahora el proceso de desencriptación usando la misma cadena.

text = "Ebiil Tloia !"

decrypted = cipher_cipher_using_lookup(text, 3, characters = (string.ascii_lowercase + string.ascii_uppercase), decrypt = True, shift_type="left")

print("encrypted text with negative shift:", text)

print("recovered text:",decrypted)

Salida:

descifrado de cifrado César utilizando salida de desplazamiento negativo

Así que podríamos encriptar y desencriptar un texto usando una tabla de búsqueda y una clave negativa.

Cifrado de Archivo

En esta sección, veremos cómo usar el Cifrado César para encriptar un archivo.

Tengan en cuenta que sólo podemos encriptar archivos de texto plano, y no archivos binarios porque conocemos el conjunto de caracteres de los archivos de texto plano.

Por lo tanto, se puede encriptar un archivo usando uno de los siguientes dos enfoques:

  1. Leer todo el archivo en una cadena, encriptar la cadena y volcarla en otro archivo.
  2. Leer el archivo de una línea a la vez, encriptar la línea y escribirla en otro archivo de texto.

Seguiremos con el segundo enfoque porque el primero es factible sólo para pequeños archivos cuyo contenido puede caber en la memoria fácilmente.

Así que definamos una función que acepte un archivo y lo encripte usando Cesar Cipher con un desplazamiento a la derecha de 3. Usaremos el conjunto de caracteres por defecto de letras minúsculas.

def fileCipher(fileName, outputFileName, key = 3, shift_type = "right", decrypt=False):

    with open(fileName, "r") as f_in:

        with open(outputFileName, "w") as f_out:

            # iterate over each line in input file
            for line in f_in:

                #encrypt/decrypt the line
                lineNew = cipher_cipher_using_lookup(line, key, decrypt=decrypt, shift_type=shift_type)

                #write the new line to output file
                f_out.write(lineNew)
                    
    print("The file {} has been translated successfully and saved to {}".format(fileName, outputFileName))

La función acepta el nombre del archivo de entrada, el nombre del archivo de salida y los parámetros de encriptación/desencriptación que vimos en la última sección.

Vamos a encriptar un archivo ‘milky_way.txt’ (tiene el párrafo introductorio de la página de la ‘Vía Láctea’ enWikipedia).
Daremos como salida el archivo encriptado a ‘milky_way_encrypted.txt‘.

Vamos a encriptarlo usando la función que hemos definido anteriormente:

inputFile = "./milky_way.txt"

outputFile = "./milky_way_encrypted.txt"

fileCipher(inputFile, outputFile, key=3, shift_type="right", decrypt = False)

Salida:

salida tras el cifrado exitoso de un archivo

Comprobemos cómo se ve nuestro archivo encriptado ‘Milky_way_encrypted.txt‘ ahora:

Tkh Mlonb Wdb lv wkh jdodab wkdw frqwdlqv rxu Srodu Sbvwhp, zlwk wkh qdph ghvfulelqj wkh jdodab'v dsshdudqfh iurp Eduwk: d kdcb edqg ri 
oljkw vhhq lq wkh qljkw vnb iruphg iurp vwduv wkdw fdqqrw eh lqglylgxdoob glvwlqjxlvkhg eb wkh qdnhg hbh.
Tkh whup Mlonb Wdb lv d...
...
...

Así que nuestra función encripta correctamente el archivo.

Como ejercicio, puedes probar la función de desencriptación pasando la ruta del archivo encriptado como entrada y estableciendo el parámetro ‘decrypt‘ en True.

Mira si eres capaz de recuperar el texto original.

Asegúrate de que no pasas la misma ruta de archivo como entrada y como salida, lo que llevaría a resultados no deseados ya que el programa haría la operación de lectura y escritura en el mismo archivo simultáneamente.

 Desplazamientos múltiples (Cifrado Vigenère)

Hasta ahora, hemos usado un único valor de desplazamiento (tecla) para desplazar todos los caracteres de las cuerdas por el mismo número de posiciones.

También podemos probar una variante de esto, en la que no usaremos una sola clave, sino una secuencia de claves para realizar diferentes desplazamientos en diferentes posiciones del texto.

Por ejemplo, digamos que usamos una secuencia de 4 teclas: 1,5,2,3] Con este método, nuestro primer carácter en el texto se desplazará una posición, el segundo carácter se desplazará cinco posiciones,

el tercer personaje por dos posiciones, el cuarto por tres posiciones, y luego otra vez el quinto personaje será desplazado por una posición, y así sucesivamente.

Esta es una versión mejorada del Cifrado César y se llama el Cifrado Vigenère.

Pongamos en práctica el Cifrado Vigenère.

def vigenere_cipher(text, keys, decrypt=False):

    # vigenere cipher for lowercase letters
    n = len(keys)

    translatedText =""

    i = 0 #used to record the count of lowercase characters processed so far

    # iterate over each character in the text
    for c in text:

        #translate only if c is lowercase
        if c.islower():

            shift = keys[i%n] #decide which key is to be used

            if decrypt == True:

                # if decryption is to be performed, make the key negative
                shift = -shift

            # Perform the shift operation
            shifted_c = chr((ord(c) - ord('a') + shift)%26 + ord('a'))

            translatedText += shifted_c

            i += 1

        else:

            translatedText += c
            
    return translatedText

La función realiza tanto el cifrado como el descifrado, dependiendo del valor del parámetro booleano “desencriptar”.

Llevamos la cuenta del total de letras minúsculas codificadas/descifradas usando la variable i, la usamos con el operador de módulo para determinar qué clave de la lista se usará a continuación.

Observe que hemos hecho la operación de desplazamiento muy compacta; esto equivale al proceso de varios pasos de conversión entre Unicode y valores de caracteres y el cálculo del desplazamiento que habíamos visto anteriormente.

Probemos esta función utilizando otro texto sencillo:

text = "we will call the first manned moon mission the Project Apollo"

encrypted_text = vigenere_cipher(text, [1,2,3])

print("Plain text:\n", text)

print("Encrypted text:\n", encrypted_text)

Salida:

cifrado César usando salida de turnos de cambio múltiple también conocido como vigenere

Aquí estamos realizando la encriptación usando las claves [1,2,3] y como era de esperar, el primer carácter “w” se ha desplazado una posición a “x”,

el segundo personaje “e” se ha desplazado dos posiciones a “g”; el tercer personaje “w” se ha desplazado tres posiciones a “z”.

Este proceso se repite con los caracteres siguientes.

Adelante, realice el proceso de desencriptación con las mismas claves y vea si puede recuperar la declaración original de nuevo.

¿Por qué el Cifrado es débil?

Tan simple como es entender e implementar el Cifrado César, hace más fácil para cualquiera descifrar el descifrado sin mucho esfuerzo.

El Cifrado César es una técnica de cifrado de sustitución donde reemplazamos cada carácter del texto por algún carácter fijo.

Si alguien identifica la regularidad y el patrón de aparición de ciertos caracteres en un texto cifrado, identificaría rápidamente que el Cifrado César ha sido utilizado para cifrar el texto.

Una vez convencido de que la técnica de Cesar Cipher ha sido usada para encriptar un texto, entonces recuperar el texto original sin la posesión de la llave es pan comido.

Un simple algoritmo de Fuerza Bruta calcula el texto original en un tiempo limitado.

Ataque a fuerza bruta

Romper un texto cifrado con el Cesar Cipher es sólo probar todas las claves posibles.

Esto es factible porque sólo puede haber un número limitado de claves que puedan generar un texto cifrado único.

Por ejemplo, si el texto cifrado tiene todo el texto en minúsculas codificado, todo lo que tenemos que hacer es ejecutar el paso de descifrado con valores de clave de 0 a 25.

Incluso si el usuario hubiera suministrado una clave superior a 25, produciría un texto cifrado igual a uno de los generados con claves entre 0 y 25.

Revisemos un texto cifrado que tenga todos sus caracteres en minúscula codificados, y veamos si podemos extraer un texto sensato de él usando un ataque de Fuerza Bruta.

El texto en nuestra mano es:

"ks gvozz ohhoqy hvsa tfca hvs tfcbh oh bccb cb Tisgrom"

Primero definamos la función de desencriptación que acepta un texto cifrado y una clave, y desencripta todas sus letras minúsculas.

def cipher_decrypt_lower(ciphertext, key):

    decrypted = ""

    for c in ciphertext:

        if c.islower(): 

            c_index = ord(c) - ord('a') 

            c_og_pos = (c_index - key) % 26 + ord('a')

            c_og = chr(c_og_pos)

            decrypted += c_og

        else:

            decrypted += c

    return decrypted

Ahora tenemos nuestro texto, pero no conocemos la clave, es decir, el valor de desplazamiento. Escribamos un ataque de fuerza bruta, que pruebe todas las teclas de 0 a 25 y muestre cada una de las cadenas desencriptadas:

cryptic_text = "ks gvozz ohhoqy hvsa tfca hvs tfcbh oh bccb cb Tisgrom"

for i in range(0,26):

    plain_text = cipher_decrypt_lower(cryptic_text, i)

    print("For key {}, decrypted text: {}".format(i, plain_text))

Salida:

cifrado César salida de ataque de fuerza bruta

La salida enumera todas las cadenas que se pueden generar a partir del desencriptado.

Si lo miras de cerca, la cadena con clave 14 es una declaración válida en inglés y por lo tanto es la elección correcta.

cadena correcta ubicada en la salida de ataque de fuerza bruta

Ahora ya sabes cómo romper un texto cifrado con el Cifrado César.

Podríamos usar otras variantes más fuertes del Cifrado César, como el uso de múltiples turnos (Cifrado Vigenère), pero incluso en esos casos, determinados atacantes pueden descifrar el desencriptado correcto fácilmente.

Así que el algoritmo del Cifrado César es relativamente mucho más débil que los modernos algoritmos de cifrado.

Conclusión

En este tutorial, aprendimos qué es el Cifrado César, cómo es fácil de implementar en Python, y cómo su implementación puede ser optimizada aún más usando lo que llamamos “tablas de búsqueda”.

Escribimos una función en Python para implementar un algoritmo genérico de encriptación/desencriptación del Cifrado Cesar que toma varias entradas de usuario como parámetro sin asumir mucho.

Luego miramos cómo podemos encriptar un archivo usando Cifrado Cesar, y luego cómo Cifrado Cesar puede ser reforzado usando múltiples turnos.

Finalmente, vimos cómo la vulnerabilidad del Cifrado Cesar a los ataques de la Fuerza Bruta.

Mokhtar Ebrahim
Fundadora de LikeGeeks. Estoy trabajando como administrador de sistemas Linux desde 2010. Soy responsable de mantener, proteger y solucionar problemas de servidores Linux para múltiples clientes de todo el mundo. Me encanta escribir guiones de shell y Python para automatizar mi trabajo.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *