python內置的hash函數對於字元串來說,每次得到的值不一樣?
如圖,為什麼python內置的哈希函數要這麼選擇?雖然一次運行時值是一致的,但是重新運行時得到的值就不相同了。如果set是用這個hash函數實現的話,那麼需要共享set的話在不同的程序中他們的映射規則不同,難道不會出錯嗎
給題主放倆老新聞的傳送門:
- Huge portions of the Web vulnerable to hashing denial-of-service attack
- Denial of service via hash collisions
這就是Python的字元串hash隨機化的源頭。
Java(Oracle JDK / OpenJDK)也曾經採用過類似的修補方式,像這樣來可選地初始化一個隨機數seed:
http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/1afb03696eb7/src/share/classes/java/util/HashMap.java#l332
/**
* Initialize the hashing mask value. We defer initialization until we
* really need it.
*/
final boolean initHashSeedAsNeeded(int capacity) {
boolean currentAltHashing = hashSeed != 0;
boolean useAltHashing = sun.misc.VM.isBooted()
(capacity &>= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean switching = currentAltHashing ^ useAltHashing;
if (switching) {
hashSeed = useAltHashing
? sun.misc.Hashing.randomHashSeed(this)
: 0;
}
return switching;
}
後來在Oracle JDK8 / OpenJDK8里改為在衝突鏈長的時候使用紅黑樹來降低鏈的長度,就沒有繼續使用這個alternative hashing了。
set/dict的hash還真就是這個玩意實現的,因為它保證了在同一個解釋器進程里相同字元串hash一致。
因為CPython 3.x里的str,它的實體是unicode對象,實體是個utf-8 bytes或者是wstr(嗯這裡真特么有個『或者』),並且通過一個叫做unicodedata_db的玩意來實現緩存(不然就沒法兒保證str對象的不可變與地址一致性了)。
於是乎當你調內部hash的時候,反正不同進程中的解釋器不會共用一個unicodedata_db,這個解釋器進程里的字元串的hash到另一個進程里指不定連個字元串都不是,所以在計算這個內部hash的時候加入了一個code_magic的玩意,同時也均攤了一把複雜度,省得這個db以及set/dict對特定數據表現出極差性能。再說了,誰也不會傻到拿個解釋器內部hash去做跨進程交換。
所以真需要做可重現可跨進程保持一致性的hash,請用hashlib。
新版本Python的一個功能,內置hash函數帶有隨機的magic。補充一下其他答案,這個功能有一定的安全性上的考慮,可以讓攻擊者難以預測內置的set或者dict的一些行為,但遠不足以承擔真正的密碼安全級別的hash的作用。傳遞set和dict到其他進程的時候,只會傳遞其中的值,而不會傳遞hash表結構,hash表是傳到之後重新建立起來的。
python的字元串hash演算法並不是直接遍歷字元串每個字元去計算hash,而是會有一個secret prefix和一個secret suffix,可以認為相當於是給字元串加鹽後做hash,可以規避一些規律輸入的情況
顯然這個secret前後綴的值會直接影響計算結果,而且它有一個啟動時隨機生成的機制,只不過,在2.x版本中,這個機制默認是關閉的,前後綴每次啟動都設置為0,除非你改了相關環境變數來要求隨機,而在3.x中修改了默認行為,如果你不配置環境變數,則默認是隨機一個前後綴值,這樣每次啟動都會不同
這個環境變數是PYTHONHASHSEED,無論在2.x還是3.x中,配置為一個正整數,將作為隨機種子;配置為0,則secret前後綴默認清零(和2.x默認行為就一樣了),配置為空串或「random」,則表示讓進程隨機生成(和3.x默認行為一樣)
具體為啥要這麼做,猜測一個是為了安全性(防字元串hash表的攻擊,比如php曾經碰到的攻擊),另一個可能也是強調不要依賴一些內建結果,因為這種演算法可能隨著版本而更新,避免有些用戶不看文檔,誤以為是永遠不變的
推薦閱讀:
※學習編程的時候不會那些數學題還可以繼續學嗎?
※python如何才能批量生成函數呢?函數名分別等於一個list里的每個元素~?
※為什麼要學習Python?
※如果每天堅持用12個小時學習一門編程語言,一年下來,編程能力會達到什麼程度?
※做網路工程師還是做大數據,請求大佬指路?