1. 程式人生 > >Python資料加密,解密的相關操作(hashlib、hmac、random、base64、pycrypto)

Python資料加密,解密的相關操作(hashlib、hmac、random、base64、pycrypto)

本文內容


  1. 資料加密概述
  2. Python中實現資料加密的模組簡介
  3. hashlib與hmac模組介紹
  4. random與secrets模組介紹
  5. base64模組介紹
  6. pycrypto模組介紹
  7. 總結
  8. 參考文件

提示: Python 2.7中的str是位元組串,而Python 3.x中的str是字串。本文中的程式碼都是通過Python 2.7實現的,如果你使用的是Python 3.x,由於下面大部分加密與解密函式的引數都要求是位元組物件,因此在呼叫下面介紹的加解密函式時,可能需要先將字串引數轉換為位元組物件。

一、資料加密概述


1. 網路資料傳輸面臨的威脅

網路安全涉及很多方面,而網路資料的安全傳輸通常會面臨以下幾方面的威脅:

  • 資料竊聽與機密性: 即怎樣保證資料不會因為被截獲或竊聽而暴露。
  • 資料篡改與完整性: 即怎樣保證資料不會被惡意篡改。
  • 身份冒充與身份驗證: 即怎樣保證資料互動雙方的身份沒有被冒充。

2. 相應的解決方案

針對以上幾個問題,可以用以下幾種資料加密方式來解決(每種資料加密方式又有多種不同的演算法實現):

資料加密方式 描述 主要解決的問題 常用演算法
對稱加密 指資料加密和解密使用相同的金鑰 資料的機密性 DES, AES
非對稱加密 也叫公鑰加密,指資料加密和解密使用不同的金鑰--金鑰對兒 身份驗證 DSA,RSA
單向加密 指只能加密資料,而不能解密資料 資料的完整性 MD5,SHA系列演算法

需要說明的是,上面SHA系列演算法是根據生成的密文的長度而命名的各種演算法名稱,如SHA1(160bits)、SHA224、SHA256、SHA384等。我們常聽說的MD5演算法生成的密文長度為128bits。

關於上面提到的這些內容,大家可以參考《網路資料傳輸安全及SSH與HTTPS工作原理》這篇博文來了解更多的資訊。本文主要討論的問題是,我們如何使用Python來實現這些資料加密方式。

二、Python中實現資料加密的模組簡介


Python中的大部分功能都是通過模組提供的,因此我們主要是通過Python中提供的一些內建的模組或外部模組來實現上面提到的各種加密演算法。使用過程也很簡單,只需要呼叫這些模組提供的相應的函式介面即可。

1. Python內建的加密模組演化過程

上面我們已經提到過,單向加密演算法有:MD5、SHA系列演算法 和 HMAC,而到目前為止Python內建的用於實現資料加密的模組也主要是提供單向加密功能,並且這些模組隨著Python版本的迭代也經歷了一些調整和整合:

  • Python2.5之前的版本所提供的加密模組有:md5、sha和hmac
  • Python2.5開始把對md5和sha演算法的實現整合到一個新的模組:hashlib;
  • Python3.x開始去掉了md5和sha模組,僅剩下hashlib和hmac模組;
  • Python3.6增加了一個新的可以產生用於金鑰管理的安全隨機數的模組:secrets。

md5模組和sha模組為什麼會被整合到一個hashlib模組中呢? 因為md5模組提供的是MD5演算法的實現,sha模組提供的是SHA1演算法的實現,而MD5和SHA1都是hash演算法,具體解釋看下面的名詞解釋。

2. 相關名詞解釋

  • HASH: 一般翻譯為“雜湊”(也有直接音譯為“雜湊”),就是把任意長度的輸入(又叫做預對映,pre-image),通過雜湊演算法,變成固定長度的輸出,該輸出值就是雜湊值。這種轉換是一種壓縮對映,也就是雜湊值的空間通常遠小於輸入的空間,不同的輸入可能會雜湊成相同的輸出,而不可能從雜湊值來唯一確認輸入值。簡單來說,hash演算法就是一種將任意長度的訊息壓縮為某一固定長度的訊息摘要的函式

  • MD5: 全稱為 Message Digest algorithm 5,即資訊摘要演算法。該演算法可以生成定長的資料指紋,被廣泛應用於加密和解密技術,常用於檔案和資料的完整性校驗。

  • SHA: 全稱為 Secure Hash Algorithm,即安全雜湊演算法/安全雜湊演算法。該演算法是數字簽名等密碼學應用中的重要工具,被廣泛應用於電子商務等資訊保安領域。根據生成的密文的長度而命名的各種具體的演算法有:SHA1(160bits)、SHA224(224bits)、SHA256(256bits)、SHA384(384bits)等。

  • HMAC: 全稱為 Hash Message Authentication Code,即雜湊訊息鑑別碼。HMAC是基於金鑰的雜湊演算法認證協議,主要是利用雜湊演算法(如MD5, SHA1),以一個金鑰和一個訊息作為輸入,生成一個訊息摘要作為輸出,因此其具體的演算法名稱為HMAC-MD5、HMAC-SHA1等。可見HMAC演算法是基於各種雜湊演算法的,只是它在運算過程中還可以使用一個金鑰來增強安全性。

