CommandsStyle

miércoles, 7 de junio de 2017

Almacenar las contraseñas para hacer un sistema de login

Hola de nuevo.
Hoy vamos a tratar una de las preguntas con que nos encontramos cuando vamos a hacer una aplicación y queremos hacer un sistema de login.



¿Como almaceno la contraseña?

Para responder a esta pregunta, primero vamos a imaginarnos que para almacenar la información de login del usuario vamos a utilizar una tabla como la que vemos a continuación:

Tabla users
id username password

En la columna id almacenaremos los identificadores de usuarios (Esta columna en realidad no la vamos a usar, pero me gusta ponerla ;)).
En la columna username almacenaremos el nombre de los usuarios.
En la columna password... bueno ahora veremos lo que tenemos que almacenar aquí.

Opción 1
La primera de las opciones que se nos puede ocurrir es almacenar la contraseña en texto plano.
De esta forma cuando un usuario intente hacer login en nuestra aplicación, nosotros vamos a buscar el registro por username y luego, compararemos que la contraseña que nos ha dado el usuario es igual a la contraseña que tenemos almacenada en nuestra tabla.

Por ejemplo:
Supongamos que tenemos la siguiente tabla users:

Tabla users
id username password
1 Juan ContraseñaDeJuan
2 Manuel ContraseñaDeManuel

Si el usuario con username Juan intenta hacer login con la contraseña MiContraseña.

  • Primero recuperamos el registro correspondiente al username Juan.
  • Segundo, compararemos que la contraseña Micontraseña es igual a ContraseñaDeJuan.

Como Micontraseña y ContraseñaDeJuan no son la misma cadena de caracteres (¿Es evidente no? :P) rechazamos el login del usuario.

Si el usuario por el contrario introduce el username Juan y la contraseña ContraseñaDeJuan daríamos el login por bueno y dejaríamos acceder al usuario a nuestra aplicación.

Bien, hemos visto cómo funciona este sistema, pero tiene un problema.
Si por alguna razón (Dios no lo quiera), nos roban los datos de la tabla users, el atacante podrá acceder a la aplicación como un usuario normal ya que conocerá los username y las contraseñas asociadas a esos usernames.

Esto claramente es un problema. (Bueno, son 2 problemas, uno que puede acceder como un usuario normal y otro que te han robado los datos de la base de datos ;) )
Visto este problema, vamos a darle una primera solución.

Opción 2
Vamos a hacer un hash de la contraseña y vamos a almacenarlo en la base de datos.

Una función hash es una función que, dada una entrada, nos ofrece una salida (normalmente de longitud fija) y lo interesante de las funciones hash, es que se puede calcular el hash de un valor, pero no se puede hacer la operación inversa, es decir, teniendo un hash, no puedes calcular qué valor a producido ese hash.

Hay varias funciones hash, como por ejemplo MD5, SHA-1, SHA-2...

Sabiendo esto, nuestro ejemplo de login quedaría de la siguiente manera:

Tabla users
id username password
1 Juan 63819e896e0eb74eea21027dd691f98edfa080e23677955038bcd57582881fcb
2 Manuel 86e11146f8f7a2e213dcf600976aa9e00b125d81be91c22fd193019f62cc36c4

Supongamos que Juan intenta hacer login en nuestra aplicación con la contraseña MiContraseña:

  • Primero, recuperamos el registro correspondiente al username Juan.
  • Segundo, hacemos el hash de la contraseña introducida por el usuario en este caso MiContraseña.
  • Tercero, comparamos el hash recuperado de la base de datos con el hash que hemos calculado en el paso anterior.


Si el usuario introduce el username Juan y la contraseña MiContraseña (El hash SHA-2 es 1ef1870f76d31f4b5e877996faf904fd5214135c9967100ff3eda9f568b4d751), compararemos el hash SHA-2 de MiContraseña, con el campo password del registro correspondiente al username Juan.

Como ambos hashes no son iguales, rechazamos el login del usuario.

Si por el contrario, introduce el valor ContraseñaDeJuan como contraseña, cuando calculemos el hash SHA-2 de la contraseña veremos que es igual al que tenemos almacenado en el registro para el username Juan y daremos el login por bueno.

