เกลือและแฮชรหัสผ่านใน 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 ไบต์
Duncan

1
@Duncan - ฉัน base64 เข้ารหัสเกลือเพื่อให้ฉันสามารถดำเนินการได้อย่างแข็งแกร่งโดยไม่ต้องกังวลกับปัญหาแปลก ๆ เวอร์ชัน "ไบต์" จะทำงานเป็นสตริงหรือไม่ หากเป็นเช่นนั้นฉันก็ไม่จำเป็นต้องเข้ารหัส base64 t_sha.digest () ด้วย ฉันอาจจะไม่บันทึกรหัสผ่านที่แฮชและเกลือไว้ด้วยกันเพียงเพราะดูเหมือนจะซับซ้อนกว่าเล็กน้อยและอ่านได้น้อยลงเล็กน้อย
Chris Dutrow

หากคุณใช้ Python 2.x อ็อบเจ็กต์ไบต์จะทำงานได้ดีเหมือนสตริง Python ไม่ได้ จำกัด สิ่งที่คุณสามารถมีได้ในสตริง อย่างไรก็ตามสิ่งเดียวกันนี้อาจใช้ไม่ได้หากคุณส่งสตริงไปยังรหัสภายนอกเช่นฐานข้อมูล Python 3.x แยกแยะประเภทและสตริงของไบต์ดังนั้นในกรณีนี้คุณไม่ต้องการใช้การดำเนินการสตริงกับ salt
Duncan

4
ฉันไม่สามารถบอกคุณได้ว่าจะทำอย่างไรใน python แต่ SHA-512 ธรรมดาเป็นตัวเลือกที่ไม่ดี ใช้แฮชช้าเช่น PBKDF2, bcrypt หรือ scrypt
CodesInChaos

หมายเหตุด้านข้าง: ฉันไม่แนะนำให้ใช้ UUID เป็นแหล่งที่มาของการสุ่มแบบเข้ารหัส ใช่การดำเนินงานที่ใช้โดย CPython ถูกเข้ารหัสรักษาความปลอดภัยแต่ที่ไม่ได้กำหนดโดยสเปค ธหรือสเป็ค UUIDและการใช้งานที่มีความเสี่ยงอยู่ หากโค้ดเบสของคุณทำงานโดยใช้การใช้งาน Python โดยไม่มี UUID4 ที่ปลอดภัยคุณจะทำให้ความปลอดภัยของคุณลดลง นั่นอาจเป็นสถานการณ์ที่ไม่น่าเกิดขึ้น แต่ไม่มีค่าใช้จ่ายใด ๆ ที่จะใช้secretsแทน
Mark Amery

คำตอบ:


49

แก้ไข:คำตอบนี้ผิด การทำซ้ำเพียงครั้งเดียวของ SHA512 นั้นรวดเร็วซึ่งทำให้ไม่เหมาะสำหรับใช้เป็นฟังก์ชันแฮชรหัสผ่าน ใช้หนึ่งในคำตอบอื่นที่นี่แทน


ฉันดูดี อย่างไรก็ตามฉันค่อนข้างแน่ใจว่าคุณไม่ต้องการ base64 จริงๆ คุณสามารถทำได้:

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

หากไม่สร้างปัญหาคุณจะได้รับพื้นที่จัดเก็บที่มีประสิทธิภาพมากขึ้นเล็กน้อยในฐานข้อมูลของคุณโดยการจัดเก็บ Salt และรหัสผ่านที่แฮชเป็นไบต์ดิบแทนที่จะเป็นสตริงฐานสิบหก ต้องการทำเช่นนั้นแทนที่hexด้วยbytesและมีhexdigestdigest


1
ใช่ hex จะทำงานได้ดี ฉันชอบ base64 เพราะสตริงสั้นกว่าเล็กน้อย มีประสิทธิภาพมากขึ้นในการส่งผ่านและดำเนินการกับสตริงที่สั้นกว่า
Chris Dutrow

ตอนนี้คุณจะย้อนกลับเพื่อรับรหัสผ่านกลับได้อย่างไร
nodebase