3. 本文要講解的Python內建模組簡介

Python早期的相關模組這裡不再介紹了,我們今天主要說明的是以下幾個模組:

模組名 內建模組 描述
hashlib Y 主要提供了一些常見的單向加密演算法(如MD5,SHA等),每種演算法都提供了與其同名的函式實現。
hmac Y 提供了hmac演算法的實現,hamc也是單向加密演算法,但是它支援設定一個額外的金鑰(通常被稱為'salt')來提高安全性
random Y 該模組主要用於一些隨機操作,如獲取一個隨機數,從一個可迭代物件中隨機獲取指定個數的元素。
secrets Y 這是Python 3.6中新增的模組,用於獲取安全隨機數。
base64 Y 該模組主要用於二進位制資料與可列印ASCII字元之間的轉換操作,它提供了基於Base16, Base32, 和Base64演算法以及實際標準Ascii85和Base85的編碼和解碼函式。
pycrypto N 支援單向加密、對稱加密和公鑰加密以及隨機數操作,這是個第三方模組,需要額外安裝。

說明: random模組嚴格上來講並不能被稱為資料加密模組,且官方文件中也強調過該模組不應該用於資料加密。但是我們在進行資料加密時,還是會常常用到隨機數的操作,所以這裡就順便對這個模組進行下說明。

三、hashlib與hmac模組介紹


hashlib和hmac都是python內建的加密模組,它們都提供實現了單向加密演算法的api。

1. hashlib模組

hashlib模組簡介:

hashlib模組為不同的安全雜湊/安全雜湊(Secure Hash Algorithm)和 資訊摘要演算法(Message Digest Algorithm)實現了一個公共的、通用的介面,也可以說是一個統一的入口。因為hashlib模組不僅僅是整合了md5和sha模組的功能,還提供了對更多中演算法的函式實現,如:MD5,SHA1,SHA224,SHA256,SHA384和SHA512。

提示: “安全雜湊/安全雜湊” 與 “資訊摘要” 這兩個術語是可以等價互換的。比較老的演算法被稱為訊息摘要,而現代屬於都是安全雜湊/安全雜湊。

hashlib模組包含的函式與屬性:

函式名/屬性名 描述
hashlib.new(name[, data]) 這是一個通用的雜湊物件建構函式,用於構造指定的雜湊演算法所對應的雜湊物件。其中name引數用於指定雜湊演算法名稱,如'md5', 'sha1',不區分大小寫;data是一個可選引數,表示初始資料。
hashlib.雜湊演算法名稱() 這是一個hashlib.new()的替換方式,可以直接通過具體的雜湊演算法名稱對應的函式來獲取雜湊物件,如 hashlib.md5(),hashlib.sha1()等。
hashlib.algorithms_guaranteed Python 3.2新增的屬性,它的值是一個該模組在所有平臺都會支援的雜湊演算法的名稱集合:set(['sha1', 'sha224', 'sha384', 'sha256', 'sha512', 'md5'])
hashlib.algorithms_available Python 3.2新增的屬性,它的值是是一個當前執行的Python直譯器中可用的雜湊演算法的名稱集合,algorithms_guaranteed將永遠是它的子集。

hash物件包含的方法與屬性:

函式名/屬性名 描述
hash.update(data) 更新雜湊物件所要計算的資料,多次呼叫為累加效果,如m.update(a); m.update(b)等價於m.update(a+b)
hash.digest() 返回傳遞給update()函式的所有資料的摘要資訊--二進位制格式的字串
hash.hexdigest() 返回傳遞給update()函式的所有資料的摘要資訊--十六進位制格式的字串
hash.copy() 返回該雜湊物件的一個copy("clone"),這個函式可以用來有效的計算共享一個公共初始子串的資料的摘要資訊。
hash.digest_size hash結果的位元組大小,即hash.digest()方法返回結果的字串長度。這個屬性的值對於一個雜湊物件來說是固定的,md5:16,sha1(20), sha224(28)
hash.block_size hash演算法內部塊的位元組大小
hash.name 當前hash物件對應的雜湊演算法的標準名稱--小寫形式,可以直接傳遞給hashlib.new()函式來建立另外一個同類型的雜湊物件。

hashlib模組使用步驟:

  • 1)獲取一個雜湊演算法對應的雜湊物件(比如名稱為hash): 可以通過 hashlib.new(雜湊演算法名稱, 初始出入資訊)函式,來獲取這個雜湊物件,如hashlib.new('MD5', 'Hello'),hashlib.new('SHA1', 'Hello')等;也可以通過hashlib.雜湊演算法名稱()來獲取這個雜湊物件,如hashlib.md5(), hashlib.sha1()等。

  • 2)設定/追加輸入資訊: 呼叫已得到雜湊物件的update(輸入資訊)方法可以設定或追加輸入資訊,多次呼叫該方法,等價於把每次傳遞的引數憑藉後進行作為一個引數墊底給update()方法。也就是說,多次呼叫是累加,而不是覆蓋。

  • 3)獲取輸入資訊對應的摘要: 呼叫已得到的雜湊物件的digest()方法或hexdigest()方法即可得到傳遞給update()方法的字串引數的摘要資訊。digest()方法返回的摘要資訊是一個二進位制格式的字串,其中可能包含非ASCII字元,包括NUL位元組,該字串長度可以通過雜湊物件的digest_size屬性獲取;而hexdigest()方法返回的摘要資訊是一個16進位制格式的字串,該字串中只包含16進位制的數字,且長度是digest()返回結果長度的2倍,這可用郵件的安全互動或其它非二進位制的環境中。

