Criptografía y seguridad

1
8092

Hoy vamos a empezar con este tutorial una serie de artículos sobre conceptos básicos de seguridad y criptografía. La idea es conocer los conceptos que son necesarios para comprender términos más complejos que son las piezas sobre las que se asienta la seguridad informática hoy en día. En muchos casos, lo que ocurre es que es el desconocimiento de la base lo que nos impide la comprensión de conceptos más complejos. Por ejemplo, el desconocimiento de cómo funciona la criptografía de clave pública nos impide comprender la firma digital, lo que a su vez nos impide comprender de qué y cómo nos protege el protocolo https de algunas amenazas comunes a nuestra privacidad.

Para eso voy a daros la tabarra con varios conceptos teóricos. Luego intentaremos hacer algunos ejemplos para aplicar lo que hemos aprendido.

Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core I7, 16GB DDR3)
  • Sistema Operativo: Mac OS El Capitan 10.11.6
  • Entorno de desarrollo: IntelliJ IDEA 2016.2
  • Librerías:
    • Bouncy Castle – bcprov-jdk15on-1.55.jar

1. ¿Qué es la criptografía?

La criptografía es la técnica de construir y analizar protocolos que permiten que terceras personas sean capaces de leer mensajes que se desea que permanezcan privados. Hoy en día la criptografía se ocupa de varios aspectos de la seguridad de la información como pueden ser:

  • Confidencialidad: Sabemos que la información únicamente es accesible por aquellos a los que va dirigida.
  • Integridad de los datos: Sabemos que la información es correcta y no ha sido modificada por terceros.
  • Autenticidad: El remitente de la información tiene una identidad que puede ser verificada.
  • Vinculación: Sabemos que el creador de la información es el remitente no siendo posible que haya sido un tercero.

Para lograr esos objetivos se utilizan una serie de técnicas que vamos a ir describiendo a lo largo de estos tutoriales.

2. Conceptos básicos

Hay unos cuantos conceptos básicos que tenemos que conocer antes de entrar en materia.

  • Cifrado: Es el proceso de codificar la información de tal forma que sólo pueda ser leída por terceros autorizados utilizando una clave de cifrado. Esos terceros usan el proceso contrario (descifrado) junto con la clave para acceder a los contenidos del mismo.
  • Texto plano: Son los datos antes de ser cifrados. Aunque se le llame «texto» pueden ser también datos binarios como imágenes, ejecutables, etc.
  • Texto cifrado: Es el texto plano después de pasar por el proceso de cifrado.
  • Clave de cifrado: Es una secuencia de caracteres que se utiliza para realizar el cifrado de los datos y sin la cual no es posible descifrarlos. Idealmente la clave debe permanecer secreta y ser conocida sólo por los comunicantes, aunque ya veremos que el proceso es diferente en la criptografía de clave pública.
  • Algoritmo de cifrado: Son el par de algoritmos capaces de convertir el texto plano en texto cifrado y viceversa utilizando una o varias claves de cifrado.

Es sumamente importante que se nos meta en la cabeza lo siguiente: La seguridad de un algoritmo de cifrado reside en su clave. Cuando nuestro sistema requiere utilizar datos cifrados siempre vamos a utilizar un algoritmo de cifrado público y bien conocido. Esto permite que el algoritmo en sí haya sido sometido a un análisis a fondo por parte de especialistas que tienen un nivel de conocimiento profundo de las técnicas de criptoanálisis. Nuestro sistema va a ser seguro mientras la clave se mantenga privada y sólo sea conocida por quien debe serlo. El caso contrario (la llamada «seguridad por oscuridad») no está recomendada para la mayoría de los casos y siempre deberíamos actuar como si un eventual atacante conociera perfectamente el algoritmo que se ha utilizado en el cifrado.

Los algoritmos de cifrado presentan obsolescencia. Según pasa el tiempo la potencia del hardware disponible aumenta, se descubren nuevas técnicas de criptoanálisis y se descubren vulnerabilidades en algoritmos existentes. Esto hace que sea necesario estar al día por si es necesario cambiar de algoritmos o usar versiones de los mismos con una mayor longitud de clave.

Una vez tenemos claros estos conceptos básicos podemos entrar en harina y comenzar a hablar de los dos grandes grupos de algoritmos de cifrado: los algoritmos de cifrado simétrico y los de cifrado asimétrico.

3. Cifrado simétrico y cifrado asimétrico

Los algoritmos de cifrado simétrico son aquellos en los que se usa la misma clave para el cifrado y para el descifrado. Comparados con los algoritmos de cifrado asimétrico son de ejecución rápida y con claves bastante más pequeñas. No obstante tienen un gran problema: Es necesario que ambas partes de la comunicación compartan la misma clave.

