¡Reencuéntrate con la tecnología en Platzi Day! Accede a miles de cursos por 72 horas. Una sorpresa te espera 😎

Regístrate

Termina en:

02D

19H

45M

55S

5

Cómo encriptar contraseñas en Python y por qué hacerlo

¿POR QUÉ TIENES QUE RESETEAR TUS CONTRASEÑAS CUANDO LAS PIERDES?

Cuando olvidas tu contraseña en cualquier aplicación (Facebook, Twitch o incluso Platzi), tienes la posibilidad de llevar a cabo un proceso para configurar una nueva contraseña, ¿por qué no se envía la contraseña directamente al correo en vez de llevar a cabo el engorroso proceso de volver a asignar una nueva? Más allá de las razones obvias de seguridad, todos los servicios profesionales no almacenan tu contraseña en sus bases de datos, sino que almacenan una representación encriptada de la misma que solo se puede usar como verificación, por lo que, aunque los administradores de una aplicación quisieran darte tu contraseña nuevamente, ¡No podrían!, dado que literalmente no saben cual es.

CRIPTOGRAFÍA 101

Para entendernos mejor, veamos rápidamente como funciona la criptografía de manera muy superficial.

La idea de encriptar mensajes es, inicialmente, hacerlos inentendibles para los destinatarios externos al original, hay infinidad de métodos de encriptación y varias categorías (donde las principales pueden ser criptografía simétrica, asimétrica, por bloques y esteganografía); para entenderlo mejor, vamos a trabajar en una variante del encriptado César.

Supón el abecedario (para universalizar el ejemplo eliminemos la letra ñ)

Donde las letras llevan el orden A…B…C…D…E…Etcétera

Si escribimos un mensaje “HOLA MUNDO” es leíble para todos los hispanohablantes.

Ahora definamos una variable de “rotación”, en este caso va a ser +1

La rotación será el cambio de cada letra, si el valor es +1, rotamos cada letra 1 valor hacia adelante, por ejemplo

A -> B
M -> N
T .-> U

Si la rotación fuese de -2 entonces rotaríamos hacia atrás 2 veces la letra por defecto

X -> V
C -> A
D -> B

Si aplicamos una rotación de “+1” al mensaje entero “HOLA MUNDO” resultaría en

IPMB NVOEP

El cual, a vista de cualquier espectador externo, es indescifrable, pero como sabemos cual fue la rotación, podemos invertir el cifrado y volver a leer el valor original.

En pocos minutos acabamos de entender el primer sistema de cifrado conocido en la historia.

El problema con el cifrado César, es que con suficientes iteraciones (fuerza bruta) podemos descubrir cual fue la rotación elegida (con 26 iteraciones hacia la derecha y 26 iteraciones hacia la izquierda cubriríamos todos los casos existentes), por lo que, en contextos reales, se deben usar métodos criptográficos más difíciles de vulnerar.

FUNCIÓN HASH

La función HASH es un algoritmo que permite convertir cualquier conjunto de información en una cadena de caracteres única independientemente de su tamaño, ¿una imagen?, un array único del mismo tamaño, ¿Un audio de 3 horas?, un array único del mismo tamaño, ¿literalmente una letra?, un array único del mismo tamaño.

La función Hash puede ser simplemente una operación de módulo para “encoger” datos grandes o implementaciones altamente complejas para generalizar la sinterización de cualquier grupo de datos.

No abordaré el funcionamiento de la función Hash porque es un poco más complejo que el caso César pero si abordaremos cómo usarla.

ALGORITMOS SHA

Antes de todo, entra a Este link para probar un poco de lo que te voy a hablar. Date la libertad de ingresar lo que quieras y mira la magia.

Los algoritmos SHA (Secure Hash Algorithm) son algoritmos que generan encriptaciones de volúmenes arbitrarios de datos y los convierten a un tamaño fijo (independientemente del input) definidos según el algoritmo SHA que estemos usando (128, 256 o 512, u otros).

Para los siguientes ejemplos vamos a trabajar con SHA256. Si ingresamos “HOLA MUNDO” o el Lore Ipsum entero, obtendremos un Hash de 64 caracteres

Nota: La razón por la que salen 64 caracteres y el SHA es de tamaño de 256, es porque cada carácter alfanumérico ocupa 4 bits, por lo que se divide la cantidad de bits de salida por el consumido en cada elemento: 256/4 = 64

Si ingresamos un ejemplo de Lore Ipsum completo

lore1.png

Si ingresamos solo un “Hola mundo”

lore2.png

Lo clave está en que cada Hash es único, ahora mira que pasa si hacemos una leve modificación al “Hola Mundo”

lore3.png

Lo único que hicimos fue cambiar las mayúsculas por minúsculas y el Hash de salida cambió radicalmente.

Otros ejemplos

"Hola Mundo" -> "C3A4A2E49D91F2177113A9ADFCB9EF9AF9679DC4557A0A3A4602E1BD39A6F481""hola mundo" -> "0B894166D3336435C800BEA36FF21B29EAA801A52F584C006C49289A0DCF6E2F""Hola mundo" -> "CA8F60B2CC7F05837D98B208B57FB6481553FC5F1219D59618FD025002A66F5C""hola Mundo" -> "00256EDC2CABB60F547B376373E936AC4A5BA78A0CA00960EEA3703AC2A707C5"