hashlib模組使用例項:

我們以MD5演算法為例獲取字串"Hello, World"的摘要資訊(也叫資料指紋)

import hashlib

hash  = hashlib.md5()
hash.update('Hello, ')
hash.update('World!')
ret1 = hash.digest()
print(type(ret1), len(ret1), ret1)
ret2 = hash.hexdigest()
print(type(ret2), len(ret2), ret2)

輸出結果:

(<type 'str'>, 16, 'e\xa8\xe2}\x88y(81\xb6d\xbd\x8b\x7f\n\xd4')
(<type 'str'>, 32, '65a8e27d8879283831b664bd8b7f0ad4')

分析:

  • digest()方法返回的結果是一個二進位制格式的字串,字串中的每個元素是一個位元組,我們知道1個位元組是8bits,MD5演算法獲取的資料摘要長度是128bits,因此最後得到的字串長度是128/8=16;
  • hexdigest()方法返回的結果是一個16進位制格式的字串,字串中每個元素是一個16進位制數字,我們知道每個16進位制數字佔4bits,MD5演算法獲取的資料摘要長度是128bits,因此最後得到的字串長度是128/4=32。

在實際工作中,我們通常都是獲取資料指紋的16進位制格式,比如我們在資料庫中存放使用者密碼時,不是明文存放的,而是存放密碼的16進位制格式的摘要資訊。當用戶發起登入請求時,我們按照相同的雜湊演算法獲取使用者傳送的密碼的摘要資訊,與資料中存放的與該賬號對應的密碼摘要資訊做比對,兩者一致則驗證成功。

另外需要說明的是,下面這幾段程式碼是等價的:

hash  = hashlib.md5()
hash.update('Hello, ')
hash.update('World!')
hash  = hashlib.md5()
hash.update('Hello, World!')
hash  = hashlib.new('md5')
hash.update('Hello, World!')
hash  = hashlib.new('md5', 'Hello, ')
hash.update('World!')

說明: 如果我們想使用其他雜湊演算法來獲取資料指紋,只需要把上面程式碼中的"md5"換成其他演算法名即可,如hashlib.sha1(),hashlib.new('sha1')等。

2. hmac模組

hashmac模組簡介:

前面說過,HMAC演算法也是一種一種單項加密演算法,並且它是基於上面各種雜湊演算法/雜湊演算法的,只是它可以在運算過程中使用一個金鑰來增增強安全性。hmac模組實現了HAMC演算法,提供了相應的函式和方法,且與hashlib提供的api基本一致。

hmac模組提供的函式:

函式名 描述
hmac.new(key, msg=None, digestmod=None) 用於建立一個hmac物件,key為金鑰,msg為初始資料,digestmod為所使用的雜湊演算法,預設為hashlib.md5
hmac.compare_digest(a, b) 比較兩個hmac物件,返回的是a==b的值

hmac物件中的方法和屬性:

方法名/屬性名 描述
HMAC.update(msg) 同hashlib.update(msg)
HMAC.digest() 同hashlib.digest()
HMAC.hexdigest() 同hashlib.hexdigest()
HMAC.copy() 同hashlib.copy()
HMAC.digest_size 同hashlib.digest_size
HMAC.block_size 同hashlib.block_size
HMAC.name 同hashlib.name

hmac模組使用步驟:

hmac模組模組的使用步驟與hashlib模組的使用步驟基本一致,只是在第1步獲取hmac物件時,只能使用hmac.new()函式,因為hmac模組沒有提供與具體雜湊演算法對應的函式來獲取hmac物件。

hmac模組使用例項:

import hmac
import hashlib

h1 = hmac.new('key', 'Hello, ')
h1.update('World!')
ret1 = h1.hexdigest()
print(type(ret1), len(ret1), ret1)

h2 = hmac.new('key', digestmod=hashlib.md5)
h2.update('Hello, World!')
ret2 = h2.hexdigest()
print(type(ret2), len(ret2), ret2)

輸出結果:

(<type 'str'>, 32, 'cfad9d610c1e548a03562f8eac399033')
(<type 'str'>, 32, 'cfad9d610c1e548a03562f8eac399033')

四、random與secrets模組介紹


random和secrets模組都是Pytthon內建的隨機數操作模組,其中secrets模組是Python 3.6才新增的模組。

1. random模組

random模組介紹:

random模組實現了一個偽隨機數生成器,可用來生成隨機數以及完成與隨機數相關的功能。下面我們來介紹下該模組下常用的幾個函式:

