El cifrado sin control, no sirve de nada

 

Últimamente, tanto en auditorías que realizamos, como en código que encuentro en la red, hay una cosa que me llama poderosamente la atención, y es el mal uso (o uso incompleto) de las funciones de criptografía. Por lo general, el procedimiento que suelen seguir los desarrolladores que no están en contacto con el mundo de la seguridad, suelen consistir en coger un método que venga con la API del lenguaje o en una librería de terceros, buscar el  método que  implementa el algoritmo de cifrado de turno, rellenar la firma del método y almacenar la salida.

Sin embargo, el uso correcto del cifrado, como todo lo que es crítico en esta vida, no es algo que se deba hacer a las bravas: además del algoritmo de cifrado, es necesaria la utilización de más algoritmos para la gestión de la autenticidad del mensaje y de la clave.

Pongamos que por ejemplo, Bob desea enviarle un mensaje a Alice mediante un programa que realiza un cifrado de clave: Bob introduce la clave y los datos que quiere cifrar, y el programa se encarga del resto. Pero, exactamente, ¿qué debería hacer el programa?

En primer lugar, el programa debe  obtener algunos bytes aleatorios (o lo más aleatorios posible), para generar un salt que se utilizará junto a la contraseña, con el fin de dificultar ataques basados en tablas Rainbow.

A continuación, generará la clave de cifrado mediante un algoritmo de derivación de claves, como PBKDF2. Para ello utiliza la contraseña proporcionada por Bob, el salt generado antes, el número de iteraciones que debe realizar el algoritmo, la longitud que debe tener la clave derivada y una función pseudo-aleatoria, como es HMAC, para obtener la clave que se utilizará en el cifrado. En el caso de utilizar AES-256, la longitud necesaria de la clave será de 32 bytes (256 bits). Sin embargo, el programa generará una clave mayor,  de 64 bytes, que se cortará en 2 claves de 32 bytes. De éste modo, se habrá obtenido la clave que se utilizará para cifrar los datos, y la clave que se utilizará en HMAC, tal y como detallamos a continuación.

HMAC es un algoritmo que permite verificar la integridad de los datos y la autenticidad de un mensaje. Para asegurarnos de que todos los datos no han sufrido cambios, “hmacquearemos” los datos utilizados hasta ahora: el número de iteraciones de PBKDF2, la longitud de la clave de PBKDF2, la longitud del salt, el salt, la longitud del nonce (ya llegaremos a él) y el nonce. De estos datos, junto a la segunda clave derivada antes con PBKDF2, y un algoritmo de hash (SHA-512, por ejemplo), obtendremos los datos que nos servirán para comprobar la integridad y autenticidad de los datos. Pero, espera ¿no deberíamos también añadir el mensaje cifrado a la función HMAC? Sí, pero como todavía no lo tenemos, no lo podemos añadir, por lo que a medida que vayamos cifrando los datos deberemos actualizar el valor del resultado de HMAC.

Con todo esto, ya podemos empezar a cifrar los datos. Para ello, podemos utilizar AES-256-CTR, ya que no requiere padding (AES-CBC requiere que la longitud de los datos sean múltiplos de 16, por lo que se ha de añadir morralla en caso de que no sea así), aunque requiere de un contador, que utiliza un nonce (secuencia de datos pseudo-aleatorios) y un contador de bloques inicial) en vez de un IV (Initialization Vector).

Para que los datos se puedan descifrar, el resultado del cifrado será un paquete de datos (al estilo de un paquete de red) que deberá contener: el número de iteraciones realizadas por PBKDF2, el tamaño de la clave derivada, la longitud del salt, la longitud del nonce, la longitud del texto cifrado, el resultado de HMAC, el salt, el nonce y el texto cifrado.

Para descifrar los datos, el algoritmo es similar: se deriva la contraseña proporcionada, y se genera el HMAC  de todos los datos con los que hemos hecho el HMAC anteriormente, y que vienen incluidos en el paquete de datos cifrado. Una vez finalizada la generación de HMAC,  podemos comprobar que la longitud del texto cifrado es la misma que la indicada en el paquete, y que el resultado de ambos HMAC tienen el mismo valor. En el caso de que la verificación falle, puede darse por varios motivos: la contraseña es incorrecta, los datos han sido modificados, etc., pero en ningún caso se deberá seguir con el descifrado.

Una vez que todo ha sido verificado correctamente, con la clave de cifrado derivada, podemos, por fin, descifrar los datos.

Como nota, decir que existen unas librerías de Google, Keyczar (disponible para Java, Python y C++), cuyo propósito es evitar al desarrollador pasar por todo este infierno, con la seguridad de que está utilizando la criptografía correctamente.