Con MUY leves diferencias damos con Hash de salida radicalmente diferentes.

Esto te puede llevar a entender el fundamento de cómo funcionan los sistemas Blockchain, pero no vamos a indagar en ello hoy, lo clave es entender en que, no importa lo que entre, siempre sale un valor cifrado.

Hay un pequeño detalle que no había mencionado hasta ahora, y es que las generaciones hechas por SHA, NO PUEDEN SER REVERTIDOS, por lo que una vez convertido a Hash, solo se puede usar como verificación.

CONTRASEÑAS Y CIFRADO

Ya estamos en la parte final del camino, ya entendiste cómo funciona la criptografía y por qué se usa, y ya conoces el uso de la función HASH, ahora vamos al cifrado de contraseñas.

Con lo visto anteriormente, puedes deducir entonces como se cifran las contraseñas en bases de datos y cómo se verifican los inicios de sesión, pero vamos a hacerlo de manera explícita.

Cuando veas una base de datos, todas las contraseñas van a ser cadenas HASH, ninguna será un texto plano

passw.png

Observa que las contraseñas son un arreglo bruto de caracteres, y así son guardadas en entornos profesionales.

Volvamos a los Hash. Supongamos que tu contraseña es “PrettySecure321”, en la base de datos se guardaría así

"PrettySecure321" -> "F0731FCDA236C29696E8EC80431B5BECBB5F050F873EDCA0C9319BBD0BB996D7"

Lo cual es infinitamente más seguro que guardarla en texto plano, pero nos encontramos un problema. Ahora supongamos que otro usuario tiene de contraseña “password123” la cual es mucho más común; esta será encriptada

"password123" -> "EF92B778BAFE771E89245B89ECBC08A44A4E166C06659911881F383D4473E94F"

Pero si por factores externos (como ingeniería social) alguien llega a descubrir dicha contraseña, se podrían vulnerar todos los usuarios que coincidan con el mismo valor.

Por la razón anterior se propone una variable extra llamada “Salt” o sal en español, la cual añade un modificador único a cada contraseña, haciendo que las contraseñas encriptadas se vean diferente aunque sean la misma.

SALT Y BCRYPT, LA SOLUCIÓN DEFINITIVA

Los fundamentos para el manejo de contraseñas son 2, generar contraseñas cifradas y autentificarlas en procesos de inicio de sesión. Ya vimos que guardar las generaciones por SHA puede generar vulnerabilidades si se obtiene la contraseña de una persona por métodos externos y se debe solucionar, y dicha solución se llama Salt.

La sal es una cadena de caracteres aleatoria que se genera previo al cifrado y se añade al proceso de hashing para crear contraseñas únicas aunque se repitan indefinidamente.

La combinación entre SHA y SALT se da en el algoritmo BCrypt, el cual se encarga fácilmente de combinan dichos 2 factores para generar contraseñas encriptadas únicas verificables.

CÓDIGO DE EJEMPLO

Esta es la sección final. Para poder ejecutar el código de a continuación, debes instalar BCrypt


pip install bcrypt

Ahora podemos empezar a jugar con la librería.

Primero importamos la librería y definimos una contraseña

import bcrypt
passwd = 'invulnerablepassword'

Ahora debemos codificar la contraseña a binario, esto se puede hacer de 2 maneras

password = b'invulnerablepassword'password = 'invulnerablepassword'.encode('utf-8')

Ambos resultados funcionarán.

Ahora se genera una sal aleatoria

salt = bcrypt.gensalt()
#print(salt)#b'$2b$12$Fr9U.PfS9P5gNDLUIIP2du'

Y finalmente hashear la contraseña

hashed = bcrypt.hashpw(passwd, salt)

Imprimimos la contraseña cifrada

#Iteration1print(hashed)
b'$2b$12$Fr9U.PfS9P5gNDLUIIP2du4aMpHI.EvBAEEiqfOduqgQaWnorEOL2'

Ahora, si generamos otra sal con la misma contraseña obtenemos

#Iteration2print(hashed)
b'$2b$12$9g2Rx2PmjcDboBZKEAEmNOguvxZM9jTbzSxW1SyzR1Nj63Q918bli'

Ambas fueron la misma contraseña y presentan outputs radicalmente diferentes.

Este es el código para generar el hash anterior.

import bcrypt

passwd = b'invulnerablepassword'

salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(passwd, salt)

print(salt)print(hashed)

Ahora falta únicamente la autentificación.

Recuerda que los hashes son unilaterales, por lo que una vez generados, solo se pueden usar para verificaciones.

Para verificar el hash frente a un dato de entrada del usuario (típico login de cualquier plataforma) usamos la función checkpw() incorporada de Bcrypt, la cual se encarga de leer descomponer el hash y leer la sal usada.

to_test =  b'invulnerablepassword'
hashed = b'$2b$12$9g2Rx2PmjcDboBZKEAEmNOguvxZM9jTbzSxW1SyzR1Nj63Q918bli'if bcrypt.checkpw(passwd, hashed):
    returnTrueelse:
    returnFalse

Ahora sabes como crear contraseñas seguras y compararlas en casos de autentificación.

Escribe tu comentario
+ 2