函式名 描述
random.random() 用於生成半開區間[0, 1.0)內的一個隨機浮點數
random.uniform(a, b) 用於生成一個指定範文內[a, b]的隨機浮點數
random.randint(a, b) 用於生成一個指定範圍內[a, b]的整數
random.randrange(start, stop[, step]) 用於從指定範圍內[start, stop),按指定基數step遞增的集合中獲取一個隨機數,step預設值為1。
random.randrange(stop) 等價於random.randrange(0, stop)
random.choice(seq) 從指定序列seq中隨機獲取一個元素
random.sample(population, k) 從指定序列中隨機獲取k個不重複的元素,並以列表形式返回,用於不進行內容替換的隨機抽樣。
random.shuffle(x[, random]) 用於隨機打亂一個列表中元素,需要注意的是該函式操作的是列表物件,且沒有返回值。

說明:

  • 1)random.sample(population, k)只有在population中沒有重複元素的情況下獲取到的隨機抽樣結果才會有相同的元素;
  • 2)其實上面這些函式都是基於random.random()這個基礎函式實現的;
  • 3)官方文章中有宣告,該模組完全不適合用作資料加密。

random模組例項:

import random

print("random.random(): ", random.random())
print("random.uniform(10, 20): ", random.uniform(10, 20))
print("random.randint(10, 20): ", random.randint(10, 20))
print("random.randrange(10, 20, 2): ", random.randrange(10, 20, 2))
print("random.choice('abcd1234'): ", random.choice('abcd1234'))
print("random.sample('abcd1234', 3): ", random.sample('abcd1234', 3))
print("random.sample('abcd1234', 3): ", random.sample('abcd1234', 3))
print("random.shuffle([1, 2, 3, 4, 5, 6]): ", random.shuffle([1, 2, 3, 4, 5, 6]))
list = [1, 2, 3, 4, 5, 6]
random.shuffle(list)
print("random.shuffle([1, 2, 3, 4, 5, 6]): ", list)

輸出結果:

('random.random(): ', 0.2967959697940342)
('random.uniform(10, 20): ', 10.774070602657055)
('random.randint(10, 20): ', 18)
('random.randrange(10, 20, 2): ', 18)
("random.choice('abcd1234'): ", 'd')
("random.sample('abcd1234', 3): ", ['d', '1', '4'])
("random.sample('abcd1234', 3): ", ['d', 'd', 'd'])
('random.shuffle([1, 2, 3, 4, 5, 6]): ', None)
('random.shuffle([1, 2, 3, 4, 5, 6]): ', [5, 1, 2, 4, 3, 6])

再次說明,random.shuffle()的函式沒有返回結果,且其操作的引數必須是一個列表物件。

2. secrets模組

secrets模組介紹:

secrets模組是Python 3.6新增的內建模組,它可以生成用於管理密碼、賬戶驗證資訊、安全令牌和相關祕密資訊等資料的密碼強隨機數。需要特別宣告的是,與random模組中的預設偽隨機數生成器相比,我們應該優先使用secrets模組,因為random模組中的預設偽隨機數生成器是為建模和模擬而設計的,不是為安全或密碼學而設計的。總體來講,我們可以通過secrets模組完成兩種操作:

  • 1)生成安全隨機數
  • 2)生成一個篤定長度的隨機字串--可用作令牌和安全URL

secrets模組提供的函式:

下面來看下secrets模組提供的函式:

函式名 描述
secrets.choice(sequence) 功能與random.choice(seq)相同,從指定的非空序列中隨機選擇一個元素並返回
secrets.randbelow(n) 功能與random.randrange(n)相同,從半開區間[0, n)內隨機返回一個整數
secrets.randbits(k) 返回一個帶有k個隨機位的整數
secrets.token_bytes([nbytes=None]) 返回一個包含nbytes個位元組的隨機位元組串
secrets.token_hex([nbytes=None]) 返回一個包含nbytes位元組的16進位制格式的隨機文字字串,每個位元組被轉成成2個16進位制數字,這可以用來生成一個隨機密碼
secrets.token_urlsafe([nbytes]) 返回一個包含nbytes個位元組的隨機安全URL文字字串,這可以在提供重置密碼的應用中用來生成一個臨時的隨機令牌
secrets.compare_digest(a, b) 比較字串a和字串b是否相等,相等則返回True,否則返回False

如果以上函式中的nbytes引數未提供或設定為None則會取一個合理的預設值。那麼生成一個令牌(token)時應該使用多個位元組呢? 為了抵抗暴力破解攻擊,令牌需要有足夠的隨機性,當生成令牌使用的位元組數越多時,暴力破解需要嘗試的次數就越多。因此,當計算機的計算能力變得更強時,也就意味著計算可以再更短的時間內完成更多的猜測次數。因此,這裡所使用的位元組個數不應該是一個固定的值,而是應該隨著計算機計算能力的增強而增加。到2015年為止,我們相信32個位元組(256bits)的隨機性對於secrets模組的典型應用場景來說是足夠的了。

secrets模組的最佳實踐

例項1: 生成一個由8位數字和字母組成的隨機密碼

import secrets
import string

alphanum = string.ascii_letters + string.digits
password = ''.join(secrets.choice(alphanum) for i in range(8))

例項2: 生成一個由10位數字和字母組成的隨機密碼,要求至少有一個小寫字元,至少一個大寫字元 和 至少3個數字

