Cómo protege Devise las contraseñas de la aplicación Rails

Devise es una increíble solución de autenticación para Rails con más de 40 millones de descargas. Sin embargo, dado que abstrae la mayoría de las operaciones criptográficas, no siempre es fácil comprender lo que sucede detrás de escena.

Una de esas abstracciones culmina en la persistencia de un encrypted_passworddirectamente en la base de datos. Así que siempre he sentido curiosidad por saber qué representa realmente. He aquí un ejemplo:

$2a$11$yMMbLgN9uY6J3LhorfU9iuLAUwKxyy8w42ubeL4MWy7Fh8B.CH/yO

Pero, ¿qué significa ese galimatías?

Devise usa Bcrypt para almacenar información de forma segura. En su sitio web menciona que utiliza el “ algoritmo de hash de contraseña bcrypt () de OpenBSD, que le permite almacenar fácilmente un hash seguro de las contraseñas de sus usuarios ”. Pero, ¿qué es exactamente este hash? ¿Cómo funciona y cómo mantiene seguras las contraseñas almacenadas?

Eso es lo que quiero mostrarte hoy.

Trabajemos al revés: desde el hash almacenado en su base de datos hasta el proceso de cifrado y descifrado.

Ese hash $2a$11$yMMbLgN9uY6J3LhorfU9iuLAUwKxyy8w42ubeL4MWy7Fh8B.CH/yOse compone de varios componentes:

  • Bcrypt version ( 2a): la versión del algoritmo bcrypt () utilizado para producir este hash (almacenado después del primer $signo)
  • Costo ( 11): el factor de costo utilizado para crear el hash (almacenado después del segundo $signo)
  • Salt ( $2a$11$yMMbLgN9uY6J3LhorfU9iu): una cadena aleatoria que cuando se combina con su contraseña la hace única (primeros 29 caracteres)
  • Checksum ( LAUwKxyy8w42ubeL4MWy7Fh8B.CH/yO): la parte hash real de la encrypted_passwordcadena almacenada (cadena restante después de los 29 caracteres)

Exploremos los últimos 3 parámetros:

  • Cuando se usa Devise, el Costvalor lo establece una variable de clase llamada stretch y el valor predeterminado es 11. Especifica el número de veces que se aplica el hash a la contraseña. ( En su inicializador devise.rb, puede configurar esto a un valor más bajo para el entorno de prueba para que su conjunto de pruebas se ejecute más rápido ) . *
  • La sal es la cadena aleatoria que se utiliza para combinar con la contraseña original. Esto es lo que hace que la misma contraseña tenga valores diferentes cuando se almacena encriptada. ( Vea más abajo sobre por qué eso es importante y qué son los Rainbow Table Attack ). **
  • La suma de comprobación es el hash generado real de la contraseña después de combinarse con la sal aleatoria.

Cuando un usuario se registra en su aplicación, debe establecer una contraseña. Antes de que esta contraseña se almacene en la base de datos, se genera una sal aleatoria a través de BCrypt :: Engine.generate_salt (costo) teniendo en cuenta el factor de costo mencionado anteriormente. (Nota: si pepperse establece el valor de la variable de clase, agregará su valor a la contraseña antes de salarlo).

Con esa sal (ej. $2a$11$yMMbLgN9uY6J3LhorfU9iu, Que incluye el factor de costo) llamará a BCrypt :: Engine.hash_secret (contraseña, sal) que computa el hash final a almacenar usando la sal generada y la contraseña seleccionada por el usuario. Este hash final (por ejemplo, $2a$11$yMMbLgN9uY6J3LhorfU9iuLAUwKxyy8w42ubeL4MWy7Fh8B.CH/yO) se almacenará a su vez en la encrypted_passwordcolumna de la base de datos.

Pero si este hash no es reversible y la sal se genera aleatoriamente en la BCrypt::Password.createllamada por BCrypt::Engine.generate_salt(cost), ¿cómo se puede usar para iniciar sesión en el usuario?

Ahí es donde esos diferentes componentes hash son útiles. Después de encontrar el registro que coincide con el correo electrónico proporcionado por el usuario para iniciar sesión, la contraseña cifrada se recupera y se desglosa en los diferentes componentes mencionados anteriormente ( versión Bcrypt , Cost , Salt y Checksum ).

Después de esta preparación inicial, esto es lo que sucede a continuación:

  1. Obtener la contraseña de entrada ( 1234)
  2. Obtener la sal de la contraseña almacenada ( $2a$11$yMMbLgN9uY6J3LhorfU9iu)
  3. Genere el hash a partir de la contraseña y la sal usando la misma versión de bcrypt y factor de costo ( BCrypt::Engine.hash_secret(“1234”, “$2a$11$yMMbLgN9uY6J3LhorfU9iu”))
  4. Compruebe si el hash almacenado es el mismo que el calculado en el paso 3 ( $2a$11$yMMbLgN9uY6J3LhorfU9iuLAUwKxyy8w42ubeL4MWy7Fh8B.CH/yO)

Y así es como Devise almacena las contraseñas de forma segura y lo protege de una variedad de ataques, incluso si su base de datos está comprometida.

¡Ponte en contacto en Twitter @alvesjtiago y avísame si te ha parecido interesante este artículo! Gracias por leer.

PD: De ninguna manera soy un experto en seguridad o criptografía, así que comuníquese si encuentra algo mal. Espero que al simplificar algunos de los conceptos sea más fácil entender lo que está sucediendo.

Gracias @filipepina, @ivobenedito, @jackveiga, @joao_mags y @pedrosmmoreira por los comentarios y sugerencias. Este artículo también está disponible en //blog.tiagoalves.me/how-does-devise-keep-your-passwords-safe.

Más información sobre algunos de los temas.

Factor de costo *

  • Peligros del factor de coste predeterminado de bcrypt
  • Número recomendado de rondas para bcrypt

Ataques de mesa arcoíris **

  • Mesa arcoiris - Wikipedia
  • ¿Qué son las tablas arcoíris y cómo se utilizan?