Fungsi hash di Python 3.3 mengembalikan hasil yang berbeda antar sesi

106

Saya telah menerapkan BloomFilter di python 3.3, dan mendapatkan hasil yang berbeda setiap sesi. Mengebor perilaku aneh ini membawa saya ke fungsi hash () internal - ia mengembalikan nilai hash yang berbeda untuk string yang sama setiap sesi.

Contoh:

>>> hash("235")
-310569535015251310

----- membuka konsol python baru -----

>>> hash("235")
-1900164331622581997

Mengapa ini terjadi? Mengapa ini berguna?

redlus
sumber

Jawaban:

140

Python menggunakan benih hash acak untuk mencegah penyerang mengadu domba aplikasi Anda dengan mengirimkan kunci yang dirancang untuk bertabrakan. Lihat pengungkapan kerentanan asli . Dengan mengimbangi hash dengan seed acak (disetel sekali saat startup), penyerang tidak dapat lagi memprediksi kunci apa yang akan bertabrakan.

Anda dapat mengatur benih tetap atau menonaktifkan fitur dengan mengatur PYTHONHASHSEEDvariabel lingkungan ; defaultnya adalah randomtetapi Anda dapat mengaturnya ke nilai integer positif tetap, dengan 0menonaktifkan fitur tersebut sama sekali.

Python versi 2.7 dan 3.2 memiliki fitur dinonaktifkan secara default (gunakan -Rsakelar atau setel PYTHONHASHSEED=randomuntuk mengaktifkannya); itu diaktifkan secara default di Python 3.3 dan yang lebih baru.

Jika Anda mengandalkan urutan kunci dalam set Python, maka jangan. Python menggunakan tabel hash untuk mengimplementasikan tipe-tipe ini dan urutannya bergantung pada riwayat penyisipan dan penghapusan serta benih hash acak. Perhatikan bahwa di Python 3.5 dan yang lebih lama, ini juga berlaku untuk kamus.

Juga lihat object.__hash__()dokumentasi metode khusus :

Catatan : Secara default, __hash__()nilai objek str, byte, dan datetime "di-salted" dengan nilai acak yang tidak dapat diprediksi. Meskipun mereka tetap konstan dalam proses Python individu, mereka tidak dapat diprediksi antara pemanggilan berulang Python.

Ini dimaksudkan untuk memberikan perlindungan terhadap penolakan layanan yang disebabkan oleh input yang dipilih dengan cermat yang mengeksploitasi kinerja kasus terburuk dari penyisipan dict, kompleksitas O (n ^ 2). Lihat http://www.ocert.org/advisories/ocert-2011-003.html untuk detailnya.

Mengubah nilai hash mempengaruhi urutan iterasi dicts, set dan pemetaan lainnya. Python tidak pernah menjamin tentang pengurutan ini (dan biasanya bervariasi antara build 32-bit dan 64-bit).

Lihat juga PYTHONHASHSEED.

Jika Anda membutuhkan implementasi hash yang stabil, Anda mungkin ingin melihat hashlibmodul ; ini mengimplementasikan fungsi hash kriptografi. Proyek pybloom menggunakan pendekatan ini .

Karena offset terdiri dari prefiks dan sufiks (nilai awal dan nilai XOR akhir, masing-masing) Anda tidak bisa begitu saja menyimpan offset. Di sisi positifnya, ini berarti bahwa penyerang juga tidak dapat dengan mudah menentukan offset dengan serangan waktu.

Martijn Pieters
sumber
13
Saya berharap ini muncul di dokumen hash () dan tidak hanya di __hash __ (). 1 untuk jawaban yang bagus. ps Bukankah hashlib berlebihan untuk penggunaan fungsi hash non-kriptografi?
redlus
1
pybloom menggunakan fungsi hashlib. Tetapi jika Anda menginginkan sesuatu yang lebih cepat, Anda dapat memeriksa pyhash .
Håken Lid
3
Mengapa dokumentasi menyebutnya disablesaat menyetelnya ke 0? Saya tidak melihat perbedaan efektif untuk menyetelnya ke nomor benih stabil lama, kecuali saya melewatkan sesuatu. Yang saya maksud adalah ketika saya menggunakan PYTHONHASHSEED=12345saya mendapatkan hash yang sama untuk string yang sama bahkan di seluruh sesi - hal yang sama terjadi saat saya menggunakan PYTHONHASHSEED=0- hash untuk string yang sama akan sama di seluruh sesi (meskipun berbeda dengan 12345, tapi itu jelas, begitulah seed kerja).
blubberdiblub
@blubberdiblub: dengan 0tidak ada seed sama sekali dan hash untuk objek sama dengan yang dihasilkan di versi Python lama tanpa dukungan hashseed.
Martijn Pieters
1
@MartijnPieters Apa artinya hash yang terpengaruh memiliki "tidak ada benih sama sekali"? Apa perbedaan semantik atau kualitatif untuk memiliki benih, katakanlah, 12345, terlepas dari fakta bahwa ia menciptakan dua set sesi berbeda di mana nilai hash berbeda dan selain PYTHONHASHSEED = 0 sama dengan versi yang lebih lama? Dapatkah Anda menghubungkan saya ke bagian kode sumber tertentu? Saya kira maksud saya adalah bahwa jika tidak ada perbedaan seperti itu, saya akan menyebutnya sebagai seed dari 0 dan versi Python yang lebih lama hanya mendukung seed 0. Dokumentasi yang ada saat ini cukup membingungkan saya.
blubberdiblub
10

Pengacakan hash diaktifkan secara default di Python 3 . Ini adalah fitur keamanan:

Pengacakan hash dimaksudkan untuk memberikan perlindungan terhadap penolakan layanan yang disebabkan oleh input yang dipilih dengan cermat yang mengeksploitasi kinerja kasus terburuk dari konstruksi dict

Di versi sebelumnya dari 2.6.8, Anda dapat mengaktifkannya di baris perintah dengan -R, atau opsi lingkungan PYTHONHASHSEED .

Anda dapat mematikannya dengan menyetel PYTHONHASHSEEDke nol.

Peter Wood
sumber
-11

hash () adalah Python built-in fungsi dan menggunakannya untuk menghitung nilai hash untuk objek , bukan untuk string atau num.

Anda dapat melihat detailnya di halaman ini: https://docs.python.org/3.3/library/functions.html#hash .

dan nilai hash () berasal dari metode __hash__ objek. Dokter mengatakan yang berikut:

Secara default, nilai hash () dari str, byte, dan objek datetime "di-salted" dengan nilai acak yang tidak dapat diprediksi. Meskipun mereka tetap konstan dalam proses Python individu, mereka tidak dapat diprediksi antara pemanggilan berulang Python.

Itulah mengapa Anda memiliki nilai hash yang berbeda untuk string yang sama di konsol yang berbeda.

Apa yang Anda terapkan bukanlah cara yang baik.

Saat Anda ingin menghitung nilai hash string, gunakan saja hashlib

hash () bertujuan untuk mendapatkan nilai hash objek, bukan mengaduk.

Adam Wen
sumber
6
hash()sangat valid untuk nilai string atau numerik. Anda mengacaukan ini dengan __hash__metode kustom, yang digunakan olehhash() untuk menyediakan implementasi kustom dari nilai hash.
Martijn Pieters