import secrets
import string

alphanum = string.ascii_letters + string.digits
while True:
    password = ''.join(secrets.choice(alphanum) for i in range(10))
    if (any(c.islower() for c in password)
            and any(c.isupper() for c in password)
            and len(c.isdigit() for c in password) >= 3):
        break

例項3: 生成一個用於找回密碼應用場景的、包含一個安全令牌的、很難猜到的臨時URL

import secrets
url = 'https://mydomain.com/reset=' + secrets.token_urlsafe()

說明: secrets模組是Python 3.6新增的內建模組,儘管官方文件中強調過random模組並不適合用來做安全和加密相關的工作,但是在Python 3.6之前我們還是可以用random模組來模擬secrets模組提供的這些功能的實現。

五、base64模組介紹


經常聽到有人說“base64加密”,其實base64並不能用於資料加密,它也不是為了純粹的資料加密而生的,它的出現是為了解決不可見字串的網路傳輸和資料儲存問題。因為,用base64對資料進行轉換的過程不能成為“加密”與“解密”,只能成為“編碼”與“解碼”。下面我們也會用到它,所以這裡順便做下簡單的介紹。

1. base64的作用

Base64是一種用64個字元來表示任意二進位制資料的方法,它是一種通過查表對二進位制資料進行編碼的方法,不能用於資料加密。base64最初的出現時為了能夠正確的傳輸郵件資料,因為郵件中的附件(比如圖片)的二進位制數中可能存在不可見字元(ascii碼中128-255之間的值是不可見字元),比如我們嘗試用記事本或其他文字編輯器開啟一個圖片時,通常都會看到一大堆亂碼,這些亂碼就是不可見字元。由於早期的一些網路裝置和網路協議是無法正確識別這些字元的,這就可能在資料傳輸時出現各種無法預知的問題。base64的作用就是把含有不可見字元的資訊用可見字元來表示(Ascii碼中0-127之間的值是可見字元),從而解決這個問題。

關於base64的介紹及實現原理可以看看這幾篇文章:

  • http://www.cnblogs.com/wellsoho/archive/2009/12/09/1619924.html
  • https://www.zhihu.com/question/36306744/answer/
  • http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001399413803339f4bbda5c01fc479cbea98b1387390748000

2. base64的常見應用場景

base64適用於小段內容的編碼,比如數字證書的簽名、URL、Cookie資訊等需要通過網路進行傳輸的小段資料。關於base64在數字簽名中的應用會在本文後面講解pycrypto模組使用例項時有具體的應用示例。

3. base64模組介紹及簡單使用示例

Python中有一個內建的base64模組可直接用來進行base64的編碼和解碼工作--即提供 “二進位制資料” 與 “可列印(可見)的ASCII字元”之間的轉換功能。常用的函式有以下幾個:

函式名 描述
base64.b64encode(s, altchars=None) 對二進位制資料(位元組串)s通過base64進行編碼,返回編碼後的位元組串
base64.b64decode(s, altchars=None, validate=False) 對通過base64編碼的位元組物件或ASCII字串s進行解碼,返回解碼後的位元組串
base64.urlsafe_b64encode(s) 與b64encode()函式不同的是,它會把標準Base64編碼結果中的字元'+'和字元'/'分別替換成字元'-'和字元'_'。
base64.urlsafe_b64decode(s) 解碼通過base64.urlsafe_b64encode()函式編碼的位元組物件或ASCII字串s。

提示: URL中有一些有特殊意義的字元,也就是保留字元:';', '/', '?', ':', '@', '&', '=', '+', '$' 和 ',' ,在URL的引數值中應該避免這些字元的出現。

下面來看個簡單的示例:

import base64

data = 'hello, 世界!'
based_data1 = base64.b64encode(data)
plain_data1 = base64.b64decode(based_data1)
based_data2 = base64.urlsafe_b64encode(data)
plain_data2 = base64.urlsafe_b64decode(based_data2)
print(based_data1)
print(based_data2)
print(plain_data1)
print(plain_data2)

輸出結果:

aGVsbG8sIOS4lueVjO+8gQ==
aGVsbG8sIOS4lueVjO-8gQ==
hello, 世界!
hello, 世界!

3. base64編碼結果後的等號'='

通過上面的這個簡單示例的輸出結果會發現,隨翻urlsafe_b64encode()函式會把編碼結果中的字元'+'和字元'/'替換成其他URL的非保留字元,但是它的編碼結果中還是可能出現字元'='。那麼這些字元'='代表什麼呢?能否去掉呢?

其實base64編碼的過程中會先把原來資料中的每3個位元組的二進位制資料編碼為4個位元組的文字資料,當原始資料最後不滿3個位元組時就需要用'\00'位元組進行補位湊夠3個位元組,而且會在編碼結果的最後加上相應個數的'='號來表示補了多少個位元組,這樣解碼的時候就可以去掉那些補位的位元組。

由此我們可以得出兩個結論:

  • 1)base64編碼後的結果的末尾可能存在字元'='個數分別是:0個、1個和2個;
  • 2)base64編碼後的結果應該是4的倍數。

