En un sitio web es normal tener que mantener datos de usuarios, a veces protegidos mediante una contraseña. A continuación muestro algunas maneras de hacerlo y sobre todo: de cómo NO se debe hacer.
Espero que esto de algunas ideas para las personas que terminan implementando un sistema de estos, y sobre todo para no caer en errores simples (o no tanto).
Al principio... era el texto plano, y fue un horror
Supondremos que básicamente tenemos una base de datos con un identificador de usuario (debe ser único) y una contraseña:
USUARIO: fulanito@example.com
CONTRASEÑA: ¡FraseSuperSecreta!
Bien, la manera trivial de implementar el sistema sería guardar estos datos tal cuales, pero existe un gran problema: si alguien obtiene acceso a nuestra base de datos ¡podrá acceder a la contraseña! Y no solamente a una, sino a todas. Y como los usuarios no suelen cambiar de contraseña será muy probable que tengan acceso a todas las cuentas del pobre fulanito@example.com. Pésima idea.
Cifrar las contraseñas (tampoco es tan seguro)
Si no podemos guardar las contraseñas como tales entonces una buena idea es cifrarlas, ¿pero cómo? Si las ciframos con una contraseña propia entonces deberemos tener dicha contraseña en texto plano en algún sitio.
Otra opción es usar una función no invertible, por ejemplo: SHA1, MD5, etc... De esta manera en lugar de los horrores anteriores tendremos algo como:
USUARIO: fulanito@example.com
CONTRASEÑA: 8cd93beb1fc767dbd98591d5582e4ba02f131e2462b53c92966270e5bf59d3a0
¿Mejor verdad? Bien, es cierto que es mejor que el texto plano, pero aún no es tan seguro como queremos, debido a las rainbow tables. Si alguien tiene una gran tabla de frases entonces puede calcular el MD5 de todas esas frases (que tendría en pares Frase-MD5) y luego hacer la búsqueda mediante el MD5... Con MD5 (o SHA o CRC o el que quieran) se puede adivinar la contraseña en O(1), suponiendo que uno tenga una tabla con las frases.
¡Cuidado con su función HASH!
Por supuesto, existen funciones más fáciles de invertir que otras. SHA y MD5, pero los CRC no lo son. Además existe otro problema y es que a veces se descubren ataques contra ciertas funciones HASH... por ejemplo MD5 ya no se considera criptograficamente seguro, pues ya existen ataques en tiempos razonables (todas las funciones HASH tienen un ataque posible: probar todos los posibles casos. En el caso de MD5 existe un ataque más rápido que ese de fuerza bruta).
En general, deberíamos considerar usar las funciones de la familia SHA (como SHA-2, etc)...
Cifre sus contraseñas, añada sal al gusto y revuelva :)
Una opción para evitar ataques que usen rainbow tables sería añadir un poco de sal a cada contraseña. De la siguiente manera:
CONTRASEÑA ORIGINAL: ¡FraseSuperSecreta!
SAL DEL SITIO: SOYSAL
Luego concatenamos ambas frases y obtenemos algo como "¡FraseSuperSecreta!SOYSAL". Luego obtenemos su HASH:
HASH: 9b2d2085531f29fe009bf94d788bbc1af85d642e2285ee53ac1c1d5dbc467bac
Utilizar la sal hará que sea necesario computar tablas rainbow para cada "sal" distinta, algo que ya no es muy deseable... Y si queremos complicar aún más la cuestión simplemente podemos volver a repetir el proceso varias veces (con o sin sal):
1: 9b2d2085531f29fe009bf94d788bbc1af85d642e2285ee53ac1c1d5dbc467bac
2: d13b72194c638088e88e06c3f0e5599bc68d632ff731d0d0910628e81dfa0fd3
3: ee241c46e83757d66e6e372f0679f654ae0cd1983db527ffd9f56d7c211d5e3f
Como se puede ver ya el proceso se volvería computacionalmente costoso, y esto es algo que aprovechan algoritmos como BCRYPT (utilizado por OpenBSD). En particular es mejor utilizar algo que sirva (como BCRYPT) el lugar de inventarnos nuestra propia receta para cocinar contraseñas.
Consideraciones finales
Es mentira que las contraseñas no deben tener vocales o d€b€h u$@r símbolos extraños. Por un lado es pésima idea usar contraseñas que vengan de un diccionario, pero mezclar varias palabras de diccionario con números y espacios ya no es algo simple de adivinar.
Así que si obliga al usuario a no usar vocales en su contraseña le impedirá tener una contraseña realmente fuerte, pues probablemente el usuario la apunte en el monitor o algo así. Es mejor que lo obliguen a utilizar más de 8 letras y al menos dos números o algo así. Por ejemplo:
- Contraseña debil: CASA (Palabra del diccionario. Además se puede adivinar en menos de 2^32 pasos)
- Otra contraseña debil: C@$@ (Se puede adivinar en menos de 2^32 pasos)
- Contraseña ¿fuerte?: e372f0679f654ae0cd1983db527ffd9f56d7c21 (FUERTE: ¿No es una frase? ¡No! ¡es porque es larga! Tiene 39 letras, se requerirían 2^312 pasos para adivinarla. DEBIL: ¿Usted se la podría aprender? Probablemente termine escribiéndola en un papel...)
- Contraseña realmente fuerte: Estaba-la-pájara-pinta-sentadita-en-su-verde-limón.-Pues-en-un-lugar-de-la-mancha-de-cuyo-nombre-no-quiero-acordarme-la-pájara-pinta-voló
La última contraseña es realmente fuerte por su largo, no por números o por símbolos extraños. Y probablemente usted ya la pudo memorizar, por lo que no necesita tenerla escrita en un papel. Por supuesto, es muy larga, en realidad la mayor parte de las personas estamos bien con contraseñas del tipo:
lacucarachavoló1313¡quesusto!
Postdata
Obviamente no utilicé ninguna de mis contraseñas en este artículo...
Por supuesto, es más fácil utilizar OpenID, Facebook o Twitter y dejar que ellos se encarguen de las contraseñas... pero por supuesto, esto no siempre es posible o deseable.