Salt y hash una contraseña en Python

93

Se supone que este código usa un hash en una contraseña. La sal y la contraseña hash se están guardando en la base de datos. La contraseña en sí no lo es.

Dada la naturaleza delicada de la operación, quería asegurarme de que todo fuera kosher.

import hashlib
import base64
import uuid

password = 'test_password'
salt     = base64.urlsafe_b64encode(uuid.uuid4().bytes)


t_sha = hashlib.sha512()
t_sha.update(password+salt)
hashed_password =  base64.urlsafe_b64encode(t_sha.digest())
Chris Dutrow
fuente
¿Por qué estás codificando la sal en b64? Sería más sencillo usar la sal directamente y luego b64 codificar ambos juntos t_sha.digest() + salt. Puede dividir la sal nuevamente más tarde cuando haya decodificado la contraseña hash con sal, ya que sabe que la contraseña hash decodificada tiene exactamente 32 bytes.
Duncan
1
@Duncan - Codifiqué el salt en base64 para poder realizar operaciones sólidas sin tener que preocuparme por problemas extraños. ¿Funcionará la versión de "bytes" como una cadena? Si ese es el caso, tampoco necesito codificar en base64 t_sha.digest (). Probablemente no guardaría la contraseña hash y la sal juntas solo porque parece un poco más complicado y un poco menos legible.
Chris Dutrow
Si está utilizando Python 2.x, el objeto bytes funcionará perfectamente bien como una cadena. Python no impone restricciones sobre lo que puede tener en una cadena. Sin embargo, es posible que no se aplique lo mismo si pasa la cadena a cualquier código externo, como una base de datos. Python 3.x distingue los tipos de bytes y las cadenas, por lo que en ese caso no querrá utilizar operaciones de cadena en el salt.
Duncan
4
No puedo decirte cómo hacerlo en Python, pero SHA-512 simple es una mala elección. Utilice un hash lento como PBKDF2, bcrypt o scrypt.
CodesInChaos
Nota al margen: desaconsejaría el uso de UUID como fuente de aleatoriedad criptográfica. Sí, la implementación utilizada por CPython es criptográficamente segura , pero eso no lo dicta la especificación de Python ni la especificación UUID , y existen implementaciones vulnerables . Si su base de código se ejecuta utilizando una implementación de Python sin UUID4 seguros, habrá debilitado su seguridad. Ese puede ser un escenario poco probable, pero su uso no cuesta nada secrets.
Mark Amery

Respuestas:

49

EDITAR: Esta respuesta es incorrecta. Una sola iteración de SHA512 es rápida , lo que la hace inapropiada para su uso como función hash de contraseña. Utilice una de las otras respuestas aquí en su lugar.


Se ve bien para mí. Sin embargo, estoy bastante seguro de que en realidad no necesitas base64. Podrías hacer esto:

import hashlib, uuid
salt = uuid.uuid4().hex
hashed_password = hashlib.sha512(password + salt).hexdigest()

Si no crea dificultades, puede obtener un almacenamiento un poco más eficiente en su base de datos almacenando la sal y la contraseña hash como bytes sin procesar en lugar de cadenas hexadecimales. Para hacerlo, reemplace hexcon bytesy hexdigestcon digest.

Taymon
fuente
1
Sí, el maleficio funcionaría bien. Prefiero base64 porque las cadenas son un poco más cortas. Es más eficiente pasar y realizar operaciones en cadenas más cortas.
Chris Dutrow
Ahora, ¿cómo lo revierte para recuperar la contraseña?
nodos
28
No la inviertes, nunca inviertes una contraseña. Por eso lo codificamos y no lo encriptamos. Si necesita comparar una contraseña de entrada con una contraseña almacenada, hash la entrada y compare los hashes. Si cifra una contraseña, cualquiera que tenga la clave puede descifrarla y verla. No es seguro
Sebastian Gabriel Vinci
4
uuid.uuid4 (). hex es diferente cada vez que se genera. ¿Cómo va a comparar una contraseña para fines de verificación si no puede recuperar el mismo uuid?
LittleBobbyTables
3
@LittleBobbyTables Creo que saltestá almacenado en la base de datos y también en la contraseña hash salada.
clemtoy
70