基於以上第2個結論,為了避免編碼結果中可能出現的的字元'='對網路資料傳輸造成影響,可以在傳出前去掉後面的字元'=',接收方可以通過對資料的長度對4求模得到應該補上的字元'='個數,從而得到正確的資料。比如,我們可以通過下面這個解碼函式來完成這個過程:

import base64

def safe_b64decode(s):
    length = len(s) % 4
    for i in range(length):
        s = s + '='
    return base64.b64decode(s)

if __name__ == '__main__':
    print(safe_b64decode('aGVsbG8sIOS4lueVjO+8gQ=='))
    print(safe_b64decode('aGVsbG8sIOS4lueVjO+8gQ='))
    print(safe_b64decode('aGVsbG8sIOS4lueVjO+8gQ'))

輸出結果:

hello, 世界!
hello, 世界!
hello, 世界!

可見,雖然我們把上面那個示例中通過base64編碼後的結果後面的字元'='去掉了,通過我們自己定義的safe_b64decode()函式最終得到了正確的解碼結果。

六、pycrypto模組


1. pycryto模組介紹

pycryto模組不是Python的內建模組,它的官方網站地址是這裡。pycrypto模組是一個實現了各種演算法和協議的加密模組的結合,提供了各種加密方式對應的多種加密演算法的實現,包括 單向加密、對稱加密以及公鑰加密和隨機數操作。而上面介紹的hashlib和hmac雖然是Python的內建模組,但是它們只提供了單向加密相關演算法的實現,如果要使用對稱加密演算法(如, DES,AES等)或者公鑰加密演算法我們通常都是使用pycryto這個第三方模組來實現。

需要注意的是,pycrypto模組最外層的包(package)不是pycrypto,而是Crypto。它根據加密方式類別的不同把各種加密方法的實現分別放到了不同的子包(sub packages)中,且每個加密演算法都是以單獨的Python模組(一個.py檔案)存在的。我們來看下這些子包:

包名 描述
Crypto.Hash 該包中主要存放的是單向加密對應的各種雜湊演算法/雜湊演算法的實現模組,如MD5.py, SHA.py,SHA256.py等。
Crypto.Cipher 該包中主要存放的是對稱加密對應的各種加密演算法的實現模組,如DES.py, AES.py, ARC4.py等;以及公鑰加密對應的各種加密演算法的實現模組,如PKCS1_v1_5.py等。
Crypto.PublicKey 該包中主要存放的是公鑰加密與簽名演算法的實現模組,如RSA.py, DSA.py等。
Crypto.Signatue 該包中主要存放的是公鑰簽名相關演算法的實現模組,如PKCS1_PSS.py, PKCS1_v1_5.py。
Crypto.Random 該包中只有一個隨機數操作的實現模組 random.py
Crypto.Protocol 該包中存放的是一些加密協議的實現模組,如Chaffing.py, KDF.py等。
Crypto.Util 該包存放的是一些有用的模組和函式

這裡需要說明的是,Crypto.PublicKey子包下的RSA.py和DSA.py模組只是用來生成祕鑰對的,而基於公鑰的加密與解密功能是由Crypto.Cipher子包下的PKCS1_v1_5.py或PKCS1_OAEP.py以這個金鑰對兒為金鑰來實現的;同樣,簽名與驗證相關演算法的功能是由Crypto.Signature子包下的PKCS1_v1_5.py和PKCS1_PASS.py以這個金鑰對而為金鑰來實現的。

2. pycrypto的安裝與使用

pycrypto的安裝

由於pycryto不是Python的內建模組,所以在使用它之前需要通過Python模組管理工具(如pip)來安裝。不幸的是,如果你使用的是Windows平臺會遇到一些問題,比如執行pip install pycryto命令來安裝pycrpto時可能會得到以下錯誤提示資訊:

error: Microsoft Visual C++ 9.0 is required (Unable to find vcvarsall.bat). 

error: Unable to find vcvarsall.bat

這是由於pycrypto模組是用C語言實現的,Python模組管理工具在安裝它時需要使用C/C++編譯工具對它的程式碼進行編譯,但是找不到對應版本的編譯工具,具體解釋及解決方案請檢視《這篇博文》

pycrypto的使用方式

由於pycrypto把不同的類別加密演算法的實現模組都放到了Crypto下不同的子包下了,所以我們只需要確定我們所需要使用的加密演算法的實現模組在哪個子包下,然後匯入相應的實現模組就可以使用了。比如我們打算使用MD5演算法,就可以通過from Crypto.Hash import MD5來匯入MD5這個模組,然後就可以使用該模組相應的api了。

pycrypto使用例項

例項1: 使用SHA256演算法獲取一段資料的摘要資訊

from Crypto.Hash import SHA256

hash = SHA256.new()
hash.update('Hello, World!')
digest = hash.hexdigest()
print(digest)

輸出結果:

dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f

例項2: 使用AES演算法加密,解密一段資料

from Crypto.Cipher import AES

# 加密與解密所使用的金鑰,長度必須是16的倍數
secret_key = "ThisIs SecretKey" 
# 要加密的明文資料,長度必須是16的倍數
plain_data = "Hello, World123!"
# IV引數,長度必須是16的倍數
iv_param = 'This is an IV456'

