7.3. Trabajo de prácticas: Circuito cifrador de Vigenère

7.3.1. Objetivo

En este trabajo se desarrollará un cifrador usando el métdo de Vigenère. El alfabeto que se va a usar serán todos los códigos ASCII que se comprenden entre el espacio (hexadecimal x20) y el comando DEL (hexadecimal x7F). El texto a cifrar consistirá en 4 líneas de 64 caracteres, lo que hace un total de 256 caracteres. El resultado encriptado tendrá el mismo número de caracteres. La clave a utilizar serán 8 caracteres ASCII. Tanto el texto a cifrar como el resultado se manejarán en grupo de 8 caracteres cada uno de los cuales tendrán 8 bits, y se presentarán en una pantalla VGA.

El trabajo se realizará por dos personas.

7.3.2. Introducción

El cifrado de Vigenère es un cifrado por sustitución simple polialfabético. Normalmente el cifrado se realiza mediante un cuadro o tabla con un alfabeto de solo letras. En este trabajo usaremos un alfabeto ampliado de 96 caracteres, que son todos los que van en el código ASCII entre los hexadecimales x20 (espacio) y el x7F (comando DEL). De esta forma podremos cifrar frases que tengan mayúsculas, minúsculas, números y signos de puntuación. La clave que utilizaremos tendrá 8 caracteres ASCII en el mismo rango mencionado anteriormente.

Si tenemos un texto a cifrar \(X\) con \(i\) caracteres y una clave \(K\) también con \(i\) caracteres, en términos matemáticos, puede expresarse la función de cifrado de Vigenère como:

\(E(X_{i}) = (X_{i} + K_{i}) mod L\)

Donde \(X_{i}\) es la letra en la posición del texto a cifrar, \(K_{i}\) es el carácter de la clave correspondiente, pues se encuentran en la misma posición, y \(L\) es el tamaño del alfabeto (en nuestro caso 96). Para obtener el texto cifrado del texto completo C se debe trocear el texto original en grupos que contenga tantos caracteres como tenga la clave K. Al elegir claves con 8 caracteres el texto debe estar compuesto por bloques de 8 caracteres, es decir 64 bits.

Para descifrar C realizamos la operación inversa:

Cuando \((Ci - Ki) >= 0\)

\(D(E_{i}) = (C_{i} - K_{i}) mod L\)

Cuando \((Ci - Ki) < 0\)

\(D(E_{i}) = (C_{i} - K_{i} + L) mod L\)

Donde \(C_{i}\) es el carácter en la posición \(i\) del texto cifrado, \(K_{i}\) viene siendo el carácter de la clave correspondientea , y \(L\) el tamaño del alfabeto. Se observa que a una misma letra en el texto plano le pueden corresponder diferentes letras en el texto cifrado, depeniendo el valor de la clave.

7.3.3. Definición del cifrador a realizar

Se debe realizar un cifrador siguiendo el procedimiento de Vigenère tal como se ha definido en el apartado anterior. El texto a cifrar constará de 4 líneas cada una de las cuales contiene 64 caracteres ASCII. El sistema cifrará el texto dividiendo cada línea en bloques independientes de 8 caracteres: habrá 8 bloques por línea y 4 líneas, y en total serán 32 bloques de 8 caracteres cada uno. Los caracteres ASCII estarán representados en 8 bits (ver Figura 7.17) y, por tanto, cada bloque tiene \(8x8 = 64\) bits.

../../_images/Block_character.jpg

Figura 7.17 Representa un bloque de 8 caracteres y como cada carácter es un ASCII de 8 bits.

La Figura 7.18 representa el formato de entrada de los bloques a cifrar. El sistema leerá y procesará los bloques de entrada hasta que la señal last_block se ponga a nivel “1”. Eso indicará que estamos procesando el último bloque de la última línea.

../../_images/Definicion_bloque_in.jpg

Figura 7.18 Definición de los bloques de entrada a cifrar.

La clave con la que vamos a cifrar los bloques \((K_{i})\) también tendrá 8 caracteres (en total 64 bits). Para procesar cada bloque se proporcionará al sistema, uno a uno, los caracteres de la clave hasta llegar al último, momento en el cual la señal last_key se podrá a “1”. La señal last_key = 1 indicará que ya hemos procesado el bloque actual y que pasaremos a procesar el bloque siguiente.

