OpenSSL y seguridad con cifrado simétrico

Publicado por Diego Córdoba en

Ya hemos aprendido los conceptos fundamentales del cifrado simétrico. Ahora veremos cómo aplicar confidencialidad, autenticidad e integridad mediante OpenSSL.

Info y listado de algoritmos en OpenSSL

Primero es interesante conocer algunos parámetros de nuestro OpenSSL.

Por ejemplo, para conocer la versión de nuestro OpenSSL podemos usar el subcomando versión:

diego@cryptos ~ $ openssl version
OpenSSL 1.1.1q  5 Jul 2022

OpenSSL, como implementación del protocolo TLS, incorpora las denominadas cipher suites, conjuntos de algoritmos simétricos, asimétricos y funciones Hash necesarios para lograr una comunicación segura. Podemos listar estas cipher suites de esta forma:

diego@cryptos ~ $ openssl ciphers -V

Omito la salida porque es muuuy larga 🙂

Particularmente para el trabajo de este artículo, donde aprenderemos a encriptar y desencriptar de manera simétrica, será últil el siguiente comando para listar los algoritmos de cifrado disponibles:

diego@cryptos ~ $ openssl enc -list
Supported ciphers:
-aes-128-cbc     -aes-128-cfb     -aes-128-cfb1
-aes-128-cfb8    -aes-128-ctr     -aes-128-ecb
-aes-128-ofb     -aes-192-cbc     -aes-192-cfb
-aes-192-cfb1    -aes-192-cfb8    -aes-192-ctr
-aes-192-ecb     -aes-192-ofb     -aes-256-cbc
-aes-256-cfb     -aes-256-cfb1    -aes-256-cfb8
-aes-256-ctr     -aes-256-ecb     -aes-256-ofb
[...]

El subcomando enc lista los algoritmos simétricos disponibles, para cifrado asimétrico se usan otros subcomandos que analizaremos en otro artículo.

En esta lista pueden verse algunos datos importantes: algoritmos (AES, CAST, DES, BF, etc.), muchos de ellos con sus tamaños de clave asociados (128, 192, 256, etc.) y, si se trata de cifradores simétricos de bloque, el modo de encadenamiento de dichos bloques (ECB, CBC, CTR, OFB, CFB, etc.).

Estos algoritmos son los que debemos elegir más adelante para encriptar y, posteriormente, desencriptar un archivo.

Función de derivación de clave

Antes de continuar, es necesario aclara un punto importante en los mecanismos de cifrado simétrico: la función de derivación de clave.

Cuando vamos a encriptar o desencriptar archivos usando herramientas como OpenSSL, introducimos una contraseña, o una passphrase, que solo nosotros y el destinatario del mensaje conocemos.

Ahora bien, hemos hablado en el artículo anterior sobre encriptación con algoritmos y claves de cierto tamaño: 128 bits, 256 bits, etc.

Si la clave que vamos a usar es de 128 bits, ¿es requisito que nuestra contraseña de cifrado/descifrado tenga ese tamaño? ¿podría calcularse?

La realidad es que no, nosotros ponemos contraseñas complejas o simples, y la implementación criptográfica genera, mediante una función de derivación de clave, una clave de determinado tamaño para usar en el cifrado.

Por supuesto, si a la función de derivación de clave le introducimos la misma contraseña, generará la misma clave.

Esta función de derivación de clave, o KDF (Key Derivation Function) depende de la implementación de la aplicación criptográfica. Existen varias implementaciones, pero una de las más utilizadas es PBKDF2.

PBKDF2

PBKDF proviene de «Password-Based Key Derivation Function», y toma como argumento, entre otros, una contraseña de usuario, generando en base a ella, una clave de cierto tamaño.

El prototipo de la función, con sus argumentos, es el siguiente:

key = pbkdf2(PRF, Pass, Salt, Iter, Size)

Donde:

  • PRF: función pseudo-random para generar el salt.
  • Pass: contraseña de usuario.
  • Salt: serie de bits pseudo-aleatoria utilizada para incrementar la resistencia a ataques de fuerza bruta.
  • Iter: cantidad de iteraciones del algoritmo. Mientras más iteraciones, más segura será la clave generada, y más tiempo requerirá su generación.
  • Size: tamaño resultante de la clave generada.

