python加密

长期以来,python 处理各种加密算法都没有一个统一好用的库,但是最近发现了 Cryptography 这个库,基本上统一了各个算法的使用方式,而且主流的加密算法都支持,以后可以尽量使用这个库处理加密问题。

关于密码学的底层知识有非常多需要掌握,如果要深入学习建议看一下Crypto 101和 TheCryptopals Crypto Challenges这两个在线教程。

1、快速开始

如果只是想通过 python 快速进行加密解密的操作,不关注底层的实现算法,可以直接调用 Cryptography 提供的高层 API。

from cryptography.fernet import Fernet
key = Fernet.generate_key()
# encrypt
cipher = Fernet(key)
ciphertext = cipher.encrypt(b"wyz")
# decrypt
cipher = Fernet(key)
plaintext = cipher.decrypt(ciphertext)

解密操作允许你控制密文的失效时间,比如你在 9:00 给了别人一个 token,这个 token 必须在 10秒钟之内被解密,超出这个时间 token 会失效。

import time
ciphertext = cipher.encrypt(b"wyz")
time.sleep(11)
# 测试密文会失效
plaintext = cipher.decrypt(ciphertext, ttl=10)

你可以通过密文查看加密时间:

cipher.extract_timestamp(ciphertext)

2、AES

AES 是目前推荐的对称加密方式,使用这种加密方式需要准备以下数据:

  • key,秘钥长度可以是128, 192 或者 256 位(bits)。
  • mode,推荐使用 CBC
  • iv,初始向量, 使用 CBC 方式必须提供
  • padding, 补位方位,推荐用 PKCS7
from cryptography.fernet import (
    Cipher, algorithms, modes, padding
)
key = os.urandom(16)
iv = os.urandom(16)
cipher = Cipher(
    algorithm=algorithms.AES(key),
    mode=modes.CBC(iv)
)
encryptor = cipher.encryptor()
padder = padding.PKCS7(128).padder()
padder_data = padder.update(b'wyz') + padder.finalize()
cipher_text = encryptor.update(padder_data) + encryptor.finalize()

可以封装成函数,以后直接使用:

import os
from cryptography.fernet import (
    Cipher, algorithms, padding, modes
)
def aes_encrypt(plain_text, key, iv, mode=None, pad=None):
    if not mode:
        mode = modes.CBC
    if not pad:
        pad = padding.PKCS7
    cipher = Cipher(
        algorithm=algorithms.AES(key),
        mode=mode(iv)
    )
    encryptor = cipher.encryptor()
    padder = pad(len(key) * 8).padder()
    padder_data = padder.update(plain_text) + padder.finalize()
    cipher_text = encryptor.update(padder_data) + encryptor.finalize()
    return cipher_text
key = os.urandom(16)
ct = aes_encrypt(b'wyz', key, key)

解码则是相反的:

decryptor = cipher.decryptor()
padder_data = decryptor.update(cipher_text) + decryptor.finalize()
unpadder = padding.PKCS7(128).unpadder()
plain_text = unpadder.update(padder_data) + unpadder.finalize()

也可以封装成函数模板,以后直接使用:

import os
from cryptography.fernet import (
    Cipher, algorithms, padding, modes
)
def aes_decrypt(cipher_text, key, iv, mode=None, pad=None):
    if not mode:
        mode = modes.CBC
    if not pad:
        pad = padding.PKCS7
    cipher = Cipher(
        algorithm=algorithms.AES(key),
        mode=mode(iv)
    )
    encryptor = cipher.decryptor()
    
    padder_data = encryptor.update(cipher_text) + encryptor.finalize()
    unpadder = pad(len(key) * 8).unpadder()
    plain_text = unpadder.update(padder_data) + unpadder.finalize()
    return plain_text

3、DES 和 3DES

DES 的密钥过短,现在已经不推荐使用了,所以这个库没有 DES 的算法封装,如果你需要使用这种算法,推荐用 pyDes 这个库。 DES 需要提供以下数据:

  • key 秘钥,56, 112, 或者 168 位(bits)
  • mode 加密方式,可以是 ECB 和 CBC
  • IV 初始向量,CBC 方式必须提供,加密的 IV 和解密的 IV 必须一致。
  • pad 补位字符串,如果补位方式是 Normal, 必须提供,如果补位方式是 PAD_PKCS5, 不需要提供。
  • padmode 补位方式,可以是 Normal 和 PAD_PKCS5,建议 PAD_PKCS5

这个库使用 DES 加密,默认使用 ECB 方式,默认补位方式为 PKCS5 写法比较简单,得到的是一个 bytes 类型。

import pyDes
key = 'mysecret'
cipher = pyDes.des(key, padmode=pyDes.PAD_PKCS5)
encrypted = cipher.encrypt('abc')
# >>> b'v\x02J\x867\x8fQ\\'

解密的 key, mode, IV, pad, padmode 都和加密时保持一致:

cipher = pyDes.des(key, padmode=pyDes.PAD_PKCS5)
decrypted = cipher.decrypt(encrypted)
# >>> b'abc'

详细版加密

