1. 程式人生 > 實用技巧 >Python爬蟲技術--基礎篇--內建模組hashlib和hmac

Python爬蟲技術--基礎篇--內建模組hashlib和hmac

1.hashlib

摘要演算法簡介

Python的hashlib提供了常見的摘要演算法,如MD5,SHA1等等。

什麼是摘要演算法呢?摘要演算法又稱雜湊演算法、雜湊演算法。它通過一個函式,把任意長度的資料轉換為一個長度固定的資料串(通常用16進位制的字串表示)

舉個例子,你寫了一篇文章,內容是一個字串'how to use python hashlib - by Michael',並附上這篇文章的摘要是'2d73d4f15c0db7f5ecb321b6a65e5d6d'。如果有人篡改了你的文章,並發表為'how to use python hashlib - by Bob',你可以一下子指出Bob篡改了你的文章,因為根據'how to use python hashlib - by Bob'

計算出的摘要不同於原始文章的摘要。

可見,摘要演算法就是通過摘要函式f()對任意長度的資料data計算出固定長度的摘要digest,目的是為了發現原始資料是否被人篡改過

摘要演算法之所以能指出資料是否被篡改過,就是因為摘要函式是一個單向函式,計算f(data)很容易,但通過digest反推data卻非常困難。而且,對原始資料做一個bit的修改,都會導致計算出的摘要完全不同。

我們以常見的摘要演算法MD5為例,計算出一個字串的MD5值:

import hashlib

md5 = hashlib.md5()
md5.update('how to use md5 in python hashlib?'.encode('utf-8'))
print(md5.hexdigest())

計算結果如下:

d26a53750bc40b38b65a520292f69306

如果資料量很大,可以分塊多次呼叫update(),最後計算的結果是一樣的:

import hashlib

md5 = hashlib.md5()
md5.update('how to use md5 in '.encode('utf-8'))
md5.update('python hashlib?'.encode('utf-8'))
print(md5.hexdigest())

試試改動一個字母,看看計算的結果是否完全不同。

MD5是最常見的摘要演算法,速度很快,生成結果是固定的128 bit位元組,通常用一個32位的16進位制字串表示

另一種常見的摘要演算法是SHA1,呼叫SHA1和呼叫MD5完全類似

import hashlib

sha1 = hashlib.sha1()
sha1.update('how to use sha1 in '.encode('utf-8'))
sha1.update('python hashlib?'.encode('utf-8'))
print(sha1.hexdigest())

SHA1的結果是160 bit位元組,通常用一個40位的16進位制字串表示。

比SHA1更安全的演算法是SHA256和SHA512,不過越安全的演算法不僅越慢,而且摘要長度更長

有沒有可能兩個不同的資料通過某個摘要演算法得到了相同的摘要?完全有可能,因為任何摘要演算法都是把無限多的資料集合對映到一個有限的集合中。這種情況稱為碰撞,比如Bob試圖根據你的摘要反推出一篇文章'how to learn hashlib in python - by Bob',並且這篇文章的摘要恰好和你的文章完全一致,這種情況也並非不可能出現,但是非常非常困難。

摘要演算法應用

摘要演算法能應用到什麼地方?舉個常用例子:

任何允許使用者登入的網站都會儲存使用者登入的使用者名稱和口令。如何儲存使用者名稱和口令呢?方法是存到資料庫表中:

namepassword
michael 123456
bob abc999
alice alice2008

如果以明文儲存使用者口令,如果資料庫洩露,所有使用者的口令就落入黑客的手裡。此外,網站運維人員是可以訪問資料庫的,也就是能獲取到所有使用者的口令。

正確的儲存口令的方式是不儲存使用者的明文口令,而是儲存使用者口令的摘要,比如MD5

usernamepassword
michael e10adc3949ba59abbe56e057f20f883e
bob 878ef96e86145580c38c87f0410ad153
alice 99b1c2188db85afee403b1536010c2c9

當用戶登入時,首先計算使用者輸入的明文口令的MD5,然後和資料庫儲存的MD5對比,如果一致,說明口令輸入正確,如果不一致,口令肯定錯誤。

