domingo, 20 de abril de 2008

Hashing en PHP

Una de las técnicas más básicas y simples relativas a la seguridad de información es el hashing o codificación de datos sensibles (ej.: contraseñas).

Los algoritmos de hashing permiten generar un hash (o "huella") a partir de un string y tienen la característica principal de que no es posible obtener el string original a partir del hash generado.

La longitud del hash es fija para cada algoritmo por lo que no importa si la entrada tiene una longitud de 1 byte o de varios gigas, el hash tendá siempre la misma longitud. Una de las características básicas de todo hash es que el menor cambio en el string de entrada implica un gran cambio en el hash generado.

Los algoritmos de hash más populares son: MD5, SHA-1 y SHA-2. En la siguiente tabla muestro la longitud de algunos hash y un ejemplo al cifrar el texto "123":

AlgoritmoBitsEjemplo
CRC3232-2008521774 (representado como int32)
MD4128c58cda49f00748a3bc0fcfa511d516cb
MD5128202cb962ac59075b964b07152d234b70
SHA-119240bd001563085fc35165329ea1ff5c5ecbdbbeef
SHA-256 (SHA-2)256a665a45920422f9d417e4867efdc4fb8 a04a1f3fff1fa07e998e86f7f7a27ae3
SHA-512 (SHA-2)5123c9909afec25354d551dae21590bb26e 38d53f2173b8d3dc3eee4c047e7ab1c1 eb8b85103e3be7ba613b31bb5c9c3621 4dc9f14a42fd7a2fdb84856bca5c44c2
RIPEMD-160160e3431a8e0adbf96fd140103dc6f63a3f8fa343ab
HAVAL-256256e3891cb6fd1a883a1ae723f13ba336f5 86fa8c10506c4799c209d10113675bc1
TIGER-128128fe14a796bb0768a83398e6935842229b
TIGER-160160fe14a796bb0768a83398e6935842229bbef2eeb0

Como información adicional puedes ver el siguiente sitio que permite generar hashes para múltiples algotirmos.

Debido a que la longitud del hash es de tamaño fijo, existe la posibilidad de que ocurran colisiones, las cuales son extremadamente improblables pero no imposibles, por ejemplo en MD5 teóricamente se pueden generar 3.4 × 1038 (2128) valores diferentes de hash y en SHA-256 son casi 1.2 × 1077 (2256) valores diferentes.


Aplicaciones

Entre las aplicaciones más comunes de hash se pueden contar las siguientes:
  • Almacenamiento de contraseñas.
  • Generación de tokens (ej: para sesión de usuarios, cookies).
  • Implementación de arreglos asociativos.
  • Checksums (ej: al transmitir información como un torrent, un instalador o un archivo .ISO).
  • Digest para firma electrónica (ej: xmlsignature).
A continuación me referiré al almacenamiento de contraseñas.



Almacenamiento de contraseñas mediante HASH
Al guardar las contraseñas codificadas mediante un hash, se imposibilita conocer la contraseña original tanto para un eventual atacante como también para un administrador.

Otro alcance de esta técnica es para que -por políticas de seguridad, cumplimiento de estándares de certificación o por cumplimiento de los términos de uso- la organización no maneje las contraseñas originales de sus usuarios.

El mecanismo más común para autenticar usuarios es por medio de un login y password, lo que implica que en la base de datos exista una tabla que llamaremos usuarios que contiene al menos las columnas usua_login y usua_password, dando como resultado la siguiente tabla de usuarios:

usua_loginusua_password
jessicap7q,f2
pedrobonsai
rodrigoro123

Como podemos darnos cuenta, cualquiera que tenga acceso a la base de datos podrá conocer la contraseña de los usuarios. Existen muchas técnicas para lograr tener acceso a la base de datos, como por ejemplo errores documentados de software, SQL injection, errores en páginas de inicio de sesión, programas como PhpMyAdmin que no estén lo suficientemente resguardados, usuarios al interior de la organización o ingeniería social.

Por lo tanto el primer nivel de seguridad consiste en guardar las contraseñan cifradas con algún algoritmo de hashing. En la actualidad el algoritmo más popular es MD5, sin embargo desde hace algunos años se han reportado ataques satisfactorios a este algoritmo. Por lo tanto recomiendo utilizar algoritmos de mayor dureza como SHA-1 o SHA-256 los cuales ya están disponibles en muchas herramientas desarrollo. A continuación presento una tabla de las funciones en PHP para codificar en algunos de los algoritmos de hashing:

MD5md5($txt)
SHA-1sha1($txt)
sha-256mhash(MHASH_SHA256, $txt)
tiger-128mhash(MHASH_TIGER128, $txt)
haval-256mhash(MHASH_HAVAL256, $txt)
La función hash() requiere tener PHP con el módulo mhash.