Basado en las otras respuestas a esta pregunta, he implementado un nuevo enfoque usando bcrypt.

Por que usar bcrypt

Si he entendido bien, el argumento para utilizar bcryptsobre SHA512es que bcryptestá diseñado para ser lento. bcrypttambién tiene una opción para ajustar qué tan lento desea que sea al generar la contraseña hash por primera vez:

# The '12' is the number that dictates the 'slowness'
bcrypt.hashpw(password, bcrypt.gensalt( 12 ))

Lento es deseable porque si una parte malintencionada pone sus manos en la tabla que contiene contraseñas hash, entonces es mucho más difícil forzarlas.

Implementación

def get_hashed_password(plain_text_password):
    # Hash a password for the first time
    #   (Using bcrypt, the salt is saved into the hash itself)
    return bcrypt.hashpw(plain_text_password, bcrypt.gensalt())

def check_password(plain_text_password, hashed_password):
    # Check hashed password. Using bcrypt, the salt is saved into the hash itself
    return bcrypt.checkpw(plain_text_password, hashed_password)

Notas

Pude instalar la biblioteca con bastante facilidad en un sistema Linux usando:

pip install py-bcrypt

Sin embargo, tuve más problemas para instalarlo en mis sistemas Windows. Parece que necesita un parche. Vea esta pregunta de desbordamiento de pila: py-bcrypt instalando en win 7 64bit python

Chris Dutrow
fuente
4
12 es el valor predeterminado para gensalt
Ahmed Hegazy
2
Según pypi.python.org/pypi/bcrypt/3.1.0 , la longitud máxima de la contraseña para bcrypt es 72 bytes. Cualquier carácter más allá de eso se ignora. Por esta razón, recomiendan el hash con una función hash criptográfica primero y luego codificar el hash en base64 (consulte el enlace para obtener más detalles). py-bcryptComentario al margen : parece que es el antiguo paquete pypi y desde entonces se le ha cambiado el nombre a bcrypt.
balu
48

Lo inteligente es no escribir la criptografía usted mismo, sino usar algo como passlib: https://bitbucket.org/ecollins/passlib/wiki/Home

Es fácil estropear la escritura de su código criptográfico de forma segura. Lo malo es que con un código que no es criptográfico, a menudo lo nota inmediatamente cuando no está funcionando, ya que su programa falla. Mientras que con el código criptográfico, a menudo solo se entera después de que es demasiado tarde y sus datos se han visto comprometidos. Por lo tanto, creo que es mejor usar un paquete escrito por otra persona que tenga conocimientos sobre el tema y que esté basado en protocolos probados en batalla.

Además, passlib tiene algunas características agradables que lo hacen fácil de usar y también fácil de actualizar a un protocolo de hash de contraseña más nuevo si se rompe un protocolo antiguo.

Además, una sola ronda de sha512 es más vulnerable a los ataques de diccionario. sha512 está diseñado para ser rápido y esto es realmente algo malo cuando se intenta almacenar contraseñas de forma segura. Otras personas han pensado mucho en este tipo de cuestiones, así que es mejor que aproveches esto.

Maryland
fuente
5
Supongo que el consejo de usar bibliotecas criptográficas es bueno, pero el OP ya está usando hashlib, una biblioteca criptográfica que también se encuentra en la biblioteca estándar de Python (a diferencia de passlib). Seguiría usando hashlib si estuviera en la situación de OP.
dgh
18
@dghubble hashlibes para funciones hash criptográficas. passlibes para almacenar contraseñas de forma segura. No son lo mismo (aunque mucha gente parece pensar que sí ... y luego se descifran las contraseñas de sus usuarios).
Brendan Long
3
En caso de que alguien se lo pregunte: passlibgenera su propia sal, que se almacena en la cadena hash devuelta (al menos para ciertos esquemas como BCrypt + SHA256 ), por lo que no debe preocuparse por eso.
z0r
22

Para que esto funcione en Python 3, necesitará codificar UTF-8, por ejemplo:

hashed_password = hashlib.sha512(password.encode('utf-8') + salt.encode('utf-8')).hexdigest()

De lo contrario, obtendrá:

