(02)Python密碼庫Cryptography探究學習---深入理解Fernet
Fernet不僅僅是個對稱密碼演算法,它是密碼學原語的集合應用,主要有3個特點:(1)使用了符合密碼安全的隨機數密鑰。(2)提供了加密功能:使用了128位密鑰的AES加密演算法,對數據 PKCS7 填充後,以AES-CBC模式進行加密。(3)提供了認證的功能,採用Sha256的哈希函數,產生消息認證碼(HMAC)。
大家先通過https://github.com/pyca/cryptography/blob/master/src/cryptography/fernet.py,結合源代碼,看看下面的詳細介紹:
一、使用了密碼安全的隨機數密鑰
對於密碼演算法而言,密鑰應該是符合密碼學安全的隨機數。只有這樣才能保證密碼演算法的安全性,否則容易遭受攻擊。在很多應用中,密鑰並不隨機,不能符合密碼學的要求,存在著漏洞。
那麼如何得到真正的隨機數呢?真正的隨機數是通過物理過程得到的,比如拋硬幣、擲骰子,布朗運動,量子效應,放射性衰變,振蕩器採樣等。
通過計演算法的方式得到真正的隨機數,是幾乎不可能的。馮.諾依曼說過:「任何人考慮用數學的方法產生隨機數肯定是不合情理的」。一般而言,大家經常用到的隨機數生成函數,並不能真正的產生隨機數,也不能用在密碼學演算法中,比如C語言中的rand()函數,它通過如下的函數計算而來。能密碼演算法中使用的隨機數,如何產生呢?建議採用操作系統中的隨機數產生器來產生,在Unix操作系統中使用 /dev/urandom,而在Windows操作系統中使用CryptGenRandom 生成。由於我們使用Python語言,只需要使用如下的方法,即
>>> import osn>>> salt = os.urandom(32)n
我們通過15.1. os - Miscellaneous operating system interfaces - Python 2.7.13 documentation,來查看os.urandom(n)的說明。
Return a string of n random bytes suitable for cryptographic use.
This function returns random bytes from an OS-specific randomness source. The returned data should be unpredictable enough for cryptographic applications, though its exact quality depends on the OS implementation.
使用Fernet時,有兩種方式產生密鑰:
(1)讓Fernet直接產生
這種方法調用key = Fernet.generate_key()。
def generate_key(cls):nn return base64.urlsafe_b64encode(os.urandom(32))n
通過os.urandom(32)產生符合密碼學要求的隨機數,而後進行base64編碼。
(2)自己設定一個密碼
如下是個例子:
>>> import base64n>>> import osn>>> from cryptography.fernet import Fernetn>>> from cryptography.hazmat.backends import default_backendn>>> from cryptography.hazmat.primitives import hashesn>>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMACn>>> password = b"password"n>>> salt = os.urandom(16)n>>> kdf = PBKDF2HMAC(n... algorithm=hashes.SHA256(),n... length=32,n... salt=salt,n... iterations=100000,n... backend=default_backend()n... )n>>> key = base64.urlsafe_b64encode(kdf.derive(password))n>>> f = Fernet(key)n>>> token = f.encrypt(b"Secret message!")n>>> tokenn...n>>> f.decrypt(token)nSecret message!n
設定一個password,接著使用PBKDF2HMAC。它是個密鑰推導函數,通過多次對salt進行hash運算從而產生密鑰。該方法被美國政府標準化,並得到廣泛採用。以後有時間再進行更加詳細的介紹。
通過密鑰推導函數,輸出32位隨機數,使用key = base64.urlsafe_b64encode(kdf.derive(password))產生Fernet使用的密鑰。
二、加密和認證功能
這兩個功能主要通過 encrypt和_encrypt_from_parts函數實現。
def _encrypt_from_parts(self, data, current_time, iv):n if not isinstance(data, bytes):n raise TypeError("data must be bytes.")nn padder = padding.PKCS7(algorithms.AES.block_size).padder()n padded_data = padder.update(data) + padder.finalize()n encryptor = Cipher(n algorithms.AES(self._encryption_key), modes.CBC(iv), self._backendn ).encryptor()n ciphertext = encryptor.update(padded_data) + encryptor.finalize()nn basic_parts = (n b"x80" + struct.pack(">Q", current_time) + iv + ciphertextn )nn h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)n h.update(basic_parts)n hmac = h.finalize()n return base64.urlsafe_b64encode(basic_parts + hmac)n
(1)數據填充
......n padder = padding.PKCS7(algorithms.AES.block_size).padder()n padded_data = padder.update(data) + padder.finalize()n......n
它使用Cryptography提供的padding函數,對明文數據進行填充。
明文消息的長度是隨機的,按128比特分組時,最後一組消息長度可能不足128比特。此時要填充一些數字湊夠128比特。為了讓接收者能區分正確消息與填充的無用數字,需要加上指示信息。通常數據尾部、填充字元和填充指示符作為一組明文進行加密。n(2)使用了AES-CBC模式進行加密
......n encryptor = Cipher(n algorithms.AES(self._encryption_key), modes.CBC(iv), self._backendn ).encryptor()n ciphertext = encryptor.update(padded_data) + encryptor.finalize()n......n
(3)產生認證碼
basic_parts = (n b"x80" + struct.pack(">Q", current_time) + iv + ciphertextn )nn h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)n h.update(basic_parts)n hmac = h.finalize()n return base64.urlsafe_b64encode(basic_parts + hmac)n
basic_parts以「x80」開頭,包含現有時間、IV和密文。接著使用SHA256()哈希函數產生HMAC認證碼。HMAC是使用hash演算法構造的含有密鑰散列函數演算法,其中哈希演算法採用了SHA256,密鑰是self._signing_key(32位key中的前16位),產生固定長度的認證碼,以防止密文在傳輸過程中被篡改。
最後將basic_parts和消息認證碼hmac一同返回。
三、小結
本文對Fernet進行了深入的介紹,希望讀者能夠理解密碼演算法中使用的隨機數是如何產生的,並理解對明文的加密,要有填充、加密的步驟,且為了防止密文傳輸過程中的篡改,要使用HMAC演算法,從而對cryptographic recipes有更加深刻的認識。
由於部分讀者對一些密碼學原語並不十分清楚,這裡知道大概的含義即可,有時間我會進行更加詳細的介紹。
推薦閱讀:
※這串字元什麼意思?
※如果我隨意打出亂碼,可不可以用密碼學解出有意義的信息?
※用密碼學玩暗軍棋 -- 閑聊多方計算
※將大於2的正整數分解成2個因數(不是質因數),並求其各結果間兩因數最小非負數差,這樣的數有研究的意義嗎?
※從 Shor 演算法到格密碼學