(02)Python密码库Cryptography探究学习---深入理解Fernet

(02)Python密码库Cryptography探究学习---深入理解Fernet

本节对Fernet进行深入介绍,使读者能够理解cryptographic recipes的含义,能在实践中正确使用密码学的相关算法。

Fernet不仅仅是个对称密码算法,它是密码学原语的集合应用,主要有3个特点:(1)使用了符合密码安全的随机数密钥。(2)提供了加密功能:使用了128位密钥的AES加密算法,对数据 PKCS7 填充后,以AES-CBC模式进行加密。(3)提供了认证的功能,采用Sha256的哈希函数,产生消息认证码(HMAC)。

大家先通过github.com/pyca/cryptog,结合源代码,看看下面的详细介绍:

一、使用了密码安全的随机数密钥

对于密码算法而言,密钥应该是符合密码学安全的随机数。只有这样才能保证密码算法的安全性,否则容易遭受攻击。在很多应用中,密钥并不随机,不能符合密码学的要求,存在着漏洞。

那么如何得到真正的随机数呢?真正的随机数是通过物理过程得到的,比如抛硬币、掷骰子,布朗运动,量子效应,放射性衰变,振荡器采样等。

通过计算法的方式得到真正的随机数,是几乎不可能的。冯.诺依曼说过:“任何人考虑用数学的方法产生随机数肯定是不合情理的”。一般而言,大家经常用到的随机数生成函数,并不能真正的产生随机数,也不能用在密码学算法中,比如C语言中的rand()函数,它通过如下的函数计算而来。

能密码算法中使用的随机数,如何产生呢?建议采用操作系统中的随机数产生器来产生,在Unix操作系统中使用 /dev/urandom,而在Windows操作系统中使用CryptGenRandom 生成。由于我们使用Python语言,只需要使用如下的方法,即

>>> import os
>>> salt = os.urandom(32)

我们通过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):

        return base64.urlsafe_b64encode(os.urandom(32))

通过os.urandom(32)产生符合密码学要求的随机数,而后进行base64编码。

(2)自己设定一个密码

如下是个例子:

>>> import base64
>>> import os
>>> from cryptography.fernet import Fernet
>>> from cryptography.hazmat.backends import default_backend
>>> from cryptography.hazmat.primitives import hashes
>>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
>>> password = b"password"
>>> salt = os.urandom(16)
>>> kdf = PBKDF2HMAC(
...     algorithm=hashes.SHA256(),
...     length=32,
...     salt=salt,
...     iterations=100000,
...     backend=default_backend()
... )
>>> key = base64.urlsafe_b64encode(kdf.derive(password))
>>> f = Fernet(key)
>>> token = f.encrypt(b"Secret message!")
>>> token
'...'
>>> f.decrypt(token)
'Secret message!

设定一个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):
        if not isinstance(data, bytes):
            raise TypeError("data must be bytes.")

        padder = padding.PKCS7(algorithms.AES.block_size).padder()
        padded_data = padder.update(data) + padder.finalize()
        encryptor = Cipher(
            algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend
        ).encryptor()
        ciphertext = encryptor.update(padded_data) + encryptor.finalize()

        basic_parts = (
            b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext
        )

        h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
        h.update(basic_parts)
        hmac = h.finalize()
        return base64.urlsafe_b64encode(basic_parts + hmac)

(1)数据填充

 ......
   padder = padding.PKCS7(algorithms.AES.block_size).padder()
            padded_data = padder.update(data) + padder.finalize()
......

它使用Cryptography提供的padding函数,对明文数据进行填充。


明文消息的长度是随机的,按128比特分组时,最后一组消息长度可能不足128比特。此时要填充一些数字凑够128比特。为了让接收者能区分正确消息与填充的无用数字,需要加上指示信息。通常数据尾部、填充字符和填充指示符作为一组明文进行加密。

(2)使用了AES-CBC模式进行加密

......
        encryptor = Cipher(
            algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend
        ).encryptor()
        ciphertext = encryptor.update(padded_data) + encryptor.finalize()
......

在加密开始前,选择一个初始化向量IV,先于明文分组异或,再开始加密流程;加密每一分组后,该分组的密文与下一分组的明文异或,接着继续加密流程,并如此反复。初始化向量(IV)没有实际意义,在第一次计算时使用,当然在解密时同样需要IV。该模式不容易被主动攻击,适合传输长度长的报文,是SSL、IPSec的标准。


(3)产生认证码

        basic_parts = (
            b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext
        )

        h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
        h.update(basic_parts)
        hmac = h.finalize()
        return base64.urlsafe_b64encode(basic_parts + hmac)

basic_parts以“\x80”开头,包含现有时间、IV和密文。接着使用SHA256()哈希函数产生HMAC认证码。HMAC是使用hash算法构造的含有密钥散列函数算法,其中哈希算法采用了SHA256,密钥是self._signing_key(32位key中的前16位),产生固定长度的认证码,以防止密文在传输过程中被篡改。

最后将basic_parts和消息认证码hmac一同返回。

三、小结

本文对Fernet进行了深入的介绍,希望读者能够理解密码算法中使用的随机数是如何产生的,并理解对明文的加密,要有填充、加密的步骤,且为了防止密文传输过程中的篡改,要使用HMAC算法,从而对cryptographic recipes有更加深刻的认识。


由于部分读者对一些密码学原语并不十分清楚,这里知道大概的含义即可,有时间我会进行更加详细的介绍。

编辑于 2017-02-17