# 資料加密
aes1 = AES.new(secret_key, AES.MODE_CBC, iv_param)
cipher_data = aes1.encrypt(plain_data)
print('cipher data:', cipher_data)

# 資料解密
aes2 = AES.new(secret_key, AES.MODE_CBC, 'This is an IV456')
plain_data2 = aes2.decrypt(cipher_data)  # 解密後的明文資料
print('plain text:', plain_data2)

輸出結果:

('cipher data\xef\xbc\x9a', '\xcb\x7fd\x03\x12T,\xbe\x91\xac\x1a\xd5\xaa\xe6P\x9a')
('plain text\xef\xbc\x9a', 'Hello, World123!')

例項3: 隨機數操作

from Crypto.Random import random

print('random.randint: ', random.randint(10, 20))
print('random.randrange: ', random.randrange(10, 20, 2))
print('random.randint: ', random.getrandbits(3))
print('random.choice: ', random.choice([1, 2, 3, 4, 5]))
print('random.sample: ', random.sample([1, 2, 3, 4, 5], 3))
list = [1, 2, 3, 4, 5]
random.shuffle(list)
print('random.shuffle: ', list)

輸出結果:

('random.randint: ', 10L)
('random.randrange: ', 10L)
('random.randint: ', 5L)
('random.choice: ', 5)
('random.sample: ', [5, 4, 2])
('random.shuffle: ', [5, 2, 1, 3, 4])

例項4: 使用RSA演算法生成金鑰對兒

生成祕鑰對:

from Crypto import Random
from Crypto.PublicKey import RSA

# 獲取一個偽隨機數生成器
random_generator = Random.new().read
# 獲取一個rsa演算法對應的金鑰對生成器例項
rsa = RSA.generate(1024, random_generator)

# 生成私鑰並儲存
private_pem = rsa.exportKey()
with open('rsa.key', 'w') as f:
    f.write(private_pem)

# 生成公鑰並儲存
public_pem = rsa.publickey().exportKey()
with open('rsa.pub', 'w') as f:
    f.write(public_pem)

私鑰檔案rsa.key的內容為:

-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCo7vV5xSzEdQeFq9n5MIWgIuLTBHuutZlFv+Ed8fIk3yC4So/d
y1f64iuYFcDeNU7eVGqTSkHmAl4AihDXoaH6hxohrcX0bCg0j+VoQMe2zID7MzcE
d50FhJbuG6JsWtYzLUYs7/cQ3urZYwB4PEVa0WxQj2aXUMsxp6vl1CgB4QIDAQAB
AoGAS/I5y4e4S43tVsvej6efu1FTtdhDHlUn1fKgawz1dlwVYqSqruSW5gQ94v6M
mZlPnqZGz3bHz3bq+cUYM0jH/5Tygz4a+dosziRCUbjMsFePbJ4nvGC/1hwQweCm
+7sxog4sw91FrOfAg/iCcoeho0DghDolH9+zzwRYPIWUyUECQQDFGe+qccGwL9cU
v+GmZxtF8GkRL7YrXI7cvnZhnZZ7TANjxlYukLGEpiFGIDd0Aky1QhkK18L8DTO4
+iGXTpgJAkEA22o03/1IqeRBofbkkDmndArHNUnmv5pyVFaLKPoVgA4A1YsvqxUL
DK6RwFGONUMknBWY59EDKCUdIf3CsVIhGQJAJKDMRB19xBMv4iBCe9z/WYDy1YnL
TcWWmvkeIMfbVjBrFNif3WlwQ9lnp5OHGpzuymRtKPGtv49ohECfi3HEmQJAPI+n
AoAdk07+Up8b3TccoinrbCj2uMH/dongpTHJx2uWDVr6kEUhpKF2d1fLYaYjr7VC
XBHTxjvgO6aYG2to2QJBAIzDugOSTeQFpidCoewfa0XX4guF+WRf8wzyBC/XE6TY
3cIY05sjbpfiVwW/Cb8Z2ia8EgBTGN8HSIFOUQ2jRl4=
-----END RSA PRIVATE KEY-----

公鑰檔案rsa.pub的內容為:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCo7vV5xSzEdQeFq9n5MIWgIuLT
BHuutZlFv+Ed8fIk3yC4So/dy1f64iuYFcDeNU7eVGqTSkHmAl4AihDXoaH6hxoh
rcX0bCg0j+VoQMe2zID7MzcEd50FhJbuG6JsWtYzLUYs7/cQ3urZYwB4PEVa0WxQ
j2aXUMsxp6vl1CgB4QIDAQAB
-----END PUBLIC KEY-----

例項5: 公鑰加密演算法的實現

前面說過,公鑰加密演算法是由Crypto.Cipher子包下的PKCS1_v1_5.py或PKCS1_OAEP.py模組以已經存在的金鑰對兒為金鑰來實現的,現在常用的是PKCS1_v1_5。另外,我們前面提到過,使用對方的公鑰加密,使用對方的私鑰解密才能保證資料的機密性,因此這裡以上面生成的公鑰進行加密資料,以上面生成的私鑰解密資料:

