Garam dan hash kata sandi dengan Python

93

Kode ini seharusnya hash kata sandi dengan garam. Kata sandi salt dan hash sedang disimpan dalam database. Kata sandi itu sendiri tidak.

Mengingat sifat sensitif dari operasi tersebut, saya ingin memastikan semuanya halal.

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
sumber
Mengapa Anda b64 menyandikan garam? Akan lebih mudah hanya menggunakan salt secara langsung dan kemudian b64 menyandikan keduanya bersama-sama t_sha.digest() + salt. Anda dapat membagi salt lagi nanti ketika Anda telah memecahkan kode kata sandi hash yang diasinkan karena Anda tahu kata sandi hash yang didekodekan persis 32 byte.
Duncan
1
@ Duncan - Saya base64 menyandikan garam sehingga saya dapat melakukan operasi yang kuat di atasnya tanpa harus khawatir tentang masalah aneh. Akankah versi "byte" berfungsi sebagai string? Jika demikian, maka saya juga tidak perlu menyandikan base64 t_sha.digest (). Saya mungkin tidak akan menyimpan kata sandi yang di-hash dan garam bersama-sama hanya karena itu tampak sedikit lebih rumit dan sedikit kurang dapat dibaca.
Chris Dutrow
Jika Anda menggunakan Python 2.x maka objek byte akan bekerja dengan baik sebagai string. Python tidak membatasi apa pun yang dapat Anda miliki dalam sebuah string. Namun hal yang sama mungkin tidak berlaku jika Anda meneruskan string ke kode eksternal seperti database. Python 3.x membedakan tipe byte dan string sehingga Anda tidak ingin menggunakan operasi string pada salt.
Duncan
4
Saya tidak dapat memberi tahu Anda cara melakukannya dengan python, tetapi SHA-512 biasa adalah pilihan yang buruk. Gunakan hash lambat seperti PBKDF2, bcrypt atau scrypt.
CodesInChaos
Catatan tambahan: Saya menyarankan agar tidak menggunakan UUID sebagai sumber keacakan kriptografi. Ya, implementasi yang digunakan oleh CPython aman secara kriptografik , tetapi itu tidak ditentukan oleh spesifikasi Python maupun spesifikasi UUID , dan implementasi yang rentan ada . Jika basis kode Anda dijalankan menggunakan implementasi Python tanpa UUID4 yang aman, Anda akan melemahkan keamanan Anda. Itu mungkin skenario yang tidak mungkin, tetapi tidak ada biaya untuk digunakan secretssebagai gantinya.
Mark Amery

Jawaban:

49

EDIT: Jawaban ini salah. Sebuah iterasi tunggal SHA512 cepat , yang membuatnya tidak sesuai untuk digunakan sebagai fungsi hashing kata sandi. Gunakan salah satu jawaban lain di sini.


Tampak baik-baik saja oleh saya. Namun, saya cukup yakin Anda tidak benar-benar membutuhkan base64. Anda bisa melakukan ini:

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

Jika tidak menimbulkan kesulitan, Anda bisa mendapatkan penyimpanan yang sedikit lebih efisien dalam database Anda dengan menyimpan kata sandi salt dan hash sebagai byte mentah daripada string hex. Untuk melakukannya, ganti hexdengan bytesdan hexdigestdengan digest.

Taymon
sumber
1
Ya, hex akan bekerja dengan baik. Saya lebih suka base64 karena senarnya sedikit lebih pendek. Lebih efisien untuk menyebarkan dan melakukan operasi pada string yang lebih pendek.
Chris Dutrow
Sekarang, bagaimana Anda membalikkannya untuk mendapatkan kata sandi kembali?
nodebase
28
Anda tidak dapat membalikkannya, Anda tidak pernah membalikkan kata sandi. Itulah mengapa kami melakukan hash dan kami tidak mengenkripsinya. Jika Anda perlu membandingkan kata sandi input dengan kata sandi yang disimpan, Anda mencirikan input dan membandingkan hash. Jika Anda mengenkripsi kata sandi, siapa pun yang memiliki kunci dapat mendekripsi dan melihatnya. Tidak aman
Sebastian Gabriel Vinci
4
uuid.uuid4 (). hex berbeda setiap kali dibuat. Bagaimana Anda akan membandingkan kata sandi untuk tujuan pemeriksaan jika Anda tidak bisa mendapatkan kembali uuid yang sama?
LittleBobbyTables
3
@LittleBobbyTables Saya rasa saltdisimpan dalam database dan kata sandi hash asin juga.
clemtoy
70