Pero este sistema sigue teniendo un pequeño problema.
Imaginemos de nuevo que nos roban los datos de la tabla. Un atacante podría calcular los hashes SHA-2 de todas las cadenas (o de las contraseñas más comunes) almacenando el par palabra-hash(palabra).

Con lo que únicamente tendría que buscar en esa tabla de hashes pre-calculados el hash correspondiente al que tenemos almacenados en nuestra tabla, para ver que palabra genera ese hash.

Luego podría utilizar el username y esa palabra para hacer login como un usuario normal de nuestra aplicación.

A esas tablas de hashes, se las suele llamar Rainbow Tables.

Llegados a este punto, tenemos que darle una solución a este nuevo problema.

Opción 3
Esta solución pasa por la utilización de un salt.
En nuestro caso, el salt, va a ser una cadena aleatoria que utilizaremos junto con la contraseña para generar el hash que vamos a almacenar en la base de datos.

En este caso, por ejemplo, cuando vallamos a generar el hash de la contraseña, vamos a concatenar la contraseña con el salt (vamos a utilizar la concatenación, pero podríamos realizar cual otra operación que quisiéramos).

También tendremos que crear una nueva fila para almacenar el salt, con lo que la tabla users nos quedara como vemos a continuación.
Tabla users
id username salt password
1 Juan idav fe2951935dbd28541b561ba5be8d06c4378f46b8a05dabe0e95c5fb5a515ee72
2 Manuel iwns e2c5a2a35873aaa35a1a5b66d5ed8f82a483fa31f307772e06eae0aa4ff9d413


Supongamos ahora que Juan intenta hacer login en nuestra aplicación con la contraseña MiContraseña:


  • Primero, recuperamos el registro correspondiente al username Juan.
  • Segundo, concatenamos el salt correspondiente a Juan con la contraseña introducida. El resultado es MiContraseñaidav.
  • Tercero, calculamos el hash SHA-2 de MiContraseñaidav que es 37884538e6ea5efc921f602bb5793810b8106a6a290e91ee80a28d98c608476c
  • Cuarto, comprobamos que el hash que hemos calculado, es el mismo que tenemos almacenado en la base de datos.

  • Como podemos ver, el hash no coincide, por lo que rechazamos el login.
    Por el contrario, si la contraseña introducida hubiese sido ContraseñaDeJuan, al concatenarle el salt
    el resultado habría sido ContraseñaDeJuanidav.
    Como el hash SHA-2 de ContraseñaDeJuanidav es fe2951935dbd28541b561ba5be8d06c4378f46b8a05dabe0e95c5fb5a515ee72 el usuario habría echo login correctamente.

    Pero ¿Que ventaja me proporciona el uso del salt?
    Supongamos que nos roban de nuevo los datos de la tabla.

    Vamos a suponer que el atacante tiene una rainbow table en la que tiene unos hashes que hashes coinciden con algunos que tenemos guardados en la base de datos.

    Supongamos que una de las palabras de la rainbow table es  PATATA da como resultado el hash asociado a Juan, fe2951935dbd28541b561ba5be8d06c4378f46b8a05dabe0e95c5fb5a515ee72.

    El atacante introducirá PATATA como contraseña, pero nosotros a PATATA le vamos a concatenar idav, que es el salt correspondiente a Juan. con lo cual el hash SHA-2 correspondiente a PATATAidav no nos dará como resultado el mismo hash que PATATA y por lo tanto el atacante no podrá hacer login.

    En este punto, podríamos pensar, bueno, como el atacante ya tiene el salt, podría hacerse una rainbow table concatenando todas las palabras y calculando sus hashes.
    Esto es cierto, pero hacer esto, lleva una cantidad de tiempo elevada (podría utilizar fuerza bruta o un ataque de diccionario).
    Además, esto únicamente le valdría para calcular la contraseña de un usuario, ya que para cada usuario, el salt es diferente y por tanto, puede que en un hipotético caso se averigüe la contraseña de un usuario, pero no la del resto (a no ser que repita este proceso para cada uno de ellos).


    Espero que después de esta parrafada haya quedado claro cuál es el mejor de los 3 métodos para hacer un sistema de login. Si tenéis alguna duda, podéis plantearla en los comentarios para intentar resolverla.

    Hasta la siguiente!

    No hay comentarios:

    Publicar un comentario