Traceback (última llamada más reciente):
archivo "", línea 1, en
hashed_password = hashlib.sha512 (contraseña + salt) .hexdigest ()
TypeError: los objetos Unicode deben codificarse antes de aplicar hash

Wayne
fuente
7
No. No use ninguna función de hash sha para aplicar hash a las contraseñas. Utilice algo como bcrypt. Vea los comentarios a otras preguntas para conocer el motivo.
Josch
11

A partir de Python 3.4, el hashlibmódulo de la biblioteca estándar contiene funciones de derivación de claves que están "diseñadas para el cifrado seguro de contraseñas" .

Entonces use uno de esos, como hashlib.pbkdf2_hmac, con una sal generada usando os.urandom:

from typing import Tuple
import os
import hashlib
import hmac

def hash_new_password(password: str) -> Tuple[bytes, bytes]:
    """
    Hash the provided password with a randomly-generated salt and return the
    salt and hash to store in the database.
    """
    salt = os.urandom(16)
    pw_hash = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000)
    return salt, pw_hash

def is_correct_password(salt: bytes, pw_hash: bytes, password: str) -> bool:
    """
    Given a previously-stored salt and hash, and a password provided by a user
    trying to log in, check whether the password is correct.
    """
    return hmac.compare_digest(
        pw_hash,
        hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000)
    )

# Example usage:
salt, pw_hash = hash_new_password('correct horse battery staple')
assert is_correct_password(salt, pw_hash, 'correct horse battery staple')
assert not is_correct_password(salt, pw_hash, 'Tr0ub4dor&3')
assert not is_correct_password(salt, pw_hash, 'rosebud')

Tenga en cuenta que:

  • El uso de una sal de 16 bytes y 100000 iteraciones de PBKDF2 coinciden con los números mínimos recomendados en los documentos de Python. Aumentar aún más el número de iteraciones hará que sus hashes sean más lentos de calcular y, por lo tanto, más seguros.
  • os.urandom siempre utiliza una fuente de aleatoriedad criptográficamente segura
  • hmac.compare_digest, utilizado en is_correct_password, es básicamente el ==operador de cadenas pero sin la capacidad de cortocircuito, lo que lo hace inmune a los ataques de sincronización. Eso probablemente no proporciona ningún valor de seguridad adicional , pero tampoco hace daño, así que seguí adelante y lo usé.

Para obtener una teoría sobre lo que hace un buen hash de contraseña y una lista de otras funciones apropiadas para usar el hash de contraseñas, consulte https://security.stackexchange.com/q/211/29805 .

Mark Amery
fuente
10

passlib parece ser útil si necesita usar hashes almacenados por un sistema existente. Si tiene el control del formato, use un hash moderno como bcrypt o scrypt. En este momento, bcrypt parece ser mucho más fácil de usar desde Python.

passlib admite bcrypt, y recomienda instalar py-bcrypt como backend: http://pythonhosted.org/passlib/lib/passlib.hash.bcrypt.html

También puede usar py-bcrypt directamente si no desea instalar passlib. El archivo Léame tiene ejemplos de uso básico.

ver también: Cómo usar scrypt para generar hash para contraseña y sal en Python

Terrel Shumway
fuente
0

Primero importar: -

import hashlib, uuid

Luego cambie su código de acuerdo con esto en su método:

uname = request.form["uname"]
pwd=request.form["pwd"]
salt = hashlib.md5(pwd.encode())

Luego, pase este salt y uname en la consulta SQL de su base de datos, debajo del inicio de sesión hay un nombre de tabla:

sql = "insert into login values ('"+uname+"','"+email+"','"+salt.hexdigest()+"')"
Sheetal Jha
fuente
uname = request.form ["uname"] pwd = request.form ["pwd"] salt = hashlib.md5 (pwd.encode ()) Luego pase este salt y uname en la consulta SQL de su base de datos, debajo del inicio de sesión hay un nombre de tabla : - sql = "insertar en los valores de inicio de sesión ('" + uname + "', '" + correo electrónico + "', '" + salt.hexdigest () + "')"
Sheetal Jha
-1 porque md5 es muy rápido, lo que hace que usar una sola iteración de md5 sea una mala elección para una función de hash de contraseña.
Mark Amery