../../_images/Key_in.jpg

Figura 7.19 Definición de la clave.

En la Figura 7.20 se representa el bloque general del cifrador (encrypt_top), incluyendo la unidad de control y el data-path. El datapath recibe el bloque a cifrar (block_in) cuando se activa la señal inicio. Posteriormente irá recibiendo la clave \((K_{i})\) cuando se activa la señal enable. Cuando termine de procesar el bloque pondrá la señal done_out a 1. Cuando reciba la señal last el data-path sabrá que está procesando el último bloque del texto a cifrar.

Por otro lado, el control del cifrador gestionará la transacción de bloques de entrada y la clave, de manera que cuando reciba las señales ready_block y ready_key indicará al data-path que hay que procesar los datos a través de las señales inicio y enable respectivamente. Además, activará la señal end of block (eob) en el momento que se reciba el útlimo carácter de la clave (last_key = 1). Finalmente, cuando se activen las señales last_block y last_key al mismo tiempo dará por finalizado todo el proceso.

../../_images/Diagrama_Bloques_cifrador.jpg

Figura 7.20 Diagrama de bloques general, incluyendo la unidad de control y el data-path.

7.3.3.1. Descripción del datapath

La Figura 7.21 representa un diagrama general del data-path. En el diagrama BASE es el valor en 8 bits del carácter ASCII espacio (Hexadadecima x20, “0010 0000”), que es el primer carácter del alfabeto que se usa para cifrar. Tanto al carácter a cifrar como a la clave debe restarse el valor de la BASE. Cuando se hagan las operaciones \(E(X_{i}) = (X -{i} + K -{i}) mod L\), se debe volver a sumar la base, para convertir el resultado en un carácter ASCII. \(L\) es la longitud total del alfabeto (96 caracteres en total). El \(mod L\) se realiza restando \(L\) al resultado si éste es mayor que \(L\).

Cada bloque de entrada es de 64 bits y se debe procesar carácter a carácter (cada 8 bits). Por eso se requiere un multiplexor a la entrada y un demultiplexor a la salida. El bloque de entrada (block_in) se debe almacenar en unos registros cuando la señal inicio se activa (inicio = 1) y el resultado se debe registrar con la señal enable. La señal index_key se debe obtener de un contador cuya activación se realiza con la señal enable (cuenta en cada flanco de reloj que enable sea igual a 1). Este contador no se muestra en la figura.

../../_images/Datapath_Vigenere.jpg

Figura 7.21 Diagrama general del data-path.

7.3.3.2. Descripción del control

Un ejemplo de grafo se muestra en la Figura 7.22. Como se observa, una vez que start = 1 el sistema pasa al estado load donde esperará a que un bloque a cifrar este disponible, lo que se indicará con ready_block = 1. Cuando esto ocurre se activará la señal inicio y se pasará al estado encript. En este estado podrá ocurrir una de las siguientes posibilidades:

  • ready_key = 0, esperaremos a que ready_key = 1.

  • ready_key = 1 y last_key = 0, activaremos enable y desactivamos inicio.

  • ready_key = 0 y last_key = 1, pasaremos a esperar un nuevo bloque, siempre que last_block = 0.

  • ready_key = 0, last_key = 1 y last_block = 1 se ha terminado de procesar las 4 líneas de texto.

../../_images/Grafo_estados.jpg

Figura 7.22 Grafo de estados del controlador del cifrador.

7.3.4. Trabajo previo

Como trabajo previo se debe diseñar la máquina de estados del control usando la definición de la Entidad que aparece en el Listado 7.13. Posteriormente incluir la descripción en el VPL del Campus Virtual. Cuando esté funcionando, de debe hacer la descripción del data-path usando la entidad que se describe en el Listado 7.14 y la descripción estructural del cifrador usando como entidad la que se encuentra en el Listado 7.15.

7.3.5. Trabajo en el laboratorio

