Python에서 비밀번호를 솔트하고 해시합니다.


93

이 코드는 솔트를 사용하여 암호를 해시해야합니다. 솔트 및 해시 된 암호가 데이터베이스에 저장됩니다. 암호 자체는 아닙니다.

수술의 민감한 특성을 감안할 때 모든 것이 정결한지 확인하고 싶었습니다.

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())

소금을 b64로 인코딩하는 이유는 무엇입니까? 솔트를 직접 사용하고 b64로 둘 다 함께 인코딩하는 것이 더 간단합니다 t_sha.digest() + salt. 디코딩 된 해시 암호가 정확히 32 바이트라는 것을 알고 있으므로 나중에 솔트 된 해시 암호를 디코딩 할 때 솔트를 다시 분할 할 수 있습니다.
던컨

1
@Duncan-나는 소금을 base64로 인코딩하여 이상한 문제에 대해 걱정할 필요없이 강력한 작업을 수행 할 수 있습니다. "바이트"버전이 문자열로 작동합니까? 이 경우 t_sha.digest ()를 base64로 인코딩 할 필요가 없습니다. 나는 아마도 해시 된 암호와 솔트를 함께 저장하지 않을 것입니다. 그 이유는 그것이 조금 더 복잡하고 조금 덜 읽을 수 있기 때문입니다.
Chris Dutrow 2012 년

Python 2.x를 사용하는 경우 bytes 객체는 문자열처럼 완벽하게 작동합니다. 파이썬은 문자열에서 가질 수있는 것에 제한을 두지 않습니다. 그러나 데이터베이스와 같은 외부 코드에 문자열을 전달하는 경우에는 동일하지 않을 수 있습니다. Python 3.x는 바이트 유형과 문자열을 구별하므로이 경우 솔트에서 문자열 작업을 사용하고 싶지 않습니다.
던컨

4
파이썬으로하는 방법을 말할 수는 없지만 평범한 SHA-512는 나쁜 선택입니다. PBKDF2, bcrypt 또는 scrypt와 같은 느린 해시를 사용하십시오.
CodesInChaos

참고 : 암호화 무작위성의 소스로 UUID를 사용하지 않는 것이 좋습니다. 예, CPython에 의해 사용되는 구현은 암호화 확보되어 있지만, 파이썬의 사양에 의해 결정 아니에요 이나 UUID 사양취약 구현이 존재한다 . 코드베이스가 보안 UUID4없이 Python 구현을 사용하여 실행되면 보안이 약화됩니다. 이는 가능성이 낮은 시나리오 일 수 있지만 secrets대신 사용하는 데 비용이 들지 않습니다 .
Mark Amery

답변:


49

편집 : 이 대답은 잘못되었습니다. SHA512의 단일 반복은 빠르기 때문에 암호 해싱 기능으로 사용하기에 부적절합니다. 대신 여기에 다른 답변 중 하나를 사용하십시오.


내가보기에 괜찮아 보인다. 그러나 실제로 base64가 필요하지 않다고 확신합니다. 다음과 같이 할 수 있습니다.

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

문제가 발생하지 않는 경우 솔트 및 해시 된 암호를 16 진 문자열이 아닌 원시 바이트로 저장하여 데이터베이스에 좀 더 효율적인 저장소를 얻을 수 있습니다. 이렇게하려면, 교체 hexbyteshexdigest함께 digest.


1
예, 16 진수는 잘 작동합니다. 문자열이 조금 더 짧기 때문에 base64를 선호합니다. 더 짧은 문자열을 전달하고 작업을 수행하는 것이 더 효율적입니다.
크리스 Dutrow

이제 암호를 되찾기 위해 어떻게 되돌릴 수 있습니까?
nodebase

28
되 돌리지 않고 암호를 되 돌리지 않습니다. 그것이 우리가 그것을 해시하고 암호화하지 않는 이유입니다. 입력 암호를 ​​저장된 암호와 비교해야하는 경우 입력을 해시하고 해시를 비교합니다. 암호를 암호화하면 키를 가진 사람은 누구나 암호를 해독하여 볼 수 있습니다. 그것은 안전하지
세바스찬 가브리엘 다빈치에게