En versiones de OpenSSL anteriores a la v1.1.1, la función de derivación de clave predeterminada era utilizar el hash SHA-256 sobre la contraseña.

Esto hoy se considera obsoleto y vulnerable a ataques de fuerza bruta, por lo que si no especificamos una función de derivación de clave, obtendremos una advertencia.

Las opciones: usar PBKDF2 con un número predeterminado de iteraciones, o especificando la cantidad de iteraciones que queremos. Así es que los OpenSSL con versiones mayores a v1.1.1 disponen de dos opciones extra:

  • -pbkdf2: habilita esta función de derivación de clave, con una cantidad predeterminada de iteraciones.
  • -iter nnnn: habilita PBKDF2 como función de derivación de clave, con nnnn iteraciones.

La cantidad de iteraciones predeterminadas se encuentra hardcodeado en el código fuente de OpenSSL, particularmente en el archivo apps/enc.c. Acabo de revisarlo y es de 10000 iteraciones.

En fin, encriptemos y veamos cómo se usan estas opciones.

Confidencialidad con OpenSSL

Veamos el primer pilar de la seguridad en las comunicaciones: la confidencialidad… en la práctica: cómo podemos enviar un dato a alguien más, y que solamente esta persona pueda leerlo.

Encriptando un archivo

Supongamos que tenemos un archivos de texto plano llamado archivo.txt, y supongamos que elegimos un cifrador simétrico de bloques AES, con claves de 256 bits, en modo de cifrado CTR. Podríamos encriptar el archivo utilizando un comando como el siguiente:

$ openssl enc -aes-256-ctr -pbkdf2 -in archivo.txt -out archivo.txt.aes
enter aes-256-ctr encryption password:
Verifying - enter aes-256-ctr encryption password:

Donde:

  • El algoritmo -aes-256-ctr debe ser uno de los algoritmos válidos listados arriba.
  • -pbkdf2 es la función de derivación de claves con el número de iteraciones predeterminado.
  • -in: permite especificar el archivo a encriptar.
  • -out: permite especificar cuál será el archivo encriptado.

El archivo encriptado tendrá un formato binario particular… veamos qué devuelve el comando file en GNU/Linux:

diego@cryptos $ file archivo.txt.aes 
archivo.txt.aes: openssl enc'd data with salted password

De requerir más iteraciones en PBKDF2 podríamos haber utilizado la algo como -iter 50000 en lugar de -pbkdf2.

Este procedimiento podemos repetirlo con cualquiera de los algoritmos soportados por OpenSSL en su subcomando enc, listados al principio.

Desencriptando un archivo

Tenemos el archivo encriptado, en nuestro caso archivo.txt.aes. Supongamos que le enviamos este archivo al destinatario, y éste lo intenta desencriptar. Para ello, necesitará conocer:

  • La contraseña utilizada para encriptar el archivo.
  • La función de derivación de clave y la cantidad de iteraciones.
  • El algoritmo utilizado para encriptar, incluyendo el modo de cifrado y el tamaño de la clave.

Suponiendo que tenemos todo esto, vamos a utilizar el modificador -d para desencriptar, de esta forma:

$ openssl enc -d -aes-256-ctr -pbkdf2 -in archivo.txt.aes -out archivo.txt.plain
enter aes-256-ctr decryption password:

El proceso completo…

Una imagen vale mas que mil palabras… veamos el procedimiento completo en un gif:

openssl encriptado desencriptado

Autenticidad e Integridad con OpenSSL

Ya hemos podido verificar confidencialidad, y como hemos visto en una entrada anterior, la integridad y la autenticidad pueden lograrse mediante el uso de HMAC en criptografía simétrica.

Para calcular el HMAC necesitamos dos cosas:

  • Una función hash (o digest para OpenSSL).
  • Una clave de autenticación.

