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 진 문자열이 아닌 원시 바이트로 저장하여 데이터베이스에 좀 더 효율적인 저장소를 얻을 수 있습니다. 이렇게하려면, 교체 hex로 bytes와 hexdigest함께 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.