from Crypto import Random
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5 as Cipher_PKCS1_v1_5
import base64

# 資料加密
message = "This is a plain text."
with open('rsa.pub', 'r') as f:
    public_key = f.read()
    rsa_key_obj = RSA.importKey(public_key)
    cipher_obj = Cipher_PKCS1_v1_5.new(rsa_key_obj)
    cipher_text = base64.b64encode(cipher_obj.encrypt(message))
    print('cipher test: ', cipher_text)

# 資料解密
with open('rsa.key', 'r') as f:
    private_key = f.read()
    rsa_key_obj = RSA.importKey(private_key)
    cipher_obj = Cipher_PKCS1_v1_5.new(rsa_key_obj)
    random_generator = Random.new().read
    plain_text = cipher_obj.decrypt(base64.b64decode(cipher_text), random_generator)
    print('plain text: ', plain_text)

輸出結果:

('cipher test: ', 'oq1sOSz4lS9PgrKmiwuAHs7iUhmWMvWdEbXLTOdhGtyIAr6xwmjtnBNpuvMVIM2Mz/O/xVzPu5L8nzUVW2THKpQinNwC7JWF0wnxrTHwKrmfXIIxxibQJS02obxkoEeqrjRo0b8V7yktYIV3ig2SlU3yjcr+lOFmRX+h6dE2TAI=')
('plain text: ', 'This is a plain text.')

例項6: 資料簽名與簽名驗證的實現

同樣,簽名與驗證相關演算法的功能是由Crypto.Signature子包下的PKCS1_v1_5.py和PKCS1_PASS.py以這個金鑰對而為金鑰來實現的。資料簽名的目的是為了防止別人篡改傳送人的原始資料,其原理是:

  • 1)先以單向加密方式通過某種雜湊演算法(如MD5,SHA1等)對要傳送的資料生成摘要資訊(資料指紋);
  • 2)然後傳送方用自己金鑰對兒中的私鑰對這個摘要資訊進行加密;
  • 3)資料接收方用傳送的公鑰對加密後的摘要資訊進行解密,得到資料摘要的明文A;
  • 4)資料接收方再通過相同的雜湊演算法計算得到資料摘要資訊B;
  • 5)資料接收方對比資料摘要A與資料摘要B,如果兩者一致說明資料沒有被篡改過。
from Crypto.Hash import SHA
from Crypto.Signature import PKCS1_v1_5 as Signature_PKCS1_v1_5
message = "This is the message to send."
# 資料簽名
with open('rsa.key', 'r') as f:
    private_key = f.read()
    rsa_key_obj = RSA.importKey(private_key)
    signer = Signature_PKCS1_v1_5.new(rsa_key_obj)
    digest = SHA.new()
    digest.update(message)
    signature = base64.b64encode(signer.sign(digest))
    print('signature text: ', signature)

# 驗證簽名
with open('rsa.pub', 'r') as f:
    public_key = f.read()
    rsa_key_obj = RSA.importKey(public_key)
    signer = Signature_PKCS1_v1_5.new(rsa_key_obj)
    digest = SHA.new(message)
    is_ok = signer.verify(digest, base64.b64decode(signature))
    print('is ok: ', is_ok)

輸出結果:

('signature text: ', 'Bb4gvPU9Ji63kk3SSTiAVLctDbdb91DQuQKecbTcO2Jvpwbr7fr9sKZO+vZ8LIuSOdJkhbGX6swsSNwDI/CoT0xCdjiasfySPgsLyTcSWLyy9P7SrDuveH1ABUR/oYisvT1wFsScu0NMOBR8sLpboPk2DiW6n400jZq7t09xUyc=')
('is ok: ', True)

上面這幾個關於pycrpto的使用例項來自這裡

七、總結


上面講了很多內容,現在我們簡單總結下:

  • 資料加密方式大體分為3類:單向加密、對稱加密 和 公鑰加密(非對稱加密)。
  • 這3類加密方式都各自包含不同的加密演算法,如單向加密方式中包含MD5、SHA1、SHA256等,這些演算法又稱為“雜湊演算法”或“雜湊演算法”或“資料摘要演算法”
  • Python內建的hashlib和hmac只提供了單向加密的各種演算法實現,如果要做對稱加密或者公鑰加密操作需要安裝第三方擴充套件模組,常用的是pycrypto模組。另外,hmac允許在使用雜湊演算法計算資料摘要時使用一個金鑰。
  • 隨機數操作可以通過三個模組來做,Python內建的random模組和secrets模組(Python 3.6中才可用),還可以通過pycrypto模組中的Crypto.Random子包中的模組來完成。
  • base64只適合編碼小段資料,且不能用於資料加密(演算法是公開的,且沒有金鑰,所有人都可以解碼)
  • pycrypto是一個加密演算法庫,幾乎所有的加密演算法都可以在它裡面找到相應的實現模組。

八、參考文件


  • https://docs.python.org/3/library/hashlib.html
  • https://docs.python.org/3/library/hmac.html
  • https://docs.python.org/3/library/random.html
  • https://docs.python.org/3/library/secrets.html
  • https://www.dlitz.net/software/pycrypto/
  • 相關原始碼的註釋資訊