28
คุณไม่ย้อนกลับคุณไม่เคยย้อนรหัสผ่าน นั่นเป็นเหตุผลที่เราแฮชและเราไม่เข้ารหัส หากคุณต้องการเปรียบเทียบรหัสผ่านอินพุตกับรหัสผ่านที่จัดเก็บไว้คุณจะแฮชอินพุตและเปรียบเทียบแฮช หากคุณเข้ารหัสรหัสผ่านใครก็ตามที่มีคีย์สามารถถอดรหัสและดูได้ ไม่ปลอดภัย
Sebastian Gabriel Vinci

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 ของฉัน ดูเหมือนว่าจะต้องมีการแก้ไข ดูคำถาม Stack Overflow นี้: py-bcrypt กำลังติดตั้งบน win 7 64bit python


4
12 เป็นค่าเริ่มต้นสำหรับ gensalt
Ahmed Hegazy

2
ตามpypi.python.org/pypi/bcrypt/3.1.0ความยาวรหัสผ่านสูงสุดสำหรับการ bcrypt 72 ไบต์ อักขระใด ๆ ที่อยู่นอกเหนือจากนั้นจะถูกละเว้น ด้วยเหตุนี้พวกเขาจึงแนะนำให้แฮชด้วยฟังก์ชันแฮชการเข้ารหัสก่อนจากนั้นจึงเข้ารหัสแฮช base64 (ดูรายละเอียดในลิงค์) หมายเหตุด้านข้าง: มันดูเหมือนpy-bcryptเป็นแพคเกจ pypi bcryptเก่าและได้รับการเปลี่ยนชื่อ
balu

48

สิ่งที่ชาญฉลาดไม่ใช่การเขียน crypto ด้วยตัวเอง แต่ต้องใช้บางอย่างเช่น passlib: https://bitbucket.org/ecollins/passlib/wiki/Home

เป็นเรื่องง่ายที่จะเขียนโค้ด crypto ของคุณอย่างปลอดภัย สิ่งที่น่ารังเกียจคือด้วยรหัสที่ไม่ใช่ crypto คุณมักจะสังเกตเห็นได้ทันทีเมื่อมันไม่ทำงานตั้งแต่โปรแกรมของคุณล่ม ในขณะที่มีรหัสเข้ารหัสลับคุณมักจะพบหลังจากที่มันล่าช้าและข้อมูลของคุณถูกบุกรุก ดังนั้นฉันคิดว่ามันจะดีกว่าถ้าใช้แพ็คเกจที่เขียนโดยคนอื่นที่มีความรู้เกี่ยวกับเรื่องนี้และเป็นไปตามโปรโตคอลที่ทดสอบการรบ

นอกจากนี้ passlib ยังมีคุณสมบัติที่ดีบางอย่างซึ่งทำให้ใช้งานง่ายและยังง่ายต่อการอัปเกรดเป็นโปรโตคอลแฮชรหัสผ่านที่ใหม่กว่าหากโปรโตคอลเก่าเสีย

นอกจากนี้ sha512 เพียงรอบเดียวก็เสี่ยงต่อการถูกโจมตีจากพจนานุกรม sha512 ได้รับการออกแบบให้ทำงานได้รวดเร็วและนี่เป็นสิ่งที่ไม่ดีเมื่อพยายามจัดเก็บรหัสผ่านอย่างปลอดภัย คนอื่นคิดมานานและหนักหน่วงเกี่ยวกับปัญหาประเภทนี้ทั้งหมดดังนั้นคุณจึงควรใช้ประโยชน์จากสิ่งนี้ให้ดีขึ้น


5
ฉันคิดว่าคำแนะนำในการใช้ไลบรารี crypo นั้นดี แต่ OP นั้นใช้ hashlib ซึ่งเป็นไลบรารีการเข้ารหัสลับซึ่งอยู่ในไลบรารีมาตรฐาน Python ด้วย (ไม่เหมือนกับ passlib) ฉันจะใช้ hashlib ต่อไปถ้าฉันอยู่ในสถานการณ์ OPs
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()