En los algoritmos de cifrado asimétrico existe un par de claves para llevar a cabo el proceso: Una clave pública que se distribuye a todo aquel que la necesite y una clave privada que ha de ser conocida sólo por el poseedor del par de claves. Estos algoritmos permiten que cualquier tercero cifre un mensaje con la clave pública que sólo podrá ser descifrado con la clave privada. Este tipo de cifrado es mucho más lento que el cifrado simétrico.

En este tutorial vamos a centrarnos en el cifrado simétrico. Dejaremos el cifrado asimétrico para próximos tutoriales.

3.1. Tipos de algoritmos de cifrado simétrico

Existen dos grandes grupos de algoritmos de cifrado simétrico:

  • Algoritmos de cifrado por bloques: Aplican su transformación sobre bloques de datos de longitud fija.
  • Algoritmos de cifrado de flujo: Combinan el texto plano con un flujo de clave (keystream) que se genera con ayuda de la clave de cifrado y que se aplica sobre el texto plano dígito a dígito.

Algunos algoritmos de cifrado de bloques muy utilizados son AES, Blowfish, Twofish, DES o TripleDES. DES está considerado como inseguro y el autor de Blowfish recomienda utilizar Twofish. La opción más utilizada a día de hoy es utilizar AES con longitudes de clave de 128, 192 y 256 bits.

En cuanto a los algoritmos de cifrado de flujo se suelen utilizar los algoritmos de cifrado de bloques en los modos de operación OFB, CFB y CTR, que transforman el cifrador efectivamente en un cifrador de flujo. El algoritmo RC4, aunque muy usado hasta hace poco a día de hoy no está considerado como seguro y va a ser eliminado de la próxima especificación de TLS. La alternativa actual como algoritmo de flujo está representada por la suite eSTREAM.

3.2. Modos de operación

Un algoritmo de cifrado de bloques aplica la transformación de texto claro a texto cifrado sobre bloques de datos de longitud fija. Algoritmos como DES, TripleDES o Blowfish utilizan un tamaño de bloque de 64 bits. Alternativas más modernas como AES o Twofish utilizan uno de 128 bits

Un cifrado por bloques sólo permite cifrar texto de una longitud igual a la del bloque que tiene definido. Para cifrar textos más largos el mensaje se divide en bloques de esa misma longitud añadiendo posiblemente un padding o relleno para completar en el último bloque con los bits faltantes hasta llegar al tamaño múltiplo del tamaño de bloque.

Los bloques generalmente no se concatenan uno detrás de otro tal cual sino que mezclan utilizando lo que llamamos modo de operación. Esta característica aplica únicamente a los algoritmos de cifrado por bloques y es sumamente importante para mantener la seguridad de la clave. En los modos de operación generalmente se utiliza un vector de inicialización para dotar de cierta aleatoriedad al resultado.

El vector de inicialización se considera un dato público y nunca debería ser a su vez cifrado. Además ha de ser aleatorio y debe generarse uno nuevo en cada aplicación del cifrado. Generalmente tiene el mismo tamaño que el tamaño de bloque del algoritmo de cifrado utilizado.

Existen diversos modos de operación sobre los que no entraremos demasiado en detalle

Los modos ECB (Electronic CodeBook) y CBC (Cipher Block Chaining) operan estrictamente sobre bloques. Si nuestro texto claro no tiene una longitud que sea un múltiplo exacto del tamaño del bloque se añadirá al último bloque un relleno o padding para llegar a esa longitud.

Los modos CFB (Cipher FeedBack), OFB (Output Feedback) y CTR (CounTeR) utilizan el algoritmo de cifrado para generar lo que llamamos un flujo de clave o keystream, que se mezclará con el texto plano para ir produciendo el texto cifrado. Esto convierte el cifrado por bloques esencialmente en un cifrado de flujo por lo que se elimina la necesidad de padding.

Generalmente se recomienda utilizar los modos CBC o CTR. El modo ECB no debería utilizarse en ninguna aplicación criptográfica.

3.3. Modos de operación autenticados

Los modos de operación normales presentan un gran problema: un potencial atacante puede interceptar textos cifrados enviados al receptor, modificarlos levemente y volverlos a enviar para observar los resultados. Esto se conoce como ataque por texto cifrado escogido (chosen ciphertext attack).

Para evitar esto se utilizan lo que se conoce como modos de operación autenticados. En ellos se protege el texto cifrado con algún tipo de algoritmo de resumen tipo HMAC. Los modos de operación autenticados se encargan de este proceso de forma transparente al usuario. Algunos de ellos son:

  • GCM (Galois Counter Mode): Libre de patentes, rápido y además soportado por TLS. (link)
  • OCB (Offset Codebook Mode): El más rápido de todos. Con patentes para uso comercial. (link)
  • EAX: Libre, fácil de implementar y ligero. Eso si, es lento en comparación con el resto. (link)
  • CCM (Counter mode with CBC MAC): Implementado en el estándar WPA2, se considera la alternativa libre de patentes a OCB aunque es mucho más lento. (link)

