Sal e hash uma senha em Python

97

Esse código deve criar um hash de uma senha com um salt. A senha salt e hash estão sendo salvas no banco de dados. A senha em si não é.

Dada a natureza delicada da operação, eu queria ter certeza de que tudo era 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
fonte
Por que você está codificando o sal em B64? Seria mais simples usar o salt diretamente e então o b64 codificar os dois juntos t_sha.digest() + salt. Você pode dividir o salt novamente mais tarde, quando tiver decodificado a senha hash salted, pois você sabe que a senha hash decodificada tem exatamente 32 bytes.
Duncan,
1
@Duncan - Eu codifiquei o salt em base64 para que eu pudesse fazer operações fortes nele sem ter que me preocupar com questões estranhas. A versão "bytes" funcionará como uma string? Se for esse o caso, também não preciso codificar t_sha.digest () em base64. Eu provavelmente não salvaria a senha com hash e o sal juntos, só porque parece um pouco mais complicado e um pouco menos legível.
Chris Dutrow,
Se você estiver usando Python 2.x, o objeto bytes funcionará perfeitamente bem como uma string. Python não impõe nenhuma restrição sobre o que você pode ter em uma string. No entanto, o mesmo pode não se aplicar se você passar a string para qualquer código externo, como um banco de dados. O Python 3.x distingue tipos de byte e strings, portanto, nesse caso, você não gostaria de usar operações de string no salt.
Duncan,
4
Eu não posso te dizer como fazer isso em python, mas o SHA-512 simples é uma escolha ruim. Use um hash lento, como PBKDF2, bcrypt ou scrypt.
CodesInChaos
Observação lateral: eu desaconselho o uso de UUIDs como fonte de aleatoriedade criptográfica. Sim, a implementação usada pelo CPython é criptograficamente segura , mas isso não é ditado pela especificação Python nem pela especificação UUID , e existem implementações vulneráveis . Se sua base de código for executada usando uma implementação Python sem UUID4s seguros, você terá enfraquecido sua segurança. Esse pode ser um cenário improvável, mas não custa nada usar em seu secretslugar.
Mark Amery

Respostas:

49

EDIT: Esta resposta está errada. Uma única iteração do SHA512 é rápida , o que o torna impróprio para uso como função de hash de senha. Use uma das outras respostas aqui.


Parece bom para mim. No entanto, tenho certeza de que você não precisa de base64. Você poderia apenas fazer isso:

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

Se isso não criar dificuldades, você pode obter um armazenamento um pouco mais eficiente em seu banco de dados, armazenando a senha salt e hash como bytes brutos em vez de strings hexadecimais. Para fazer isso, substitua hexpor bytese hexdigestpor digest.

Taymon
fonte
1
Sim, hexágono funcionaria bem. Eu prefiro base64 porque as cordas são um pouco mais curtas. É mais eficiente passar e fazer operações em strings mais curtas.
Chris Dutrow,
Agora, como você reverte isso para obter a senha de volta?
nodebase
28
Você não reverte isso, você nunca reverte uma senha. É por isso que fazemos hash e não o criptografamos. Se você precisar comparar uma senha de entrada com uma senha armazenada, faça o hash da entrada e compare os hashes. Se você criptografar uma senha, qualquer pessoa com a chave poderá descriptografá-la e vê-la. Não é seguro
Sebastian Gabriel Vinci
4
uuid.uuid4 (). hex é diferente a cada vez que é gerado. Como você vai comparar uma senha para fins de verificação se não puder obter o mesmo uuid de volta?
LittleBobbyTables
3
@LittleBobbyTables eu acho que saltestá armazenado no banco de dados e a senha com hash salgada também.
clemtoy de
72

Com base nas outras respostas a esta pergunta, implementei uma nova abordagem usando bcrypt.

Por que usar bcrypt

Se bem entendi, o argumento para usar bcryptmais SHA512é que bcrypté projetado para ser lento. bcrypttambém tem uma opção para ajustar a lentidão que você deseja ao gerar a senha com hash pela primeira vez:

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

Devagar é desejável porque se uma parte mal-intencionada colocar as mãos na mesa que contém senhas com hash, será muito mais difícil aplicá-las à força bruta.

Implementação

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

Consegui instalar a biblioteca facilmente em um sistema Linux usando:

pip install py-bcrypt

No entanto, tive mais problemas para instalá-lo em meus sistemas Windows. Parece que precisa de um patch. Veja esta pergunta do Stack Overflow: py-bcrypt instalando no win 7 64bit python

Chris Dutrow
fonte
4
12 é o valor padrão para gensalt
Ahmed Hegazy
2
De acordo com pypi.python.org/pypi/bcrypt/3.1.0 , o comprimento máximo da senha para bcrypt é 72 bytes. Quaisquer caracteres além desse são ignorados. Por esse motivo, eles recomendam o hash com uma função hash criptográfica primeiro e, em seguida, codificar o hash em base64 (consulte o link para obter detalhes). Observação lateral: Parece que py-bcrypté o antigo pacote pypi e desde então foi renomeado para bcrypt.
balu
48

