1. 程式人生 > >數據加密

數據加密

返回結果 python版本 傳遞 base64算法 ascii碼 cfi ndb block 生成隨機數

  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是一個加密算法庫,幾乎所有的加密算法都可以在它裏面找到相應的實現模塊。

數據加密