G A C E T A   D E   L I N U X
...haciendo a Linux un poco más divertido!
Encriptación empleando librerías criptográficas de OpenSSL
Por Vinayak Hegde
Traducción al español por Jesus Marcos
el día 14 de Febrero 2003, para La Gaceta de Linux

 

Razones para el artículo

Linux ya ha realizado algunas incursiones en el mundo corporativo. Una de las demandas más persistentes de este mundo ha sido la necesidad de una mejor seguridad de los datos. Aquí es donde llega la encriptación, para ocultar los datos sensibles de un intruso externo. El software de código abierto tiene cierta reputación para la programación segura. Este artículo da otro paso en la misma dirección.

libcrypto de OpenSSL es una buena librería si quiere usar encriptación sin preocupaciones con los detalles de la implementación del algoritmo. El problema es que la documentación es realmente mínima. Naturalmente puede leer los fuentes y figurarse qué hacen. También el echo de que los nombres de las funciones son intuitivos ayuda hasta cierto punto. Otra forma de obtener ayuda es suscribirse a las listas de correo del sitio de OpenSSL . De cualquier manera las herramientas de la línea de comandos de OpenSSL están muy bien documentadas y sin fáciles de usar. Explicaré en este artículo como emplear el algoritmo para encriptación Bluefish usando las librrías de criptografía de OpenSSL.

Alguna información de trasfondo

Durante los primeros días de la criptografía, los algoritmos, además de las claves, eran secretos. Sin embargo actualmente esta tendencia ha cambiado. Ahora los algoritmos son publicamente conocidos y las claves son secretas. El mejor ejemplo es el algoritmo RSA que es ampliamente conocido e implementado. La clave pública es conocida para el mundo pero la clave privada es almacenada en secreto. RSA es un algoritmo asimétrico ya que no usa la misma clave para la encriptación y para la desencriptación. Aunque generalmente no es conveniente emplear RSA para encriptar grandes cntidades de datos ya que es computacionalmente caro.

Para encriptar una gran cantidad de datos, es preferible emplear algoritmos con bajo coste computacional. En este artículo empleamos el algoritmo blowfish para encriptar y desencriptar datos. Blowfish es un algoritmo asimétrico, es decir, emplea la misma clave para la encriptación y la desencriptación. Blowfish fue diseñado por el famoso criptógrafo Bruce Schneier. Blowfish es un algoritmo rápido para la encriptación/desencriptación.

Generando la clave

Para los propositos de la demostración vamos a usar una clave de 128 bits. Esta está almacenada como un array de caracteres en el programa. También generaremos un vector de inicialización (VI) de 64 bits. Para nuestro programa usaremos el modo Cipher Block Chaining (CBC) . Además no emplearemos las funciones de blowfish directamente sino que las usaremos a través de la interfaz de alto nivel.

Un vector de inicialización es un poco de información aleatoria que es usada como entrada en los algoritmos de encriptación encadenada, esto es, cuando cada etapa de encriptación de un bloque de datos de entrada provee algún dato a la encriptación del siguiente bloque. (Blowfish emplea bloques de 64 bits para la encriptacion). El VI proporciona el primer bit de entrada para la encriptación del primer bloque, que proporciona la entrada para el segundo bloque, y así consecutivamente. El bit más a la izquierda es descartado.

Los bits aleatorios son generados a partir fichero de caracteres especiales /dev/random el cual es una buena fuente para números aleatorios. Vea la página de manual para más información.


int
generate_key ()
{
	int i, j, fd;
	if ((fd = open ("/dev/random", O_RDONLY)) == -1)
		perror ("open error");

	if ((read (fd, key, 16)) == -1)
		perror ("read key error");

	if ((read (fd, iv, 8)) == -1)
		perror ("read iv error");
	
	printf("128 bit key:\n");
	for (i = 0; i < 16; i++)
		printf ("%d \t", key[i]);
	printf ("\n ------ \n");

	printf("Initialization vector\n");
	for (i = 0; i < 8; i++)
		printf ("%d \t", iv[i]);

	printf ("\n ------ \n");
	close (fd);
	return 0;
}

La rutina de encriptación

La rutina de encriptación obtiene dos parámetros - los descriptores de fichero del fichero de entrada y el fichero de salida en el que se van a salvar los datos encriptados. Siempre es una buena idea rellenar los buffers con ceros usando los comandos memset o bzero antes de emplear los buffers con datos. Esto es especialmente importante si tiene planeado reusar los buffers. en el programa de abajo, los datos de entrada están siendo encriptados en bloques de 1K cada uno:

Los pasos para la encriptación son los siguientes :-

  1. Crear un contexto de cifrado
  2. Inicializar el contexto de cifrado con los valores de Key y IV
  3. Llamar a EVP_EncryptUpdate para encriptar bloques sucesivos de 1K cada uno
  4. Llamar a EVP_EncryptFinal para encriptar los datos sobrantes
  5. Finalmente llamar a EVP_CIPHER_CTX_cleanup para descartar toda la información sensible de memoria