import pyDes
key = 'mysecret'
cipher = pyDes.des(
    key, 
    mode=pyDes.CBC, # 模型
    IV='\0\0\0\0\0\0\0\0', # IV
    pad= ' ', # 空格补位
    padmode=pyDes.PAD_NORMAL
)
encrypted = cipher.encrypt('abc')
# >>> b'v\x02J\x867\x8fQ\\'

详细版解密

cipher = pyDes.des(
key,
mode=pyDes.CBC, # 模型
IV='\0\0\0\0\0\0\0\0', # IV
pad= ' ', # 空格补位
padmode=pyDes.PAD_NORMAL
)
decrypted = cipher.decrypt(encrypted)
# >>> b'abc'

3DES 在 DES 的基础上增加了密钥长度,秘钥可以是 64, 128 或者 192 位(bits), 3DES 目前也不推荐使用,因为他的速度非常慢。

import pyDes
import os
key = os.urandom(16)
cipher = pyDes.triple_des(key, padmode=pyDes.PAD_PKCS5)
encrypted = cipher.encrypt('abc')
# >>> b'DGFx\xc6\xb4\xad@'
cipher = pyDes.triple_des(key, padmode=pyDes.PAD_PKCS5)
decrypted = cipher.decrypt(encrypted)
# >>> b'abc'

在 cryptography 中的使用和 AES 基本一致,只需要把算法名称改一下就可以了。

from cryptography.fernet import (
    Cipher, algorithms, modes, padding
)
key = os.urandom(16)
iv = os.urandom(8)
cipher = Cipher(
    algorithm=algorithms.TripleDES(key),
    mode=modes.CBC(iv)
)
encryptor = cipher.encryptor()
padder = padding.PKCS7(128).padder()
padder_data = padder.update(b'wyz') + padder.finalize()
cipher_text = encryptor.update(padder_data) + encryptor.finalize()

4、hash

from cryptography.fernet import hashes
hasher = hashes.Hash(hashes.SHA256())
hasher.update(b'wyz')
hasher.finalize()

5、RSA

对称加密算法的密钥通常只是一个随机系列的字节,而RSA的密钥对是具有特定数学属性的复杂内部结构,需要通过特定的方式生成。通常来说需要提供两个参数:参数 public exponent 表示密钥生成的一个数学属性,通常就是 65537 这个特定的数字;参数 key size 表示生成的密钥长度,一般为 2048 或者 4096。1024 长度的已经很少使用了。

使用下面代码可以生成密钥对:

from cryptography.hazmat.primitives.asymmetric import (
    rsa, padding
)
from cryptography.hazmat.primitives import hashes
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048
)
public_key = private_key.public_key()
print(private_key)
print(public_key)

当密钥对生成以后,一般会保存到 pem 这样的文件中,或者会从文件中加载数据。

from cryptography.hazmat.primitives import serialization
pem = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
 encryption_algorithm=serialization.BestAvailableEncryption(b'mypassword')
)
with open('/path/to/key.pem', 'rb') as key_file:
    key = serialization.load_pem_private_key(
        key_file.read(),
        password=b'mypassword')
    )

6、RSA 加密和解密

有了密钥对之后,就可以通过 RSA 算法进行加解密了。使用公钥加密,提供 padding 补位方式,推荐使用 OAEP, 也可以使用 PKCS1v15。

ciphertext = public_key.encrypt(
    b"hello world",
    padding=padding.OAEP(
        mgf=padding.MGF1(hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)

使用私钥解密:

plaintext = private_key.decrypt(
    ciphertext,
    padding=padding.OAEP(
        mgf=padding.MGF1(hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)
print(plaintext)
b'hello world'

7、RSA 签名

RSA 算法除了用来加密数据,还可以使用私钥生成签名传给消费者,消费者使用公钥和签名对数据进行校验,看是否为私钥持有者发送的数据,验证数据合法性。

使用私钥签名:

message = b"A message from server"
signature = private_key.sign(
    message,
    padding=padding.PSS(
        mgf=padding.MGF1(hashes.SHA256()),
        salt_length=padding.PSS.MAX_LENGTH
    ),
    algorithm=hashes.SHA256()
)

使用公钥验证数据,如果数据生成的签名不合法,则抛出 InvalidSignature 错误。

public_key.verify(
    signature,
    message,
    padding=padding.PSS(
        mgf=padding.MGF1(hashes.SHA256()),
        salt_length=padding.PSS.MAX_LENGTH
    ),
    algorithm=hashes.SHA256()
)

签名的时候,如果数据过大,可以把数据分片,对每个部分单独执行 hash, 再把hash 后的对象传给 Prehashed.

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import (
    padding, rsa, utils
)
hasher = hashes.Hash(hashes.SHA256())
hasher.update(b"part 1")
hasher.update(b"part 2")
digest = hasher.finalize()
signature = private_key.sign(
    digest,
    padding=padding.PSS(
        mgf=padding.MGF1(hashes.SHA256()),
        salt_length=padding.PSS.MAX_LENGTH
    ),
    algorithm=utils.Prehashed(hashes.SHA256())
)
public_key.verify(
    signature,
    digest,
    padding=padding.PSS(
        mgf=padding.MGF1(hashes.SHA256()),
        salt_length=padding.PSS.MAX_LENGTH
    ),
    algorithm=utils.Prehashed(hashes.SHA256())
)