Para implementar el diseño en Quartus-II necesitaremos las descripciones encript_control.vhd, encript_round.vhd y encript_top.vhd realizadas en el trabajo previo, pero también necesitaremos descripciones VHDL de memorias que almacenen el texto a cifrar y la clave, un controlador VGA para representar la información en pantalla y una memoria RAM que almacene temporalmente la información a presentar en la pantalla. Desde el Campus Virtual se deben descargar un fichero .zip que contiene los siguientes directorios:

  1. RAM: incluirá una memoria ROM (dual_port_rom_texto.vhd) que contiene el texto a cifrar y la clave; una memoria RAM (RAM_texto.vhd) que temporalmente almacenará el texto a presentar en la pantalla y una memoria ROM (fontROM.vhd) que contiene la presentación de los caracteres ASCII. Si se quiere editar la clave o el texto a cifrar debe editarse el fichero dual_port_rom_texto.vhd. Las otras dos no deben modificarse.

  2. utils-vga: controlador VGA y otras ficheros VHDL necesarios para que se pueda presentar la información en la pantalla VGA.

  3. utils-display: descripciones VHDL necesarias para representar la información en los displays de 7-segmentos.

  4. utils-counter: contadores necesarios en el diseño.

  5. ip: ip de Altera para controlar el reloj en la pantalla VGA y en el diseño.

  6. quartus: directorio de trabajo donde deben incluirse as descripciones encript_control.vhd, encript_round.vhd y encript_top.vhd realizadas. También contiene la asignación de pines y la descripción top del trabajo de asignatura.

Nota

No olvidar incluir en el diseño todos los ficheros VHDL que se encuentran el fichero .zip. De otra forma se producirían errores de compilación.

7.3.5.1. Diagrama general

La Figura 7.23 muestra los principales elementos del diseño, con un código de colores según en el directorio en el que se encuentran, y las principales conexiones entre ellos. Como se observa el encript system controller se encarga de leer el texto a cifrar y la clave de la ROM dual_port_rom_texto.vhd y pasarlas al encriptador (encript_top.vhd). Una vez finalizado el cifrado de un bloque éste último pasa el resultado al encript system controller, que a su vez lo pasa al text_presentation para que lo almacene en la RAM y sea presentado en pantalla.

../../_images/Diagrama_general_quartus.jpg

Figura 7.23 Diagrama general del sistema en Quartus-II.

7.3.5.2. Representación de información en la DE10-Lite

Una vez programada la tarjeta DE10-Lite con el diseño del cifrador, se presentará en poantalla el texto a cifrar. La entrada Start, que da inicio al cifrado, estará conectada a uno de los botones de la tarjeta (key 1) y el otro botón hará las veces de reset del sistema (key 0). De los switches solo se utilizará el 9 que se utilizará para visualizar temporalmente la clave. El resto de señales se muestran en la figura siguiente:

../../_images/cifrador_pinout.png

Figura 7.24 Pin out del cifrador de Vigenère.

7.3.5.3. Comprobación del funcionamiento

Una forma de comprobar que nuestro diseño realiza bien el cifrado del texto es utilizar el código python del Listado 7.16. Es necesario incluir el texto a cifrar en un fichero de texto denominado input_message.txt y la clave de ocho caracteres ASCII en el fichero Key.txt.

7.3.6. Mejoras en el diseño

Aunque mucho tiempo se creyó que el cifrador de Vigenére es robusto, en la actualidad se sabe que no lo es. En concreto la versión que tenemos tiene una debilidad evidente, ya que el que conozca el alfabeto usado y la longitud de la clave, puede deducir la clave usada únicamente fijándose en el resultado de cifrar los espacios. En esta mejora se propone modificar el data-path del cifrador para detectar cuando el carácter de entrada es un espacio y representarlo a la salida tal cual (como un espacio).

7.3.7. Entregables

7.3.7.1. Trabajo previo

Por hacer

ET3.1. Memoria de trabajo incluyendo el grafo de estados de la solución propuesta y el diseño del data-path.

ET3.2. Diagrama de bloques completo del encriptador.

7.3.7.2. Trabajo práctico

Por hacer

ET3.3. Resolución laboratorio virtual (VPL) en el Campus Virtual de la máquina de control.