Recordemos qué es un HMAC: una función hash que se calcula sobre un dato combinado con una clave de autenticación. Es decir, tomamos el dato original, lo combinamos con una clave de autenticación, calculamos la firma HMAC (hash de esa combinación), y podemos enviarle al destinatario el dato original, junto con el HMAC generado.

Un intermediario que no posea la clave de autenticación no podrá calcular el hash.

Veamos un ejemplo. Supongamos que queremos calcular el HMAC basado en SHA-256 sobre el archivo de texto utilizado en el paso anterior. Podríamos hacerlo de esta manera:

$ openssl dgst -sha256 -hmac "1234" archivo.txt

HMAC-SHA256(archivo.txt)= 1d63e79658da3fe2fb659dca6d3360f15aec8839913b42bc3066926231a76306

Hemos generado una firma HMAC del archivo original, es decir, un hash SHA-256 del archivo original combinado con nuestra supersecreta clave «1234».

En acción:

Una pequeña aclaración respecto de OpenSSL: hemos utilizado el modificador -hmac para calcular un hash firmado, donde se incorporó una clave de autenticación. Si omitimos dicha opción, estaremos calculando el hash clásico, sin mecanismo de autenticación, ya que no se utilizará ninguna clave.

Ahora podríamos enviar al destinatario el archivo.txt, y el HMAC generado. El destinatario podrá realizar el mismo procedimiento de cálculo del HMAC, y comparar la salida con el que enviamos.

Si un atacante intercepta el mensaje, no podrá modificarlo ya que, al no poseer la clave de autenticación, no podrá regenerar un HMAC válido para que el destinatario verifique.

Así, si el destinatario calcula el HMAC y da correcto, implica que:

  • El mensaje no fue modificado, verifica integridad.
  • El HMAC únicamente pudo haber sido generado por el origen, que dispone de la clave de autenticación, por lo que también verifica autenticidad.
  • Además, como se supone que el origen es el único que tiene la clave de autenticación, no puede desentenderse de haber firmado el mensaje, así que verifica no repudio también.

Autenticidad, integridad y confidencialidad

Podrían lograrse las tres características combinando las dos herramientas vistas. Por ejemplo, podríamos calcular el HMAC de un archivo, guardarlo en un nuevo archivo, y luego encriptar ambos, el archivo original, y el archivo de hmac, mediante algoritmos simétricos.

El destinatario en este caso deberá desencriptar ambos archivos usando la clave de cifrado simétrico, luego calcular el hmac sobre el archivo de texto, utilizando la clave de autenticación, y comparar con la que venía adjunta.

También podría haberse encriptado el archivo de texto, y luego generar el hmac sobre el texto cifrado. Entonces, enviar el archivo cifrado, y el hmac sin cifrar, o cifrado a su vez.

Manuel José Lucena López realizó algunos diagramas muy explicativos sobre este procedimiento en su libro «Criptografía y Seguridad en Computadores«. Aquí se muestran las formas en las que se puede combinar el cifrado simétrico y el cálculo de funciones HMAC para obtener un cifrado autenticado.

a) Encriptar y luego calcular el MAC. b) Encriptar y calcular el MAC en paralelo. c) Calcular el MAC y luego encriptar.

Conclusiones

Hemos analizado, en principio, la manera de lograr confidencialidad encriptando, y luego la forma de verificar autenticidad e integridad mediante el uso de HMAC, particularmente en OpenSSL.

Iba a incluir GnuPG en este post, pero se iba a hacer demasiado extenso… GPG requiere su propio artículo, que vendrá pronto 🙂

Luego, en esta serie de artículos, hablaremos sobre criptografía asimétrica de manera conceptual, y cómo lograr la confidencialidad, autenticidad e integridad utilizando cifrado asimétrico.

Espero que les resulte interesante! Cualquier duda o consulta ya saben dónde encontrarme.

Hasta la próxima!


¿Preguntas? ¿Comentarios?

Si tenés dudas, o querés dejarnos tus comentarios y consultas, sumate al grupo de Telegram de la comunidad JuncoTIC!
¡Te esperamos!


Diego Córdoba

- Ingeniero en Informática - Mg. Teleinformática - Tesis pendiente - Docente universitario - Investigador