Berdasarkan jawaban lain untuk pertanyaan ini, saya telah menerapkan pendekatan baru menggunakan bcrypt.

Mengapa menggunakan bcrypt

Jika saya mengerti benar, argumen untuk menggunakan bcryptlebih SHA512adalah bahwa bcryptdirancang untuk menjadi lambat. bcryptjuga memiliki opsi untuk menyesuaikan seberapa lambat Anda menginginkannya saat membuat kata sandi berciri untuk pertama kalinya:

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

Lambat diinginkan karena jika pihak jahat mendapatkan tangan mereka di atas meja yang berisi kata sandi hash, maka jauh lebih sulit untuk memaksa mereka.

Penerapan

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)

Catatan

Saya dapat menginstal pustaka dengan cukup mudah di sistem linux menggunakan:

pip install py-bcrypt

Namun, saya mengalami lebih banyak masalah saat menginstalnya di sistem windows saya. Tampaknya membutuhkan tambalan. Lihat pertanyaan Stack Overflow ini: py-bcrypt menginstal pada win 7 64bit python

Chris Dutrow
sumber
4
12 adalah nilai default untuk gensalt
Ahmed Hegazy
2
Menurut pypi.python.org/pypi/bcrypt/3.1.0 , panjang kata sandi maksimum untuk bcrypt adalah 72 byte. Karakter apa pun di luar itu akan diabaikan. Untuk alasan ini, mereka merekomendasikan hashing dengan fungsi hash kriptografik terlebih dahulu dan kemudian base64-encode hash tersebut (lihat tautan untuk detailnya). Komentar samping: Tampaknya py-bcryptpaket pypi lama dan sejak itu diubah namanya menjadi bcrypt.
balu
48

Hal yang cerdas bukanlah menulis kripto sendiri tetapi menggunakan sesuatu seperti passlib: https://bitbucket.org/ecollins/passlib/wiki/Home

Sangat mudah untuk mengacaukan penulisan kode kripto Anda dengan cara yang aman. Hal yang buruk adalah bahwa dengan kode non crypto Anda sering segera menyadarinya ketika tidak berfungsi karena program Anda macet. Sementara dengan kode kripto Anda sering hanya mengetahuinya setelah terlambat dan data Anda telah dikompromikan. Oleh karena itu saya pikir lebih baik menggunakan paket yang ditulis oleh orang lain yang memiliki pengetahuan tentang subjek dan yang didasarkan pada protokol yang diuji pertempuran.

Passlib juga memiliki beberapa fitur bagus yang membuatnya mudah digunakan dan juga mudah untuk ditingkatkan ke protokol hashing kata sandi yang lebih baru jika protokol lama ternyata rusak.

Juga hanya satu putaran sha512 yang lebih rentan terhadap serangan kamus. sha512 dirancang untuk menjadi cepat dan ini sebenarnya hal yang buruk ketika mencoba menyimpan kata sandi dengan aman. Orang lain telah berpikir panjang dan keras tentang semua masalah semacam ini sehingga Anda sebaiknya memanfaatkan ini.

MD
sumber
5
Saya kira saran menggunakan pustaka crypo bagus, tetapi OP sudah menggunakan hashlib, pustaka kripto yang juga ada di pustaka standar Python (tidak seperti passlib). Saya akan terus menggunakan hashlib jika saya berada dalam situasi OP.
dgh
18
@dghubble hashlibadalah untuk fungsi hash kriptografi. passlibadalah untuk menyimpan sandi dengan aman. Mereka bukanlah hal yang sama (meskipun banyak orang tampaknya berpikir demikian .. dan kemudian kata sandi pengguna mereka dibobol).
Brendan Long
3
Jika ada yang bertanya-tanya: passlibmenghasilkan garamnya sendiri, yang disimpan dalam string hash yang dikembalikan (setidaknya untuk skema tertentu seperti BCrypt + SHA256 ) - jadi Anda tidak perlu mengkhawatirkannya.
z0r
22