4
uuid.uuid4 (). hex는 생성 될 때마다 다릅니다. 동일한 uuid를 되 찾을 수없는 경우 확인 목적으로 암호를 어떻게 비교 하시겠습니까?
LittleBobbyTables

3
@LittleBobbyTables salt데이터베이스와 해시 된 암호도 저장되어 있다고 생각 합니다.
clemtoy

70

이 질문에 대한 다른 답변을 바탕으로 bcrypt를 사용하여 새로운 접근 방식을 구현했습니다.

bcrypt를 사용하는 이유

내가 올바르게 이해한다면 bcryptover 사용에 대한 주장 은 느리게 설계 SHA512되었다는 bcrypt것입니다. bcrypt또한 처음으로 해시 된 암호를 생성 할 때 원하는 속도를 조정할 수있는 옵션이 있습니다.

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

악의적 인 당사자가 해시 된 암호가 포함 된 테이블에 손을 대면 무차별 대입하기가 훨씬 더 어렵 기 때문에 느린 것이 바람직합니다.

이행

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)

메모

다음을 사용하여 Linux 시스템에서 라이브러리를 매우 쉽게 설치할 수있었습니다.

pip install py-bcrypt

그러나 Windows 시스템에 설치하는 데 더 많은 문제가있었습니다. 패치가 필요한 것 같습니다. 이 스택 오버플로 질문을 참조하십시오 : Win 7 64bit Python에 설치하는 py-bcrypt


4
12 gensalt의 기본값입니다
아흐메드 헤 가지

2
따르면 pypi.python.org/pypi/bcrypt/3.1.0 , bcrypt 최대 암호 길이는 72 바이트이다. 그 이상의 문자는 무시됩니다. 이러한 이유로 먼저 암호화 해시 함수로 해싱 한 다음 해시를 base64로 인코딩하는 것이 좋습니다 (자세한 내용은 링크 참조). 부수적 인 말 : py-bcrypt오래된 pypi 패키지 인 것 같고 그 이후로 이름이 bcrypt.
balu apr

48

현명한 것은 암호 화폐를 직접 작성하는 것이 아니라 passlib와 같은 것을 사용하는 것입니다 : https://bitbucket.org/ecollins/passlib/wiki/Home

안전한 방법으로 암호화 코드를 작성하는 것은 엉망이되기 쉽습니다. 불쾌한 점은 암호화가 아닌 코드를 사용하면 프로그램이 충돌하기 때문에 작동하지 않을 때 즉시 알아 차릴 수 있다는 것입니다. 암호화 코드를 사용하면 늦게까지 데이터가 손상된 후에 만 ​​알 수 있습니다. 따라서 주제에 대해 잘 알고 있고 전투 테스트 프로토콜을 기반으로하는 다른 사람이 작성한 패키지를 사용하는 것이 더 낫다고 생각합니다.

또한 passlib에는 사용하기 쉽고 오래된 프로토콜이 깨졌을 때 새로운 암호 해싱 프로토콜로 쉽게 업그레이드 할 수있는 몇 가지 멋진 기능이 있습니다.

또한 단일 라운드의 sha512는 사전 공격에 더 취약합니다. sha512는 빠르도록 설계되었으며 이것은 실제로 암호를 안전하게 저장하려고 할 때 나쁜 일입니다. 다른 사람들은이 모든 종류의 문제에 대해 오랫동안 열심히 생각했기 때문에 이것을 더 잘 활용할 수 있습니다.


5
나는 crypo 라이브러리 사용에 대한 조언이 좋다고 생각하지만 OP는 이미 passlib와 달리 Python 표준 라이브러리에있는 암호화 라이브러리 인 hashlib를 사용하고 있습니다. OP 상황에 있다면 hashlib를 계속 사용할 것입니다.
dgh

