跳轉到

Python Hash的一致性問題

問題描述

某個專案在爬蟲進行爬蟲時有做重複性資料的去重時,發現無法正常過濾比對,在觀察後,發現相同文字內容的在進行Hash,所得的結果不一致,而導致比對內容發生問題

問題重現

引發這個錯誤的環境

  • 有三台主機執行相同的程式碼,每次取得的內容會進行Hash處理後,比對資料庫內保存的文章Hash值進行比對
  • 每台主機最多開5個執行緒,三台共15個執行緒再跑
  • Python 版本:3.5.10

問題重現的範例

data = "This is a girl"
print(f"'{data}'的hash值為:{hash(data)}")
將上述的程式碼用2個Python解釋器去執行,可以得到下面結果

(第一個視窗執行,Hash=-4036149443421515860)

(第二個視窗執行,Hash=-2834029426068990657)

後續做幾個簡單的小測試

  • 相同執行緒下,相同輸入的Hash值相同
  • 同一個執行緒下,多個子線程的Hash值相同
  • 每次重新執行程式,Hash值不同

問題原因

Python在計算字符串的hash值時,會加入隨機的前綴和後綴(加鹽),以提升計算的複雜度和安全性。

若要關閉前綴和後綴得加鹽,可以將PYTHONHASHSEED環境變數設定成0

下面是關閉的方式

import os
import sys
hashseed = os.getenv('PYTHONHASHSEED')
os.environ['PYTHONHASHSEED'] = '0'
os.execv(sys.executable, [sys.executable] + sys.argv)  
或者在命令列使用(這樣可以避免影響到整個環境)
PYTHONHASHSEED=0 python YOURSCRIPT.py

自我思考

雖然以目前的專案情境,關閉這個選項似乎不影響安全性,因為畢竟只是單純爬蟲拿頁面資料,但是如果其他需要有更高安全性的情境的話,關閉這個應該會引起HashDoS的問題

為了避免影響後續專案環境,還是不要關閉比較好,可能須使用命令列的方式或者使用MD5處理,畢竟Python會這樣設計主要就是為了安全性

這邊紀錄一下使用MD5處理的範例

import hashlib
data = 'this is a girl'
hashlib.md5(data.encode(encoding='UTF-8')).hexdigest()

不同版本的PYTHONHASHSEED參數環境變量

  • Python 2.x
    • 預設情況下,PYTHONHASHSEED 環境變量是關閉的。
    • 每次啟動 Python 時,hash 的前後綴都會設置為 0。
    • 可以透過設置 PYTHONHASHSEED 環境變量來啟用隨機性。
  • Python 3.x
    • 如果不顯式設置 PYTHONHASHSEED 環境變量,則會預設隨機生成前後綴值。
    • 每次啟動解釋器時,hash 算法的結果都會有所不同。