มิฉะนั้นคุณจะได้รับ:

Traceback (เรียกล่าสุดล่าสุด):
ไฟล์ "" บรรทัด 1 ใน
hashed_password = hashlib.sha512 (รหัสผ่าน + เกลือ) .hexdigest ()
TypeError: Unicode-objects ต้องเข้ารหัสก่อนแฮช


7
ไม่อย่าใช้ฟังก์ชัน sha hash ใด ๆ ในการแฮชรหัสผ่าน ใช้บางอย่างเช่น bcrypt ดูความคิดเห็นของคำถามอื่น ๆ สำหรับเหตุผล
josch

12

ในฐานะที่เป็นของงูใหญ่ 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 ไบต์และการทำซ้ำ 100000 ของ PBKDF2 จะตรงกับตัวเลขขั้นต่ำที่แนะนำในเอกสาร Python การเพิ่มจำนวนการทำซ้ำเพิ่มเติมจะทำให้แฮชของคุณคำนวณได้ช้าลงและปลอดภัยมากขึ้น
  • os.urandom ใช้แหล่งที่มาของการสุ่มที่ปลอดภัยโดยเข้ารหัสเสมอ
  • hmac.compare_digestโดยพื้นฐานแล้วใช้is_correct_passwordเป็นเพียงตัว==ดำเนินการสำหรับสตริง แต่ไม่มีความสามารถในการลัดวงจรซึ่งทำให้ไม่มีภูมิคุ้มกันต่อการโจมตีตามเวลา นั่นอาจไม่ได้ให้ค่าความปลอดภัยเพิ่มเติมแต่ก็ไม่เจ็บเช่นกันดังนั้นฉันจึงใช้มันต่อไป

สำหรับทฤษฎีในสิ่งที่ทำให้กัญชารหัสผ่านที่ดีและรายชื่อของฟังก์ชั่นอื่น ๆ ที่เหมาะสมสำหรับรหัสผ่านคร่ำเครียดกับการดูhttps://security.stackexchange.com/q/211/29805


10

passlib ดูเหมือนจะมีประโยชน์หากคุณจำเป็นต้องใช้แฮชที่จัดเก็บโดยระบบที่มีอยู่ หากคุณสามารถควบคุมรูปแบบได้ให้ใช้แฮชที่ทันสมัยเช่น bcrypt หรือ scrypt ในตอนนี้ bcrypt ดูเหมือนว่าจะใช้งานจาก python ได้ง่ายกว่ามาก

passlib สนับสนุน bcrypt และแนะนำให้ติดตั้ง py-bcrypt เป็นแบ็กเอนด์: http://pythonhosted.org/passlib/lib/passlib.hash.bcrypt.html

คุณยังสามารถใช้py-bcryptได้โดยตรงหากคุณไม่ต้องการติดตั้ง passlib readme มีตัวอย่างการใช้งานเบื้องต้น

ดูเพิ่มเติม: วิธีใช้ scrypt เพื่อสร้างแฮชสำหรับรหัสผ่านและเกลือใน Python


6

ฉันไม่ต้องการรื้อฟื้นกระทู้เก่า แต่ ... ใครก็ตามที่ต้องการใช้โซลูชันความปลอดภัยที่ทันสมัยให้ใช้ argon2

https://pypi.python.org/pypi/argon2_cffi

ชนะการแข่งขันแฮชรหัสผ่าน ( https://password-hashing.net/ ) ใช้งานง่ายกว่า bcrypt และปลอดภัยกว่า bcrypt


0

นำเข้าก่อน: -

import hashlib, uuid

จากนั้นเปลี่ยนรหัสของคุณตามนี้ในวิธีการของคุณ:

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

จากนั้นส่งเกลือนี้และยกเลิกการตั้งชื่อในแบบสอบถาม sql ฐานข้อมูลของคุณด้านล่างล็อกอินคือชื่อตาราง:

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 ฐานข้อมูลของคุณด้านล่าง login คือชื่อตาราง : - 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.