所謂雜湊 (hash) 是指透過一些編碼演算法 (例如 MD5, SHA1, SHA256, SHA512 等) 將資料轉成一段固定長度的雜湊值, 用來代表該資料的摘要 (digest), 由於不同的資料的雜湊值不同 (資料內容小小的變化雜湊值也會改變), 因此可用來驗證資料內容是否一致或有否被改變; 其次是無法從雜湊值逆推資料內容, 因此具有資料安全性.
Python 內建函式 hash() 與內建模組 hashlib 來計算雜湊值, hash() 函式較簡單, 輸出的雜湊值為 9 位數的整數; 而 hashlib 模組則較複雜, 提供豐富的雜湊值演算法, 輸出值為由16 進位的 0~f 字元組成之字串, 兩者均無須安裝可直接呼叫或匯入使用.
一. 使用 hash() 函式計算雜湊值 :
hash() 可傳入一個基本型態物件 (數值, 字串, 布林值) 或元組來計算其雜湊值, 如果是整數或布林值, 其雜湊值是可預測的 (就是它們本身), 例如 :
>>> hash(1)
1
>>> hash(2)
2
>>> hash(3)
3
>>> hash(123)
123
>>> hash(False)
0
>>> hash(True)
1
>>> hash(1.2345e5)
123450
傳入浮點數會傳回 9 位數的整數 :
>>> hash(1.01)
23058430092136961
>>> hash(1.2345e-3)
2846563194874305
但傳入小數部分為 0 的浮點數, 其雜湊值與等值之整數一樣 :
>>> hash(1.0)
1
>>> hash(2.0)
2
>>> hash(2.00)
2
傳入字串會傳回 9 位數整數 :
>>> hash('abc')
-1521791763672297916
>>> hash('abcd')
8876414069658089904
結構型物件只能傳入元組 (tuple), 因為它是 immutable 的資料 :
>>> hash((1, 2, 3))
529344067295497451
>>> hash(('a', 'b', 'c'))
-6512037586358940145
>>> hash((1.1, 2.2, 3.3))
2823207737123308454
所以看起來只要是 immutable 的資料都可以計算雜湊值, 而像是串列, 字典, 與集合等 mutable 的物件無法計算雜湊值, 但不能說 mutable 的資料都無法計算雜湊值, 這個例外是類別, 自定義類別的物件是 mutable 的, 但只要類別有定義一個 __hash__() 方法, 則其物件仍然可以計算雜湊值, 例如 :
>>> class MyClass:
def __hash__(self):
return 1
>>> a=MyClass()
>>> hash(a)
1
參考 :
二. 使用 hashlib 模組計算雜湊值 :
內建模組 hashlib 提供較多的演算法來計算雜湊值, 且其輸出為十六進位字元 0~f 組成之雜湊值, 碰撞率微乎其微. 首先匯入 hashlib 用 dir() 來檢視內容 :
>>> import hashlib
>>> dir(hashlib)
['__all__', '__block_openssl_constructor', '__builtin_constructor_cache', '__builtins__', '__cached__', '__doc__', '__file__', '__get_builtin_constructor', '__loader__', '__name__', '__package__', '__spec__', '_hashlib', 'algorithms_available', 'algorithms_guaranteed', 'blake2b', 'blake2s', 'md5', 'new', 'pbkdf2_hmac', 'scrypt', 'sha1', 'sha224', 'sha256', 'sha384', 'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512', 'sha512', 'shake_128', 'shake_256']
但 dir() 只顯示模組內的成員名稱, 如果要知道哪些是函式, 哪些是類別, 可用下列自訂模組 members 之 list_members() 函式 :
# members.py
import inspect
def varname(x):
return [k for k,v in inspect.currentframe().f_back.f_locals.items() if v is x][0]
def list_members(parent_obj):
members=dir(parent_obj)
parent_obj_name=varname(parent_obj)
for mbr in members:
child_obj=eval(parent_obj_name + '.' + mbr)
if not mbr.startswith('_'):
print(mbr, type(child_obj))
將此函式存成 members.py 模組, 放在目前供作目錄下, 然後匯入其 list_members() 函式來檢視 openai 套件 :
>>> from members import list_members
>>> list_members(hashlib)
algorithms_available <class 'set'>
algorithms_guaranteed <class 'set'>
blake2b <class 'type'>
blake2s <class 'type'>
md5 <class 'builtin_function_or_method'>
new <class 'function'>
pbkdf2_hmac <class 'builtin_function_or_method'>
scrypt <class 'builtin_function_or_method'>
sha1 <class 'builtin_function_or_method'>
sha224 <class 'builtin_function_or_method'>
sha256 <class 'builtin_function_or_method'>
sha384 <class 'builtin_function_or_method'>
sha3_224 <class 'builtin_function_or_method'>
sha3_256 <class 'builtin_function_or_method'>
sha3_384 <class 'builtin_function_or_method'>
sha3_512 <class 'builtin_function_or_method'>
sha512 <class 'builtin_function_or_method'>
shake_128 <class 'builtin_function_or_method'>
shake_256 <class 'builtin_function_or_method'>
可見 hashlib 提供了例如 md5, sha1/224/256/384 .... 等許多計算雜湊值的演算法函式, 其中較常用的式 md5() 與 sha1(). 用 hashlib 計算雜湊值需先呼叫 md5() 或 sha1() 等演算法函式來建立 HASH 物件 :
>>> import hashlib
>>> md5_hash=hashlib.md5() # 建立 MD5 演算法之 HASH 物件
>>> type(md5_hash)
<class '_hashlib.HASH'>
>>> sha1_hash=hashlib.sha1() # 建立 SHA1 演算法之 HASH 物件
>>> type(sha1_hash)
<class '_hashlib.HASH'>
用上面的 list_members() 來檢視 HASH 物件內容 :
>>> list_members(md5_hash)
block_size <class 'int'>
copy <class 'builtin_function_or_method'>
digest <class 'builtin_function_or_method'>
digest_size <class 'int'>
hexdigest <class 'builtin_function_or_method'>
name <class 'str'>
update <class 'builtin_function_or_method'>
計算雜湊值會用到的 HASH 物件方法如下表 :
HASH 物件的常用方法 | 說明 |
update(data) | 傳入 Byte 類型資料 data 更新 HASH 物件 |
digest() | 計算 HASH 物件內資料之雜湊值 (傳回 Byte 型別) |
hexdigest() | 計算 HASH 物件內資料之雜湊值 (傳回 16 進位數值字串) |
注意, 傳入 update() 的資料必須為 Byte 類型字串, 這可在字串前面加 b' 達成, 如果是非英語系文字, 則要先呼叫字串的 encode('utf-8') 轉成 UTF-8 編碼格式, 例如 :
>>> md5_hash.update(b'Hello World!') # 英數字等 ASCII 字元前面冠 b' 轉成 Byte
>>> md5_hash.digest() # 傳回位元組形式的 MD5 雜湊值
b'\xed\x07b\x87S.\x866^\x84\x1e\x92\xbf\xc5\r\x8c'
>>> md5_hash.hexdigest() # 傳回字串形式的 MD5 雜湊值
'ed076287532e86365e841e92bfc50d8c'
可見 HASH 物件產生的雜湊值都是 32 個 0~f 字元組成.
如果是非英語系資料, 則在傳入 update() 之前須先呼叫字串物件的 encode() 方法將資料轉成 UTF-8 編碼 :
>>> md5_hash.update('醒醒吧,你沒有女朋友'.encode('utf-8'))
>>> md5_hash.digest() # 傳回位元組形式的 MD5 雜湊值
b'\xb7O\xc6\xb49AS=\x16s\xdf\xb5\x10E\xd5-'
>>> md5_hash.hexdigest() # 傳回字串形式的 MD5 雜湊值
'b74fc6b43941533d1673dfb51045d52d'
雜湊值最常用來辨別文件內容是否有變化, 例如文字檔或爬蟲的標的網頁是否有被編輯過, 如果是文字檔, 則讀取時可以直接用 'rb' 模式開啟 (讀取為 Byte 類型), 例如將下列內容以 utf-8 編碼存成 HTML 文字檔 hash.htm 於目前工作目錄下 :
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title></title>
</head>
<body>
<h1>醒醒吧! 你沒有女朋友</h1>
</body>
</html>
然後用 with open() 開啟此檔案計算其雜湊值 :
>>> with open('hash.htm', 'rb') as fp :
data=fp.read()
md5_hash.update(data)
>>> print(md5_hash.hexdigest())
03db7e36e6363bbeb15f2646f573bf85
>>> print(md5_hash.hexdigest())
03db7e36e6363bbeb15f2646f573bf85
只要檔案內容不變, 則雜湊值也不變. 若將上面的網頁檔內容 "醒醒吧! 你沒有女朋友" 後面添加一個驚嘆號為 "醒醒吧! 你沒有女朋友!" :
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title></title>
</head>
<body>
<h1>醒醒吧! 你沒有女朋友!</h1>
</body>
</html>
存檔後再次讀取檔案內容, 則計算出來的雜湊值就會不一樣了 :
>>> with open('hash.htm', 'rb') as fp :
data=fp.read()
md5_hash.update(data)
>>> print(md5_hash.hexdigest())
ba5d2b91cb7e8ae33cfc0cdea06d752d
參考 :
沒有留言:
張貼留言