En estos ejemplos utilizaré SHA-1, por lo que la tabla usuarios quedará con los siguientes datos:

usua_loginusua_password
jessica1f9004142372e078dbda694238d82a192760c170
pedro9562384bbab4d406ee06012638ca65f403d1bd1f
rodrigo0f8342a2ed2797cb3d19b926d5c98db79b5a8708

Al guardar los datos codificados se debe considerar que las rutinas de inicio de sesión y de gestión de usuarios deben ser intermediadas por un proceso que convierta la contraseña original en la contraseña codificada, como por ejemplo el siguiente código:
$password = "bonsai";
$hash = sha1($password);
A este nivel hemos logrado que los password se guarden cifrados en la base de datos y sea (teóricamente) imposible conocer los passwords originales. Sin embargo, un atacante que logre acceder a la base de datos aún tendrá posibilidad de conocer algunas de las contraseñas.

Si buscamos en google por la contraseña codificada de Pedro encontraremos inmediatamente que dicho hash corresponde al texto "bonasi". La contraseña de Pedro (bonsai) es conocida como una palabra de diccionario, es decir una palabra común. En internet existen muchos sitios donde tienen indexados millones de palabras con su correspondiente hash -principalmente MD5- por lo que mediante ataques por diccionario es posible obtener las contraseñas de los usuarios más descuidados.

Jessica y en menor medida Rodrigo utilizaron contraseñas que no son palabras de diccionario ("p7q,f2" y "ro123" respectivamente) por lo que es más dificil descubrir sus contraseñas originales.

Lamentablemente, muchos desarrolladores llegan sólo hasta este nivel y descuidan agregar un mayor nivel de seguridad el cual se puede realizar con un cambio muy simple que describo a continuación.


Almacenamiento de contraseñas mediante HMAC
Un algoritmo HMAC no es más que un algoritmo HASH al cual -antes de codificar el texto original- se le añade un texto adicional (key o clave secreta).

En un principio realizabamos la codificacion de la contraseña mediante el siguiente código:
$password = "bonsai";
$hash = sha1($password);
En este ejemplo vamos a escoger la clave secreta "xy45" y vamos a codificar las contraseñas utilizando la siguiente variación:
$password = "bonsai";
$key = "xy45";
$hash = sha1($key.$password);
Es importante que la clave secreta sea lo suficientemente larga y compleja. Recomiendo que la longitud sea de 8 caracteres y se utilicen combinaciones de mayusculas, minusculas, números y caracteres de puntuación. Opcionalmente, en vez de una simple concatenación, PHP provee la función hash_hmac() especialmente diseñada para estos efectos.

Gracias a esta técnica obtendremos la siguiente tabla de usuarios:

usua_loginusua_password
jessica25062347f18d9e8a85849aade94b38beaea54653
pedro742cc2861f5c611452bf7e78c8bcbc3899851a56
rodrigo44f789c9642a64513e8682a7dea072dedae85cd9

Estas contraseñas ahora son invulnerables a un ataque directo de diccionario y por lo tanto brindan un mayor nivel de seguridad. Sin embargo, aún es posible obtener las contraseñas originales por medio de un ataque de colisión.

Si el atacante conoce la clave secreta HMAC, le bastaría con crear un pequeño programa que genere las contraseñas en HMAC a partir de un diccionario de palabras para luego compararlas con las contraseñas almacendas en la base de datos. Si no conoce la clave secreta HMAC, basta con cambiar -por medio de la aplicación- varias veces la contraseña de algún usuario del sistema y luego comparar el string HMAC generado con el del resto de usuarios.

Almacenamiento de contraseñas mediante HMAC con distinta clave secreta
La solución a este tipo de ataque consiste en que la contraseña de cada usuario se codifique con una clave secreta HMAC diferente, como por ejemplo el username, la casilla de correos del usuario o agún otro dato variable. Nuestra nueva función de codificación quedaría de la siguiente manera:
$username = "pedro";
$password = "bonsai";
$key = "xy45";
$hash = sha1($key.$username.$password);
En este nuevo escenario aún es posible que un atacante obtenga algún password por colisión pero tendrá que generar un diccionario de claves usuario por usuario.

Otra ventaja -o desventaja según cómo se le mire- es que si se cambia el login del usuario, el password se invalidará. Por lo que en la eventualidad de cambiar el login, se deberá volver a generar el password.



Medidas adicionales de seguridad
Se recomienda mantener oculta la clave secreta HMAC e implementar mecanismos para limitar el registro masivo de usuarios y cambios masivos de contraseña.

También se pueden implementar -a nivel de software- políticas de seguridad a la gestión de contraseñas para no permitir que los usuarios utilicen palabras de diccionario o contraseñas demasiado cortas.

No hay comentarios: