Hashing de contraseñas en Python con BCrypt

En esta guía, veremos cómo codificar contraseñas en Python con BCrypt y responderemos preguntas tales como: ¿qué es el cifrado de contraseñas y qué es el salado a través de ejemplos prácticos de código?

Introducción

Almacenar contraseñas de forma segura debería ser imperativo para cualquier ingeniero creíble. Las contraseñas de texto sin formato son extremadamente inseguras; ni siquiera debería molestarse en considerar almacenarlas en un formato sin formato. Es suficiente que alguien obtenga privilegios de vista en una base de datos para que toda una base de usuarios se vea comprometida.

Las contraseñas deben almacenarse en una base de datos de forma segura pero manejable.

Siempre debe asumir que su base de datos será comprometida y tomar todas las precauciones necesarias para evitar que cualquier persona que pueda obtener sus datos los explote de cualquier manera posible. Eso es especialmente cierto para las bases de datos que almacenan las credenciales de inicio de sesión de los usuarios u otros datos confidenciales.

Además - es una cuestión de conducta ética. Si un usuario se registra en su sitio web, ¿debería poder encontrar su contraseña al pie de la letra? Las contraseñas a menudo se usan en varios sitios web, contienen información personal y/o podrían exponer un lado del usuario que no les gustaría publicar. Ni usted ni un actor malintencionado deberían poder leer una contraseña de texto sin formato en ningún momento. Esta es la razón por la cual los sitios web no pueden enviarle su contraseña por correo electrónico cuando la olvida, no la conocen. Tienes que resetearlo.

Hashing de contraseñas es un procedimiento económico, seguro y estándar que mantiene las contraseñas a salvo tanto de un webmaster como de actores malintencionados.

Para evitar que alguien explote descaradamente las credenciales de inicio de sesión, siempre hash las contraseñas antes de almacenarlas en una base de datos. Esa es la forma más simple y efectiva de evitar el uso no autorizado de contraseñas almacenadas en su base de datos. Incluso si alguien obtiene las credenciales de inicio de sesión de los usuarios, esa información no se puede usar de ninguna forma, ya que el formato es ilegible para los humanos y difícil de descifrar computacionalmente.

En esta guía, explicaremos cómo cifrar sus contraseñas en Python usando BCrypt. Cubriremos qué es el hashing, cómo se comparan los hash, cómo funciona el "salting" y cómo el hashing incluso hace que las contraseñas sean seguras.