La recomendación aquí sería utilizar los modos GCM (libre) y, si nuestro desarrollo lo permite, OCB.

4. Implementando todo en Java. Librerías Bouncy Castle

Java viene con un conjunto básico de proveedores criptográficos «out-of-the-box». Sin embargo si queremos tener una mayor variedad de algoritmos y operaciones nos vemos obligados a utilizar librerías de terceros. De lejos lo más utilizado en aplicaciones Java son las librerías de Bouncy Castle, que además de ser libres pertenecen a una organización sin ánimo de lucro australiana, lo que nos evitará tener que bajarnos los archivos que permiten el uso de cifrado fuerte que aplican sobre los productos estadounidenses.

Las librerías Bouncy Castle cuentan con un provider que se integra con la JCA de Java por si queremos utilizarlas de esta manera y además cuentan con un API ligero que es el que vamos a utilizar en estos ejemplos.

Para descargarse las librerías podemos hacerlo directamente desde la página del proyecto o añadir la dependencia de maven a nuestro código:

5. Ejemplo: Cifrado en modo CBC

Vamos a implementar una función que cifra un texto plano en modo CBC utilizando una clave y un vector de inicialización aleatorio. Comenzaremos utilizando un algoritmo TripleDES con una longitud de clave de 192 bits y un vector de inicialización de 64. Más adelante comentaremos los puntos interesantes del código. Al final del tutorial hay un enlace a GitHub con los fuentes que se han utilizado.

  • En un primer momento se inicializa el cifrador. La librería implementa funcionalidades como los modos de operación y el padding mediante un patrón decorador. En este caso estamos construyendo un cifrado mediante el algoritmo TripleDES (DESedeEngine), lo ponemos en modo de operación CBC (CBCBlockCipher) y le añadimos padding y buffer (PaddedBufferedBlockCipher).
  • Más adelante generamos una clave y un vector de inicialización aleatorios con la longitud deseada utilizando la clase SecureRandom. No se considera una buena práctica utilizar el constructor de la clase al que se proporciona un byte[] así que se hace la llamada sin parámetros. A continuación se inicializa el cifrado con esos mismos parámetros.
  • Se crea el array de salida tomando como tamaño el valor que nos devuelve el método getOutputSize, que es el tamaño máximo que va a tener la salida dada la longitud de la entrada. Como hemos seleccionado un modo de operación con padding este tamaño puede ser diferente al tamaño real de la salida más adelante.
  • Se hace el cifrado llamando a los métodos processBytes y doFinal. Ambos devuelven un entero con el número de bytes que se han devuelto. En caso de que no hemos seleccionado un modo de operación con padding esto nos ayudará a recortar el resultado a su tamaño real. La llamada a doFinal procesa el último bloque y añade el padding si es necesario.
  • El resultado final se monta concatenando el vector de inicialización (recordemos: sin cifrar) al texto cifrado

La clave y el vector de inicialización son aleatorios, por lo que la ejecución varía. En nuestro caso devuelve lo siguiente:

Nótese que la longitud del texto cifrado es múltiplo del tamaño del bloque, que es de 64 bits (8 bytes). Si le restamos los 8 bytes del vector de inicialización el texto plano ocupa un total de 80 bytes (10 bloques).

Se puede cambiar el algoritmo de cifrado y la longitud de la clave de forma muy sencilla. Por ejemplo, vamos a utilizar un cifrado AES con una longitud de clave de 256 bits (32 bytes). El tamaño del vector de inicialización será en este caso el mismo que el tamaño del bloque del cifrado AES: 128 bits (16 bytes).

Lo único que hemos hecho es cambiar el engine de cifrado por la clase AESEngine y las longitudes de clave y vector de inicialización. En este caso el resultado es:

Nótese que en este caso el resultado final también tiene una longitud que es múltiplo de 16 bytes, que es el tamaño de bloque. Para completar el ejemplo vamos a comentar el código correspondiente al descifrado. En la ejecución añadiremos este código a continuación del correspondiente al cifrado.