O mais inteligente não é escrever a criptografia você mesmo, mas usar algo como passlib: https://bitbucket.org/ecollins/passlib/wiki/Home

É fácil bagunçar escrevendo seu código criptográfico de maneira segura. O desagradável é que, com código não criptografado, você geralmente percebe imediatamente quando não está funcionando, pois o programa trava. Enquanto com o código criptográfico, muitas vezes você só descobre depois que é tarde demais e seus dados foram comprometidos. Portanto, acho melhor usar um pacote escrito por alguém que tenha conhecimento sobre o assunto e que seja baseado em protocolos testados em batalha.

Além disso, passlib tem alguns recursos interessantes que o tornam fácil de usar e também de atualizar para um protocolo de hash de senha mais recente, se um protocolo antigo for quebrado.

Além disso, apenas uma única rodada de sha512 é mais vulnerável a ataques de dicionário. O sha512 foi projetado para ser rápido e isso é realmente uma coisa ruim ao tentar armazenar senhas com segurança. Outras pessoas têm pensado muito sobre todos esses problemas, então é melhor você tirar vantagem disso.

MD
fonte
5
Suponho que o conselho de usar bibliotecas crypo seja bom, mas o OP já está usando hashlib, uma biblioteca criptográfica que também está na biblioteca padrão do Python (ao contrário de passlib). Eu continuaria a usar hashlib se estivesse na situação de OPs.
dgh
18
@dghubble hashlibé para funções de hash criptográficas. passlibserve para armazenar senhas com segurança. Eles não são a mesma coisa (embora muitas pessoas pareçam pensar assim .. e então suas senhas de usuários são quebradas).
Brendan Long de
3
Caso alguém esteja se perguntando: passlibgera seu próprio salt, que é armazenado na string hash retornada (pelo menos para certos esquemas como BCrypt + SHA256 ) - então você não precisa se preocupar com isso.
z0r
22

Para que isso funcione no Python 3, você precisará codificar UTF-8, por exemplo:

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

Caso contrário, você obterá:

Traceback (última chamada mais recente):
Arquivo "", linha 1, em
hashed_password = hashlib.sha512 (senha + salt) .hexdigest ()
TypeError: objetos Unicode devem ser codificados antes de fazer hash

Wayne
fonte
7
Não. Não use nenhuma função sha hash para fazer hash de senhas. Use algo como bcrypt. Veja os comentários de outras questões para o motivo.
Josch
13

A partir do Python 3.4, o hashlibmódulo na biblioteca padrão contém funções de derivação chave que são "projetadas para hashing de senha seguro" .

Portanto, use um desses, como hashlib.pbkdf2_hmac, com um sal gerado 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')

Observe que:

  • O uso de um sal de 16 bytes e 100.000 iterações de PBKDF2 correspondem aos números mínimos recomendados nos documentos do Python. Aumentar ainda mais o número de iterações tornará seus hashes mais lentos para calcular e, portanto, mais seguros.
  • os.urandom sempre usa uma fonte criptograficamente segura de aleatoriedade
  • hmac.compare_digest, usado em is_correct_password, é basicamente apenas o ==operador para strings, mas sem a capacidade de curto-circuito, o que o torna imune a ataques de temporização. Isso provavelmente não fornece nenhum valor de segurança extra , mas também não faz mal nenhum, então fui em frente e usei isso.

Para ver a teoria sobre o que torna um bom hash de senha e uma lista de outras funções apropriadas para fazer hash de senhas, consulte https://security.stackexchange.com/q/211/29805 .

Mark Amery
fonte
11

passlib parece ser útil se você precisar usar hashes armazenados por um sistema existente. Se você tiver controle do formato, use um hash moderno como bcrypt ou scrypt. No momento, o bcrypt parece ser muito mais fácil de usar no python.

passlib suporta bcrypt e recomenda a instalação de py-bcrypt como back-end: http://pythonhosted.org/passlib/lib/passlib.hash.bcrypt.html

Você também pode usar o py-bcrypt diretamente se não quiser instalar o passlib. O leia-me contém exemplos de uso básico.

veja também: Como usar scrypt para gerar hash para senha e salt em Python

Terrel Shumway
fonte
0

Primeiramente importe: -

import hashlib, uuid

Em seguida, altere seu código de acordo com o seguinte em seu método:

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

Em seguida, passe este salt e uname em sua consulta sql de banco de dados, abaixo de login está um nome de tabela:

sql = "insert into login values ('"+uname+"','"+email+"','"+salt.hexdigest()+"')"
Sheetal Jha
fonte
uname = request.form ["uname"] pwd = request.form ["pwd"] salt = hashlib.md5 (pwd.encode ()) Em seguida, passe este salt e uname em sua consulta sql de banco de dados, abaixo de login está um nome de tabela : - sql = "inserir nos valores de login ('" + uname + "', '" + email + "', '" + salt.hexdigest () + "')"
Sheetal Jha
-1 porque o md5 é muito rápido, o que torna o uso de uma única iteração do md5 uma escolha ruim para uma função de hash de senha.
Mark Amery