¿Qué es el hashing de contraseñas? {#lo que es el hashing de contraseñas}

En su forma más básica, hashing se refiere a convertir una cadena en otra (que también se llama hash) usando una función hash. Independientemente del tamaño de una cadena de entrada, el hash tendrá un tamaño fijo que está predefinido en un algoritmo hash en sí mismo. El objetivo es que el hash no se parezca en nada a la cadena de entrada y que cualquier cambio en la cadena de entrada produzca un cambio en el hash.

Además, las funciones hash ingresan hash de forma unidireccional. No es un viaje de ida y vuelta y una contraseña codificada no se puede deshacer. La única forma de comprobar si una contraseña de entrada coincide con la de la base de datos es hash también de la contraseña de entrada, y luego comparar los hashes. De esta manera, no necesitamos saber cuál es la contraseña real para determinar si coincide con la de la base de datos o no.

{.icon aria-hidden=“true”}

Nota: En esta guía, usaremos el término "función hash" para una función matemática utilizada para calcular el hash de tamaño fijo basado en la cadena de entrada (las funciones hash populares incluyen SHA256, SHA1 , MD5, CRC32, BCrypt, etc.). Un "algoritmo de hashing" se refiere a todo el proceso de hashing, que incluye no solo una función de hash utilizada, sino muchos más parámetros que se pueden modificar durante el proceso de hashing.

cómo funciona el salado durante el hashing de contraseñas

Cada vez que coloque algo como "myPwd" en el algoritmo hash, obtendrá exactamente el mismo resultado. Pero, si cambia "myPwd" incluso un poco, la salida cambiará más allá del reconocimiento.

Eso asegura que incluso cadenas de entrada similares produzcan hashes completamente diferentes. Si contraseñas similares produjeron los mismos hashes, descifrar una contraseña simple podría llevar a crear una tabla de búsqueda para otros caracteres. Por otro lado, dado que la misma entrada siempre produce la misma salida, un hashing es bastante predecible.

La previsibilidad es fácilmente explotable.

Si alguien sabe qué función hash se usó para codificar una determinada contraseña (y no hay una gran lista de funciones hash en uso), puede descifrarla adivinando todas las contraseñas posibles, haciéndolas con la misma función hash y comparando las obtenidas. hashes al hash de la contraseña que quieren descifrar. Este tipo de ataque se denomina ataque de fuerza bruta y solía funcionar extremadamente bien con contraseñas simples, como password123, 12345678, etc.

La forma más sencilla de evitar ataques de fuerza bruta es utilizar una función hash que sea relativamente lenta de calcular. De esa manera, el ataque de fuerza bruta tomaría tanto tiempo para calcular todos los hashes posibles, que ni siquiera vale la pena intentarlo.

Además, la mayoría de las aplicaciones web tienen "tiempos de espera" incorporados después de que se ingrese una cierta cantidad de contraseñas incorrectas, lo que hace inviable la adivinación por fuerza bruta si alguien está tratando de forzar una contraseña a través de una interfaz de usuario controlada. no se sostiene si alguien obtiene una copia local de una contraseña cifrada.

¿Qué es la sal en el hashing de contraseñas?

A medida que avanza la criptografía, el precio por cómputo y la tecnología, solo elegir una función hash adecuada no es bastante suficiente para proteger las contraseñas almacenadas en una base de datos. En algunos casos, ni siquiera una gran función hash puede prevenir un ataque. Por lo tanto, se recomienda tomar precauciones adicionales para dificultar aún más el descifrado de contraseñas almacenadas.

El problema con el hashing es que la salida (es decir, el hash) siempre es la misma para la misma entrada. Eso hace que el hashing sea predecible y, por lo tanto, vulnerable. Puede resolver eso pasando una cadena aleatoria adicional junto con la cadena de entrada al realizar el hashing. Eso asegurará que el hash ya no produzca la misma salida cada vez que obtenga la misma cadena que la entrada.

Esa cadena pseudoaleatoria de longitud fija que se pasa junto con la cadena de entrada cuando se realiza el hashing se llama salt. Cada vez que desee almacenar una contraseña en una base de datos, se creará una nueva sal aleatoria y se pasará junto con la contraseña a la función hash. En consecuencia, aunque dos usuarios tengan la misma contraseña, su registro en una base de datos será totalmente diferente.

Recuerda que agregar un solo carácter al final de una cadena antes de que el hash cambie el hash por completo.

¿Qué es un salt en hash de contraseña?

La sal utilizada para generar una contraseña se almacena por separado y se agrega a cualquier entrada nueva que se va a codificar y comparar con el hash almacenado en la base de datos, asegurando que incluso con la adición de elementos aleatorios, el usuario puede iniciar sesión usando su respectiva contraseña. El objetivo de saltear no es hacer que sea mucho más inviable desde el punto de vista computacional para descifrar una sola contraseña: es evitar encontrar similitudes entre cadenas codificadas y evitar que un atacante descifre múltiples contraseñas si son las mismo.

A través de salting: las operaciones extremadamente costosas desde el punto de vista computacional se localizan en una sola instancia y deben repetirse para cada contraseña en la base de datos, lo que detiene una cascada de fallas en la seguridad.

Afortunadamente, la totalidad de esta lógica generalmente se abstrae mediante marcos y módulos de seguridad que podemos usar fácilmente en el código.

¿Qué es BCrypt?

BCrypt es un algoritmo de hashing de contraseñas, diseñado con todas las precauciones de seguridad que hemos mencionado en mente. Se utiliza como el algoritmo de hash de contraseña predeterminado en OpenBSD, un sistema operativo de código abierto centrado en la seguridad, y es el algoritmo de hash más ampliamente compatible hasta la fecha.

BCrypt se considera bastante seguro. Su función hash se basa en el algoritmo Blowfish (cifrado), implementa salado y velocidad de cálculo adaptable. La velocidad adaptativa se refiere a la capacidad de aumentar la complejidad del cálculo del valor hash, lo que prueba el algoritmo en el futuro. Sigue siendo lo suficientemente lento como para evitar ataques de fuerza bruta sin importar el aumento de la velocidad informática del hardware.

BCrypt es ampliamente compatible e implementado en la mayoría de los lenguajes principales. Existen implementaciones disponibles públicamente para Java, JavaScript, C, C++, C#, Go, Perl, PHP, etc. En esta guía, cubriremos la implementación en Python del algoritmo BCrypt.

Cómo codificar una contraseña en Python usando BCrypt

El módulo bcrypt en PyPi ofrece una gran implementación de BCrypt que podemos instalar fácilmente a través de pip:

1
$ pip install bcrypt

{.icon aria-hidden=“true”}

Nota:
Para asegurarse de que todas las dependencias requeridas estén instaladas, la documentación oficial le recomienda ejecutar los siguientes comandos según el sistema operativo que elija.

Para Debian y Ubuntu:

1
$ sudo apt-get install build-essential libffi-dev python-dev

Para derivados de Fedora y RHEL:

1
$ sudo yum install gcc libffi-devel python-devel

Para alpino:

1
$ apk add --update musl-dev gcc libffi-dev

Después de haber instalado BCrypt usando pip, puede importarlo a su proyecto:

1
import bcrypt

Para codificar su contraseña usando BCrypt, primero debe convertirla a la matriz de bytes. ¡Para lograr eso, podemos usar el método encode() de la clase string! Codificará la versión de cadena de la contraseña que desea codificar en una matriz de bytes, dado un cierto tipo de codificación, y hará posible codificar usando BCrypt.

Tomemos 'MyPassWord' como contraseña de ejemplo para ilustrar el uso de BCrypt:

1
2
3
pwd = 'MyPassWord'

bytePwd = password.encode('utf-8')

El método encode() toma una cadena en alguna codificación (por ejemplo, ASCII, UTF-8, etc.) y la convierte en una matriz de bytes correspondiente. Esa matriz de bytes formada por una cadena se llama b-cadena.

{.icon aria-hidden=“true”}

Nota: En el ejemplo anterior, pwd es una cadena y bytePwd es una matriz de bytes. Pero si imprime ambas variables, la única diferencia visible es que bytePwd tiene b como prefijo antes de su valor - b'myPassword'. De ahí el nombre de ese tipo de matriz de bytes: una cadena b.

Finalmente, puede cifrar la contraseña codificada usando BCrypt:

1
2
3
4
5
# Generate salt
mySalt = bcrypt.gensalt()

# Hash password
hash = bcrypt.hashpw(bytePwd, mySalt)

Como puede ver, el método utilizado para el hashing en BCrypt es hashpw(). Toma dos argumentos, la representación b-string de una contraseña y un salt. Obviamente, puedes crear una sal manualmente, pero definitivamente se recomienda usar el método gensalt() en su lugar. Es un método BCrypt creado específicamente para crear sal de forma criptográficamente segura.

{.icon aria-hidden=“true”}

Nota: La velocidad de computación adaptativa en BCrypt se logra al establecer una cantidad de iteraciones necesarias para crear una sal. Ese valor se pasa como argumento del método gensalt(). El valor predeterminado es 12, lo que significa que BCrypt usa 2^12^ (4096) iteraciones para generar una sal. Al aumentar el valor de ese argumento, aumenta la cantidad de iteraciones utilizadas para generar una sal y, por extensión, el tiempo necesario para calcular el hash.

Ahora, el hash está almacenando la versión hash de la contraseña pwd. El hash debería verse algo similar a:

1
b'$2b$12$1XCXpgmbzURJvo.bA5m58OSE4qhe6pukgSRMrxI9aNSlePy06FuTi'

No es muy similar a la contraseña original, ¿verdad? Pero si compara el ‘hash’ con la contraseña original utilizando el método ‘checkpw()’ de BCrypt, ¡devuelve un valor ‘Verdadero’!

{.icon aria-hidden=“true”}

Nota: El método checkpw() está diseñado para validar contraseñas cifradas. Calcula la nueva contraseña de entrada, agrega la sal que rastrea automáticamente y luego compara los resultados.

Verifiquemos si el texto literal contraseña es una contraseña válida para el nuevo hash que acabamos de crear:

1
2
print(bcrypt.checkpw(password, hash))
# Output: True

Componentes de una salida BCrypt

Como hemos visto en el ejemplo anterior, la entrada para BCrypt es una contraseña (hasta 72 bytes) y un salt (con el número de iteraciones asociado) y la salida es el hash de 24 bytes.

Examinemos la siguiente ilustración para comprender cómo BCrypt construye el hash producido:

Explicación de BCrypt

Esta ilustración muestra un hash de la contraseña, 'MyPassword', por lo tanto, ilustra el hash de la sección anterior.

Como hemos discutido antes, cada vez que llamas al método gensalt(), produce una nueva matriz de bytes de tamaño fijo (representada por una b-cadena). En este ejemplo, el método gensalt() produjo la salida marcada como salt en la ilustración. Descompongamos la sección salt y expliquemos cada subsección individual.

La sal tiene tres subsecciones divididas por el signo $:

  • versión bcrypt
    A special hashing algorithm identifier - in this case 2b - the newest version of the BCrypt algorithm.

  • exponente
    The argument of the gensalt() method representing the number of iterations used to compute a salt. If no argument is passed, the default value is 12, therefore 2^12^ iterations are used to compute a salt.

  • sal generada
    A radix-64 encoding of the generated salt represented by 22 characters.

Después de eso, BCrypt une la salt con el valor hash de MyPassword y, por lo tanto, crea el hash final de MyPassword.

{.icon aria-hidden=“true”}

Nota: El valor hash de MyPassword (o cualquier otra contraseña) se refiere a una codificación radix-64 de los primeros 23 bytes del hash de 24 bytes. Está representado por 31 caracteres.

Conclusión

Después de leer este artículo, tendrá una sólida comprensión de cómo usar un BCrypt para codificar una contraseña antes de almacenarla en una base de datos. Para poner las cosas en perspectiva, explicamos la terminología básica en un sentido general y luego ilustramos el proceso de hash de una contraseña en el ejemplo de BCrypt.