Cosas que comentar aquí:

  • En primer lugar es necesario separar el vector de inicialización del texto cifrado. Como viaja en claro se toman los primeros 16 bytes (correspondientes al vector de inicialización del algoritmo que estamos utilizando) y se separan del texto cifrado.
  • Se utiliza la misma clave con la que hemos cifrado para descifrar (lógico). Por supuesto esta clave ha de permanecer a buen recaudo.
  • Se inicializa el cifrador en modo descifrado (primer parámetro a false). Aquí puede instanciarse un nuevo objeto, a gusto del usuario. Para el ejemplo vamos a reutilizar el mismo objeto.
  • Del mismo modo que en el cifrado, se crea un array con el máximo tamaño posible de la salida y se llama a la operación de descifrado en dos pasos. En este caso guardamos un contador con el tamaño real del resultado.
  • Utilizamos ese contador para eliminar los bytes finales que corresponden al padding. Con el array resultante creamos un String y, voilà! Tenemos nuestro texto descifrado.

Nuestro resultado de la ejecución:

6. Ejemplo: Cifrado en modo GCM

El cifrado en modo de operación GCM (Gaulois-Counter mode) se realiza de un modo muy similar. Vamos con el código fuente resumido en el que subrayaremos las diferencias.

  • Para aplicar el modo de operación GCM se envuelve el algoritmo base (en este caso AES) con un wrapper de tipo GCMBlockCipher, que implementa la interfaz AEADBlockCipher. Es necesario mencionar que no podríamos utilizar aquí un algoritmo DES o TripleDES, ya que GCM sólo se puede usar con algoritmos cuyo tamaño de bloque sea de 128 bits (como por ejemplo AES o Twofish).
  • Se genera una clave adecuada para el algoritmo base. Como vamos a cifrar utilizando un algoritmo AES escogemos la longitud de clave más larga: 256 bits.
  • Se genera un nonce, que a todos efectos será equivalente al vector de inicialización y tendrá una longitud que para este modo de operación se recomienda que sea de 96 bits.
  • Se escoge un tamaño para la MAC que se va a ir generando. Se recomienda en este caso que sea de 128 bits, que es el tamaño máximo soportado.
  • El tamaño de la salida será igual al tamaño de la entrada + tamaño del nonce + tamaño del MAC.
  • El descifrado se realiza de la misma forma que en el cifrado anterior. En este caso no hay un padding que tengamos que eliminar.

La propiedad más interesante de estos modos de operación es que cuando se modifica cualquiera de los bytes del texto cifrado y se trata de descifrar ese texto la rutina de descifrado interpreta el texto como incorrecto y ni siquiera trata de descifrarlo. En nuestro caso si modificamos el array con los bytes del texto cifrado y tratamos de realizar el descifrado se produce la siguiente excepción:

Esto mitiga el ataque mediante texto cifrado escogido que hemos mencionado más arriba.

7. Rendimiento

Como curiosidad hemos hecho un pequeño experimento sobre el rendimiento del cifrado con diferentes algoritmos, modos y tamaños de clave. Los resultados varían en cada ejecución, pero aquí una muestra:

Algoritmo Longitud de clave Modo Cifrado (ns) Descifrado (ns)
DES 64 CBC 5036 5234
TripleDES 128 CBC 8155 7997
TripleDES 192 CBC 10657 11000
AES 128 CBC 1491 2075
AES 192 CBC 1714 2186
AES 256 CBC 1867 2335
TwoFish 128 CBC 19422 18624
TwoFish 192 CBC 27434 28786
TwoFish 256 CBC 35572 36263
AES 128 GCM 21682 20986
AES 192 GCM 20399 20706
AES 256 GCM 20492 21364
TwoFish 128 GCM 27718 28370
TwoFish 192 GCM 33073 34306
TwoFish 256 GCM 38612 37476

Es reseñable la diferencia de velocidad entre el modo de operación CBC y el modo autenticado GCM. Si el rendimiento es un factor determinante en nuestra aplicación o ejecutamos sobre un hardware muy restringido deberíamos plantearnos la posibilidad de utilizar un cifrado no autenticado, asumiendo los riesgos que derivan de esa elección.

Otro valor que llama la atención es la rapidez con la que se ejecuta el algoritmo AES en la máquina en la que se ha realizado la prueba. Esto es debido fundamentalmente a que en la actualidad muchas CPU incluyen aceleración por hardware para esta operación, que es estándar en el protocolo TLS.

8. Conclusiones

Hemos aprendido las generalidades y los términos básicos de la criptografía simétrica y, además, hemos aprendido lo que no hay que hacer cuando usamos este tipo de técnica. Por último, hemos aprendido cómo se hace todo esto utilizando una librería de gran difusión en el mundo Java, como son las Bouncy Castle.

En próximos tutoriales nos adentraremos en el mundo de la criptografía de clave pública y exploraremos algunas de sus aplicaciones como son los certificados digitales o el protocolo SSL/TLS. ¡Hasta entonces!

Referencias

1 Comentario

Dejar respuesta

Please enter your comment!
Please enter your name here