Agar ini berfungsi dengan Python 3, Anda harus menyandikan UTF-8 misalnya:

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

Jika tidak, Anda akan mendapatkan:

Traceback (panggilan terakhir terakhir):
File "", baris 1, di
hashed_password = hashlib.sha512 (password + salt) .hexdigest ()
TypeError: Unicode-objects harus dikodekan sebelum hashing

Wayne
sumber
7
Tidak. Jangan gunakan fungsi hash sha apa pun untuk hashing sandi. Gunakan sesuatu seperti bcrypt. Lihat komentar untuk pertanyaan lain untuk alasannya.
josch
12

Pada Python 3.4, hashlibmodul di pustaka standar berisi fungsi derivasi kunci yang "dirancang untuk hashing sandi yang aman" .

Jadi gunakan salah satu dari itu, seperti hashlib.pbkdf2_hmac, dengan garam yang dihasilkan menggunakan 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')

Perhatikan bahwa:

  • Penggunaan salt 16-byte dan 100000 iterasi PBKDF2 cocok dengan angka minimum yang direkomendasikan dalam dokumen Python. Semakin meningkatkan jumlah iterasi akan membuat hash Anda lebih lambat untuk dihitung, dan karenanya lebih aman.
  • os.urandom selalu menggunakan sumber keacakan yang aman secara kriptografis
  • hmac.compare_digest, digunakan dalam is_correct_password, pada dasarnya hanyalah ==operator untuk string tetapi tanpa kemampuan untuk hubungan pendek, yang membuatnya kebal terhadap serangan waktu. Itu mungkin tidak benar-benar memberikan nilai keamanan tambahan , tetapi tidak ada salahnya, jadi saya telah melanjutkan dan menggunakannya.

Untuk teori tentang apa yang membuat hash kata sandi yang baik dan daftar fungsi lain yang sesuai untuk hash kata sandi dengan, lihat https://security.stackexchange.com/q/211/29805 .

Mark Amery
sumber
10

passlib tampaknya berguna jika Anda perlu menggunakan hash yang disimpan oleh sistem yang ada. Jika Anda memiliki kendali atas formatnya, gunakan hash modern seperti bcrypt atau scrypt. Saat ini, bcrypt tampaknya lebih mudah digunakan dari python.

passlib mendukung bcrypt, dan merekomendasikan menginstal py-bcrypt sebagai backend: http://pythonhosted.org/passlib/lib/passlib.hash.bcrypt.html

Anda juga dapat menggunakan py-bcrypt secara langsung jika Anda tidak ingin menginstal passlib. Readme memiliki contoh penggunaan dasar.

lihat juga: Cara menggunakan scrypt untuk menghasilkan hash untuk kata sandi dan garam dengan Python

Terrel Shumway
sumber
0

Impor pertama: -

import hashlib, uuid

Kemudian ubah kode Anda sesuai dengan ini di metode Anda:

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

Kemudian berikan salt dan uname ini dalam kueri sql database Anda, di bawah ini login adalah nama tabel:

sql = "insert into login values ('"+uname+"','"+email+"','"+salt.hexdigest()+"')"
Sheetal Jha
sumber
uname = request.form ["uname"] pwd = request.form ["pwd"] salt = hashlib.md5 (pwd.encode ()) Kemudian teruskan salt ini dan uname dalam query sql database Anda, di bawah login adalah nama tabel : - sql = "masukkan ke dalam nilai login ('" + uname + "', '" + email + "', '" + salt.hexdigest () + "')"
Sheetal Jha
-1 karena md5 sangat cepat, yang membuat penggunaan satu iterasi md5 menjadi pilihan yang buruk untuk fungsi hashing sandi.
Mark Amery