採用MD5儲存口令是否就一定安全呢?也不一定。假設你是一個黑客,已經拿到了儲存MD5口令的資料庫,如何通過MD5反推使用者的明文口令呢?暴力破解費事費力,真正的黑客不會這麼幹。

考慮這麼個情況,很多使用者喜歡用123456888888password這些簡單的口令,於是,黑客可以事先計算出這些常用口令的MD5值,得到一個反推表:

'e10adc3949ba59abbe56e057f20f883e': '123456'
'21218cca77804d2ba1922c33e0151105': '888888'
'5f4dcc3b5aa765d61d8327deb882cf99': 'password'

這樣,無需破解,只需要對比資料庫的MD5,黑客就獲得了使用常用口令的使用者賬號。

對於使用者來講,當然不要使用過於簡單的口令。但是,我們能否在程式設計上對簡單口令加強保護呢?

由於常用口令的MD5值很容易被計算出來,所以,要確保儲存的使用者口令不是那些已經被計算出來的常用口令的MD5,這一方法通過對原始口令加一個複雜字串來實現,俗稱“加鹽”

def calc_md5(password):
    return get_md5(password + 'the-Salt')

經過Salt處理的MD5口令,只要Salt不被黑客知道,即使使用者輸入簡單口令,也很難通過MD5反推明文口令。

但是如果有兩個使用者都使用了相同的簡單口令比如123456,在資料庫中,將儲存兩條相同的MD5值,這說明這兩個使用者的口令是一樣的。有沒有辦法讓使用相同口令的使用者儲存不同的MD5呢?

如果假定使用者無法修改登入名,就可以通過把登入名作為Salt的一部分來計算MD5,從而實現相同口令的使用者也儲存不同的MD5。

小結

摘要演算法在很多地方都有廣泛的應用。要注意摘要演算法不是加密演算法,不能用於加密(因為無法通過摘要反推明文),只能用於防篡改,但是它的單向計算特性決定了可以在不儲存明文口令的情況下驗證使用者口令

2.hmac

通過雜湊演算法,我們可以驗證一段資料是否有效,方法就是對比該資料的雜湊值,例如,判斷使用者口令是否正確,我們用儲存在資料庫中的password_md5對比計算md5(password)的結果,如果一致,使用者輸入的口令就是正確的。

為了防止黑客通過彩虹表根據雜湊值反推原始口令,在計算雜湊的時候,不能僅針對原始輸入計算,需要增加一個salt來使得相同的輸入也能得到不同的雜湊,這樣,大大增加了黑客破解的難度

如果salt是我們自己隨機生成的,通常我們計算MD5時採用md5(message + salt)。但實際上,把salt看做一個“口令”,加salt的雜湊就是:計算一段message的雜湊時,根據不同口令計算出不同的雜湊。要驗證雜湊值,必須同時提供正確的口令。

這實際上就是Hmac演算法:Keyed-Hashing for Message Authentication。它通過一個標準演算法,在計算雜湊的過程中,把key混入計算過程中。

和我們自定義的加salt演算法不同,Hmac演算法針對所有雜湊演算法都通用,無論是MD5還是SHA-1。採用Hmac替代我們自己的salt演算法,可以使程式演算法更標準化,也更安全。

Python自帶的hmac模組實現了標準的Hmac演算法。我們來看看如何使用hmac實現帶key的雜湊。

我們首先需要準備待計算的原始訊息message,隨機key,雜湊演算法,這裡採用MD5,使用hmac的程式碼如下:

>>> import hmac
>>> message = b'Hello, world!'
>>> key = b'secret'
>>> h = hmac.new(key, message, digestmod='MD5')
>>> # 如果訊息很長,可以多次呼叫h.update(msg)
>>> h.hexdigest()
'fa4ee7d173f2d97ee79022d1a7355bcf'

可見使用hmac和普通hash演算法非常類似。hmac輸出的長度和原始雜湊演算法的長度一致。需要注意傳入的key和message都是bytes型別,str型別需要首先編碼為bytes

小結

Python內建的hmac模組實現了標準的Hmac演算法,它利用一個key對message計算“雜湊”後的hash,使用hmac演算法比標準hash演算法更安全,因為針對相同的message,不同的key會產生不同的hash。