Puede que se pregunte qué son los datos sobrantes. Como se mencionó hace un momento, Blowfish encripta la información en bloques de 64 bits cada uno. Algunas veces no disponemos de 64 bits para construir un bloque. Esto puede ocurrir si el tamaño del buffer en el programa de abajo o el tamaño de los datos del fichero de entrada no son un múltiplo entero de 8 bytes (64 bits). Así que por lo tanto los datos se rellenan y luego el bloque parcial es encriptado usando EVP_EncryptFinal. La longitud del bloque de datos encriptados es almacenada en la variable tlen y sumada a la longitud final.


int
encrypt (int infd, int outfd)
{
	unsigned char outbuf[OP_SIZE];
	int olen, tlen, n;
	char inbuff[IP_SIZE];
	EVP_CIPHER_CTX ctx;
	EVP_CIPHER_CTX_init (& ctx);
	EVP_EncryptInit (& ctx, EVP_bf_cbc (), key, iv);

	for (;;)
	  {
		  bzero (& inbuff, IP_SIZE);

		  if ((n = read (infd, inbuff, IP_SIZE)) == -1)
		    {
			    perror ("read error");
			    break;
		    }
		  else if (n == 0)
			  break;

		  if (EVP_EncryptUpdate (& ctx, outbuf, & olen, inbuff, n) != 1)
		    {
			    printf ("error in encrypt update\n");
			    return 0;
		    }

		  if (EVP_EncryptFinal (& ctx, outbuf + olen, & tlen) != 1)
		    {
			    printf ("error in encrypt final\n");
			    return 0;
		    }
		  olen += tlen;
		  if ((n = write (outfd, outbuf, olen)) == -1)
			  perror ("write error");
	  }
	EVP_CIPHER_CTX_cleanup (& ctx);
	return 1;
}

La rutina de desencriptación

La rutina de desencriptación básicamente sigue los mismos pasos de la rutina de desencriptación. El siguiente código muestra como se realiza la desencriptación.
 

int
decrypt (int infd, int outfd)
{
	unsigned char outbuf[IP_SIZE];
	int olen, tlen, n;
	char inbuff[OP_SIZE];
	EVP_CIPHER_CTX ctx;
	EVP_CIPHER_CTX_init (& ctx);
	EVP_DecryptInit (& ctx, EVP_bf_cbc (), key, iv);

	for (;;)
	  {
		  bzero (& inbuff, OP_SIZE);
		  if ((n = read (infd, inbuff, OP_SIZE)) == -1)
		    {
			    perror ("read error");
			    break;
		    }
		  else if (n == 0)
			  break;

		  bzero (& outbuf, IP_SIZE);

		  if (EVP_DecryptUpdate (& ctx, outbuf, & olen, inbuff, n) != 1)
		    {
			    printf ("error in decrypt update\n");
			    return 0;
		    }

		  if (EVP_DecryptFinal (& ctx, outbuf + olen, & tlen) != 1)
		    {
			    printf ("error in decrypt final\n");
			    return 0;
		    }
		  olen += tlen;
		  if ((n = write (outfd, outbuf, olen)) == -1)
			  perror ("write error");
	  }

	EVP_CIPHER_CTX_cleanup (& ctx);
	return 1;
}

El código completo

Un pequeño programa interactivo implementando las rutinas de arriba puede ser descargado de aquí. El comando para compilar el programa es
# gcc -o blowfish sym_funcs.c -lcrypto
El programa obtiene tres ficheros de la línea de comandos

  1. Fichero a encriptar
  2. Fichero en el que se van a almacenar los datos encriptados
  3. Ficheros en los que los datos desencriptados se van a almacenar
No olvide generar una clave antes de encriptar ;).

Una aplicación de ejemplo - Un mensajero instantáneo seguro

Considere un programa de mensajería instantánea (IM) el cual quiere comunicarse con otro IM de forma segura. La siguiente aproximación podría ser seguida.

  1. Cada cliente IM tiene su propia clave pública y privada.
  2. El cliente IM tiene las claves públicas de todos los IM con los que quiere comunicarse (los llamaremos IM amigos).
  3. La clave de la sesión es generada por el cliente que inicia la sesión. Esta clave de sesión es usada para encriptar los mensajes entre los dos clientes.
  4. La clave de sesión está encriptada e intercambiada entre dos o más clientes, empleando encriptación de clave pública (ej.: algoritmo RSA). De este modo la autentificación también está custodiada.
  5. El intercambio de datos encriptados (usando la encriptación simétrica de Blowfish) tiene lugar después entre los diferentes clientes tras este "acuerdo seguro".

Recursos

  1. OpenSSL Homepage
  2. The Blowfish Algorithm
  3. Handbook of Applied Cryptography

 

[BIO] Mi vida cambió desde que descubrí Linux. De repente las computadoras se volvieron interesantes ya que podía intentar montones de cosas en mi máquina Linux debido a la secilla disponibilidad de los fuentes del código. Mis intereses son predominantemente en el área de red, sistemas embebidos y lenguajes de programación. Actualmente trabajo para Aparna Web services donde hacemos Linux accesible para academias/corporaciones configurando estaciones de arranque remoto (Thin Clients)


Copyright © 2003, Vinayak Hegde. Copying license http://www.linuxgazette.com/copying.html
Publicado en el número 87 de Linux Gazette, Febrero 2003