ET3.4. Resolución laboratorio virtual (VPL) en el Campus Virtual del encriptador de Vigenère.

ET3.5. Implementación del cifrador serie en la tarjeta DE10-Lite.

7.3.8. Listados VHDL

Lista 7.13 ENTITY del encript_control.vhd.
 LIBRARY ieee;
 USE ieee.std_logic_1164.all;
 USE ieee.numeric_std.all;

 ENTITY encrypt_control IS
     PORT(
         clock       : IN STD_LOGIC;
         reset_n     : IN STD_LOGIC;
         start       : IN STD_LOGIC;
         ready_block : IN STD_LOGIC;
         last_block  : IN STD_LOGIC;
         ready_key   : IN STD_LOGIC;
         last_key    : IN STD_LOGIC;
         done_in        : IN STD_LOGIC;
         inicio      : OUT STD_LOGIC;
         enable      : OUT STD_LOGIC;
         eob         : OUT STD_LOGIC;
         ready       : OUT STD_LOGIC
     );
 END encrypt_control;
Lista 7.14 ENTITY del encript_round.vhd.
 LIBRARY ieee;
 USE ieee.std_logic_1164.all;
 USE ieee.numeric_std.all;

 LIBRARY ieee;
 USE ieee.std_logic_1164.all;
 USE ieee.numeric_std.all;

 ENTITY encrypt_round is
     PORT(
         clock     : IN  STD_LOGIC;
         reset_n   : IN  STD_LOGIC;
         inicio    : IN  STD_LOGIC;
         enable    : IN  STD_LOGIC;
         last      : IN  STD_LOGIC;
         block_in  : IN  STD_LOGIC_VECTOR(63 DOWNTO 0);
         subkey_in : IN  STD_LOGIC_VECTOR(7 DOWNTO 0);
         block_out : OUT STD_LOGIC_VECTOR(63 DOWNTO 0);
         done_out  : OUT STD_LOGIC
     );
 END encrypt_round;
Lista 7.15 ENTITY del encript_top.vhd.
 library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
   use IEEE.math_real.all;
   use work.all;

 entity cifrador_top is
     port(
         clock  : in std_logic;
         reset  : in std_logic; -- key0
         start_in  : in std_logic;  -- key1
         show_key  : in std_logic;  -- SW9
         key_print : out std_logic;

         Led    : out std_logic_vector(7 downto 0);
         done_out : out std_logic;

         VGA_HS  : out std_logic;
         VGA_VS  : out std_logic;
         VGA_R   : out std_logic_vector(3 downto 0);
         VGA_G   : out std_logic_vector(3 downto 0);
         VGA_B   : out std_logic_vector(3 downto 0);
         locked  : out std_logic
     );
 end cifrador_top;
Lista 7.16 Descripción en Python del cifrador de Vigenère.
 def vigenere_encrypt(plain_text, key):
     encrypted_text = []
     key_length = len(key)
     alphabet_range = 0x7F - 0x20 + 1

     for i, char in enumerate(plain_text):
         char_code = ord(char)
         key_char_code = ord(key[i % key_length])

         encrypted_char_code = (char_code - 0x20 + key_char_code - 0x20) % alphabet_range + 0x20
         encrypted_char = chr(encrypted_char_code)
         encrypted_text.append(encrypted_char)

     return ''.join(encrypted_text)

 # Leer el texto de entrada desde el archivo "input_message.txt"
 with open("input_message.txt", "r") as input_file:
     input_lines = input_file.readlines()

 # Leer la clave desde el archivo "Key.txt"
 with open("Key.txt", "r") as key_file:
     key = key_file.read()

 # Realizar el cifrado de Vigenère línea por línea
 encrypted_lines = []

 for line in input_lines:
     encrypted_line = vigenere_encrypt(line.rstrip(), key)
     encrypted_lines.append(encrypted_line)
     encrypted_lines.append("\n")

 # Guardar el texto cifrado en el archivo "encripted.txt" manteniendo el formato
 with open("encripted.txt", "w") as output_file:
     for line in encrypted_lines:
         output_file.write(line)

 print("Texto encriptado y guardado en 'encripted.txt'")