18
@dghubble hashlib은 암호화 해시 함수용 입니다. passlib암호를 안전하게 저장하기위한 것입니다. (많은 사람들이 그렇게 생각하는 것처럼 보이지만 사용자 암호가 해독 되기는하지만) 그들은 똑같은 것이 아닙니다.
Brendan Long

3
누군가 궁금해하는 경우 : passlib반환 된 해시 문자열에 저장되는 자체 솔트를 생성합니다 (적어도 BCrypt + SHA256 과 같은 특정 체계의 경우 )-따라서 걱정할 필요가 없습니다.
z0r

22

이 작업이 Python 3에서 작동하려면 다음과 같이 UTF-8 인코딩이 필요합니다.

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

그렇지 않으면 다음을 얻을 수 있습니다.

역 추적 (가장 최근 호출 마지막) :
파일 "", 줄 1, in
hashed_password = hashlib.sha512 (password + salt) .hexdigest ()
TypeError : Unicode-objects must be encode before hashing


7
아니요. 암호를 해싱하는 데 sha 해시 함수를 사용하지 마십시오. bcrypt와 같은 것을 사용하십시오. 이유는 다른 질문에 대한 의견을 참조하십시오.
josch

11

Python 3.4부터 hashlib표준 라이브러리 의 모듈 에는 "보안 암호 해싱을 위해 설계된" 키 파생 함수가 포함되어 있습니다. .

따라서 다음을 사용 hashlib.pbkdf2_hmac하여 생성 된 솔트와 함께 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')

참고 :

  • 16 바이트 솔트 사용과 PBKDF2 100000 회 반복은 Python 문서에서 권장하는 최소 숫자와 일치합니다. 반복 횟수를 더 늘리면 해시 계산 속도가 느려지므로 더 안전합니다.
  • os.urandom 항상 암호 학적으로 안전한 무작위 소스를 사용합니다.
  • hmac.compare_digest에서 사용되는 is_correct_password은 기본적으로 ==문자열 의 연산자 일 뿐이지 만 단락 기능이 없으므로 타이밍 공격에 영향을받지 않습니다. 그 실제로 추가 보안 가치를 제공 하지 않지만 나쁘지 않으므로 계속해서 사용했습니다.

좋은 암호 해시를 만드는 이론과 암호를 해싱하는 데 적합한 다른 기능 목록은 https://security.stackexchange.com/q/211/29805를 참조 하십시오 .


10

passlib는 기존 시스템에 저장된 해시를 사용해야하는 경우 유용합니다. 형식을 제어 할 수있는 경우 bcrypt 또는 scrypt와 같은 최신 해시를 사용하십시오. 현재, bcrypt는 파이썬에서 사용하기 훨씬 더 쉬운 것 같습니다.

passlib는 bcrypt를 지원하며 py-bcrypt를 백엔드로 설치할 것을 권장합니다. http://pythonhosted.org/passlib/lib/passlib.hash.bcrypt.html

passlib 를 설치하지 않으려면 py-bcrypt를 직접 사용할 수도 있습니다 . Readme에는 기본 사용 예제가 있습니다.

참조 : scrypt를 사용하여 Python에서 비밀번호 및 솔트에 대한 해시를 생성하는 방법



0

먼저 가져 오기 :-

import hashlib, uuid

그런 다음 방법에서 이에 따라 코드를 변경하십시오.

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

그런 다음이 salt와 uname을 데이터베이스 SQL 쿼리에 전달하십시오. login 아래에 테이블 이름이 있습니다.

sql = "insert into login values ('"+uname+"','"+email+"','"+salt.hexdigest()+"')"

uname = request.form [ "uname"] pwd = request.form [ "pwd"] salt = hashlib.md5 (pwd.encode ()) 그런 다음이 salt와 uname을 데이터베이스 SQL 쿼리에 전달합니다. 로그인 아래에는 테이블 이름이 있습니다. :-sql = "insert into login values ​​( '"+ uname + "', '"+ email + "', '"+ salt.hexdigest () + "')"
Sheetal Jha

-1 md5는 매우 빠르기 때문에 md5의 단일 반복을 사용하는 것은 암호 해싱 기능에 적합하지 않습니다.
Mark Amery
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.