文章目录
安装方法和模块介绍
PyCryptodome 是一个包含低级加密原语的独立 Python 包。
它支持 Python 2.7、Python 3.7+ 和 PyPy。
安装过程取决于你希望如何使用该库:
作为 PyCrypto 的替代品
如果你想将 PyCryptodome 作为旧版 PyCrypto 库的即插即用替代品,可以通过以下方式安装:
pip install pycryptodome
在这种情况下,所有模块都会安装在 Crypto
包下。
注意:PyCrypto
和 PyCryptodome
不能同时安装,因为它们会相互干扰。因此,仅当你确定整个应用程序都部署在 virtualenv
中时,才建议使用此选项。
作为独立库
如果你想将 PyCryptodome 作为一个独立于旧 PyCrypto 的库,可以使用以下方式安装:
pip install pycryptodomex
在这种情况下,所有模块都会安装在 Cryptodome
包下。PyCrypto
和 PyCryptodome
可以共存。
子包介绍
Crypto.Cipher
PyCryptodome 是一个功能强大的密码学库,它提供了多种加密算法的实现。Crypto.Cipher
模块是其中一个核心部分,用于处理对称加密。
对称加密基本概念
对称加密是指加密和解密使用相同密钥的加密方式。常见的对称加密算法有 AES、DES、3DES 等。Crypto.Cipher
模块为这些算法提供了统一的接口。
使用 Crypto.Cipher
的通用步骤
使用 Crypto.Cipher
进行对称加密通常遵循以下几个步骤:
- 导入模块: 根据你使用的算法导入相应的模块,例如
from Crypto.Cipher import AES
。 - 生成密钥: 密钥是加密和解密的关键。密钥的长度取决于所选的算法。
- 创建密码器对象: 使用密钥和其他参数(如模式、IV)创建密码器(cipher)对象。
- 加密: 使用密码器对象的
encrypt()
方法对数据进行加密。 - 解密: 使用密码器对象的
decrypt()
方法对密文进行解密。
主要函数和用法示例
1. AES (高级加密标准)
AES 是目前最常用的对称加密算法之一,支持 128、192 和 256 位的密钥长度。
常用模式:
- ECB (电子密码本模式): 最简单的模式,每个块独立加密。不推荐用于加密敏感数据,因为它无法隐藏数据模式。
- CBC (密码分组链接模式): 引入了初始化向量(IV),增加了安全性。每个明文块在加密前会与前一个密文块进行异或操作。
- CFB (密文反馈模式): 可以处理任意位数的输入,适用于流式数据加密。
- OFB (输出反馈模式): 类似于 CFB,也是流模式,适用于有噪声的信道。
- CTR (计数器模式): 也可以处理流数据,且支持并行处理,性能较好。
- GCM (伽罗瓦/计数器模式): 是一种带认证的加密模式,除了加密数据外,还能提供数据的完整性和认证。推荐使用。
1.1 AES - ECB 模式 (不推荐用于实际应用)
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
# 1. 生成密钥 (AES 要求密钥长度为 16, 24 或 32 字节)
key = get_random_bytes(16) # 16 字节 = 128 位
# 2. 创建密码器对象
cipher = AES.new(key, AES.MODE_ECB)
# 3. 准备数据 (ECB 模式要求数据是块大小的倍数,AES 块大小为 16 字节)
data = b"This is a secret message."
# 填充数据使其成为 16 字节的倍数
# 这里使用简单的 PKCS7 填充,PyCryptodome 也提供了专门的填充模块
pad_len = AES.block_size - len(data) % AES.block_size
padded_data = data + bytes([pad_len]) * pad_len
# 4. 加密
ciphertext = cipher.encrypt(padded_data)
print(f"原始数据: {data}")
print(f"加密后数据 (ECB): {ciphertext.hex()}")
# 5. 解密
decipher = AES.new(key, AES.MODE_ECB) # 解密时也需要创建新的 cipher 对象
decrypted_padded_data = decipher.decrypt(ciphertext)
# 去除填充
unpadded_data = decrypted_padded_data[:-decrypted_padded_data[-1]]
print(f"解密后数据 (ECB): {unpadded_data.decode('utf-8')}")
1.2 AES - CBC 模式
CBC 模式需要一个初始化向量 (IV),它每次加密时都应该是一个新的、随机的值,但不需要保密。
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
# 1. 生成密钥
key = get_random_bytes(16)
# 2. 生成随机 IV (必须是 16 字节)
iv = get_random_bytes(AES.block_size)
# 3. 创建密码器对象
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
# 4. 准备数据并填充
data = b"Hello, CBC mode is more secure than ECB."
padded_data = pad(data, AES.block_size)
# 5. 加密
ciphertext = cipher.encrypt(padded_data)
print(f"\n原始数据: {data}")
print(f"IV: {iv.hex()}")
print(f"加密后数据 (CBC): {ciphertext.hex()}")
# 6. 解密
# 解密时需要相同的密钥和 IV
decipher = AES.new(key, AES.MODE_CBC, iv=iv)
decrypted_padded_data = decipher.decrypt(ciphertext)
# 去除填充
unpadded_data = unpad(decrypted_padded_data, AES.block_size)
print(f"解密后数据 (CBC): {unpadded_data.decode('utf-8')}")
1.3 AES - GCM 模式 (推荐)
GCM 模式提供认证加密,可以同时保证数据的机密性、完整性和认证性。它需要一个随机数 nonce
和一个可选的 associated_data
(额外认证数据,不加密但参与认证)。
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
# 1. 生成密钥
key = get_random_bytes(16)
# 2. 创建密码器对象
# GCM 模式会自动生成一个 nonce
cipher = AES.new(key, AES.MODE_GCM)
# 3. 额外认证数据 (可选,可以为空)
associated_data = b"This is authenticated but not encrypted data."
# 4. 准备数据
data = b"This is a secret message for GCM mode."
# 5. 加密
ciphertext, tag = cipher.encrypt_and_digest(data, associated_data)
# cipher.nonce 是 GCM 模式自动生成的随机数
print(f"\n原始数据: {data}")
print(f"Nonce: {cipher.nonce.hex()}")
print(f"额外认证数据: {associated_data.decode('utf-8')}")
print(f"加密后数据 (GCM): {ciphertext.hex()}")
print(f"认证标签 (Tag): {tag.hex()}")
# 6. 解密和认证
# 解密时需要相同的密钥、nonce 和 associated_data
decipher = AES.new(key, AES.MODE_GCM, nonce=cipher.nonce)
try:
decrypted_data = decipher.decrypt_and_verify(ciphertext, tag, associated_data)
print(f"解密后数据 (GCM): {decrypted_data.decode('utf-8')}")
except ValueError:
print("认证失败或数据被篡改!")
# 尝试篡改数据或标签
# try:
# decipher_tampered = AES.new(key, AES.MODE_GCM, nonce=cipher.nonce)
# decrypted_data_tampered = decipher_tampered.decrypt_and_verify(ciphertext + b'x', tag, associated_data)
# print("篡改后解密 (不应成功):", decrypted_data_tampered.decode('utf-8'))
# except ValueError:
# print("认证失败(数据篡改)")
2. DES 和 3DES (Data Encryption Standard / Triple DES)
DES 是较老的算法,密钥长度较短,安全性较低,不推荐用于新应用。3DES 是 DES 的加强版,通过三次 DES 操作提高安全性,但性能不如 AES。
DES 的块大小是 8 字节,密钥长度是 8 字节。
3DES 的块大小是 8 字节,密钥长度是 16 或 24 字节。
from Crypto.Cipher import DES, TripleDES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
# --- DES 示例 (不推荐用于新应用) ---
print("\n--- DES 示例 ---")
# 1. 生成密钥 (DES 要求 8 字节)
key_des = get_random_bytes(8)
# 2. 生成 IV (DES 块大小也是 8 字节)
iv_des = get_random_bytes(DES.block_size)
# 3. 创建密码器对象 (以 CBC 模式为例)
cipher_des = DES.new(key_des, DES.MODE_CBC, iv=iv_des)
# 4. 准备数据并填充
data_des = b"Secret data for DES."
padded_data_des = pad(data_des, DES.block_size)
# 5. 加密
ciphertext_des = cipher_des.encrypt(padded_data_des)
print(f"原始数据 (DES): {data_des}")
print(f"加密后数据 (DES): {ciphertext_des.hex()}")
# 6. 解密
decipher_des = DES.new(key_des, DES.MODE_CBC, iv=iv_des)
decrypted_padded_data_des = decipher_des.decrypt(ciphertext_des)
unpadded_data_des = unpad(decrypted_padded_data_des, DES.block_size)
print(f"解密后数据 (DES): {unpadded_data_des.decode('utf-8')}")
# --- 3DES 示例 ---
print("\n--- 3DES 示例 ---")
# 1. 生成密钥 (3DES 要求 16 或 24 字节)
key_3des = get_random_bytes(16) # 16 字节用于双密钥模式
# 2. 生成 IV (3DES 块大小也是 8 字节)
iv_3des = get_random_bytes(TripleDES.block_size)
# 3. 创建密码器对象 (以 CBC 模式为例)
cipher_3des = TripleDES.new(key_3des, TripleDES.MODE_CBC, iv=iv_3des)
# 4. 准备数据并填充
data_3des = b"Sensitive data for 3DES."
padded_data_3des = pad(data_3des, TripleDES.block_size)
# 5. 加密
ciphertext_3des = cipher_3des.encrypt(padded_data_3des)
print(f"原始数据 (3DES): {data_3des}")
print(f"加密后数据 (3DES): {ciphertext_3des.hex()}")
# 6. 解密
decipher_3des = TripleDES.new(key_3des, TripleDES.MODE_CBC, iv=iv_3des)
decrypted_padded_data_3des = decipher_3des.decrypt(ciphertext_3des)
unpadded_data_3des = unpad(decrypted_padded_data_3des, TripleDES.block_size)
print(f"解密后数据 (3DES): {unpadded_data_3des.decode('utf-8')}")
3. ChaCha20 (ChaCha20-Poly1305)
ChaCha20 是一个流密码,通常与 Poly1305 消息认证码结合使用,形成 ChaCha20-Poly1305,提供认证加密。它通常被认为是 AES-GCM 的替代品,尤其是在没有硬件加速的系统上。
from Crypto.Cipher import ChaCha20_Poly1305
from Crypto.Random import get_random_bytes
# 1. 生成密钥 (ChaCha20 要求 32 字节)
key_chacha = get_random_bytes(32)
# 2. 创建密码器对象
# ChaCha20-Poly1305 会自动生成一个 nonce
cipher_chacha = ChaCha20_Poly1305.new(key=key_chacha)
# 3. 额外认证数据 (可选)
associated_data_chacha = b"Header data not encrypted."
# 4. 准备数据
data_chacha = b"This is a message for ChaCha20-Poly1305."
# 5. 加密
ciphertext_chacha, tag_chacha = cipher_chacha.encrypt_and_digest(data_chacha, associated_data_chacha)
print(f"\n原始数据: {data_chacha}")
print(f"Nonce: {cipher_chacha.nonce.hex()}")
print(f"额外认证数据: {associated_data_chacha.decode('utf-8')}")
print(f"加密后数据 (ChaCha20-Poly1305): {ciphertext_chacha.hex()}")
print(f"认证标签 (Tag): {tag_chacha.hex()}")
# 6. 解密和认证
# 解密时需要相同的密钥、nonce 和 associated_data
decipher_chacha = ChaCha20_Poly1305.new(key=key_chacha, nonce=cipher_chacha.nonce)
try:
decrypted_data_chacha = decipher_chacha.decrypt_and_verify(ciphertext_chacha, tag_chacha, associated_data_chacha)
print(f"解密后数据 (ChaCha20-Poly1305): {decrypted_data_chacha.decode('utf-8')}")
except ValueError:
print("ChaCha20-Poly1305 认证失败或数据被篡改!")
Crypto.Signature
PyCryptodome 库中的 Crypto.Signature
模块提供了实现数字签名功能的工具。数字签名是一种密码学技术,用于验证数据的完整性、来源和不可否认性。它不同于加密,加密旨在保护数据的机密性,而数字签名旨在提供认证和完整性保证。
数字签名的基本原理
数字签名依赖于非对称加密(也称为公钥加密)。这个过程通常涉及以下步骤:
- 密钥生成: 签名者生成一对密钥:一个私钥(secret key)和一个公钥(public key)。私钥是保密的,用于生成签名;公钥可以公开,用于验证签名。
- 哈希: 对要签名的数据(消息)计算一个哈希值(或消息摘要)。哈希函数是一种单向函数,能将任意长度的数据映射为固定长度的哈希值,且对原始数据的微小改动都会导致哈希值发生巨大变化。
- 签名: 签名者使用私钥对哈希值进行加密(或更准确地说,是使用私钥进行签名操作)。这个加密后的哈希值就是数字签名。
- 传输: 签名者将原始数据和数字签名一起发送给接收者。
- 验证: 接收者收到数据和签名后,使用签名者的公钥对数字签名进行解密(或验证操作),得到一个哈希值。同时,接收者也独立计算所收到数据的哈希值。
- 比较: 如果两个哈希值匹配,则说明:
- 数据在传输过程中未被篡改(完整性)。
- 签名确实是由私钥的所有者生成(认证)。
- 签名者不能否认他/她发送了该数据(不可否认性)。
Crypto.Signature
模块的主要组成部分
Crypto.Signature
模块主要包含以下用于数字签名的算法接口:
PKCS1_v1_5
: 用于 RSA 算法的传统签名方案。PSS
: 用于 RSA 算法的概率签名方案,安全性更高,推荐使用。DSS
: 用于 DSA (Digital Signature Algorithm) 算法。ECC
: 用于椭圆曲线数字签名算法 (ECDSA)。EdDSA
: 用于 EdDSA (Edwards-curve Digital Signature Algorithm),如 Ed25519。
使用 RSA 算法进行数字签名 (PKCS1_v1_5 方案)
这是 RSA 签名的一种标准实现,适用于兼容性要求较高的场景。
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from Crypto.Random import get_random_bytes
# 1. 生成 RSA 密钥对
# 生成一个 2048 位的 RSA 私钥
key = RSA.generate(2048)
private_key = key
public_key = key.public_key()
print("--- RSA PKCS1_v1_5 签名示例 ---")
# 2. 准备要签名的数据
message = b"This is a secret message that needs to be signed."
# 3. 对消息进行哈希
# 使用 SHA256 作为哈希算法
h = SHA256.new(message)
# 4. 创建签名器对象并签名
# 使用私钥创建签名器
signer = PKCS1_v1_5.new(private_key)
# 对哈希值进行签名
signature = signer.sign(h)
print(f"原始消息: {message.decode()}")
print(f"签名 (hex): {signature.hex()}")
# 5. 验证签名
# 接收者使用公钥创建验证器
verifier = PKCS1_v1_5.new(public_key)
# 接收者也计算接收到消息的哈希值
h_verify = SHA256.new(message)
# 验证签名
try:
verifier.verify(h_verify, signature)
print("签名验证成功!消息未被篡改。")
except (ValueError, TypeError):
print("签名验证失败!消息可能被篡改或签名无效。")
# 尝试篡改消息并验证
print("\n--- 尝试篡改消息 ---")
tampered_message = b"This is a tampered message!"
h_tampered = SHA256.new(tampered_message)
try:
verifier.verify(h_tampered, signature)
print("篡改消息后签名验证成功 (不应发生)!")
except (ValueError, TypeError):
print("篡改消息后签名验证失败 (预期结果)!")
使用 RSA 算法进行数字签名
PSS (Probabilistic Signature Scheme) 是一种更为现代和安全的 RSA 签名方案,它引入了随机填充,增强了抵抗某些攻击的能力。
from Crypto.PublicKey import RSA
from Crypto.Signature import PSS
from Crypto.Hash import SHA256
from Crypto.Random import get_random_bytes
# 1. 生成 RSA 密钥对 (同上)
key_pss = RSA.generate(2048)
private_key_pss = key_pss
public_key_pss = key_pss.public_key()
print("\n--- RSA PSS 签名示例 ---")
# 2. 准备要签名的数据
message_pss = b"This message is signed using RSA PSS."
# 3. 对消息进行哈希
h_pss = SHA256.new(message_pss)
# 4. 创建签名器对象并签名
# 使用私钥创建签名器
signer_pss = PSS.new(private_key_pss)
# 对哈希值进行签名
signature_pss = signer_pss.sign(h_pss)
print(f"原始消息: {message_pss.decode()}")
print(f"签名 (hex): {signature_pss.hex()}")
# 5. 验证签名
# 接收者使用公钥创建验证器
verifier_pss = PSS.new(public_key_pss)
# 接收者也计算接收到消息的哈希值
h_verify_pss = SHA256.new(message_pss)
# 验证签名
try:
verifier_pss.verify(h_verify_pss, signature_pss)
print("签名验证成功!消息未被篡改。")
except (ValueError, TypeError):
print("签名验证失败!消息可能被篡改或签名无效。")
使用 ECDSA 算法进行数字签名
ECDSA (Elliptic Curve Digital Signature Algorithm) 基于椭圆曲线密码学,通常比同等安全强度的 RSA 密钥更短,计算速度更快。
from Crypto.PublicKey import ECC
from Crypto.Signature import DSS
from Crypto.Hash import SHA256
from Crypto.Random import get_random_bytes
# 1. 生成 ECC 密钥对
# 使用 'NIST P-256' 曲线,它是常用的安全曲线
key_ecc = ECC.generate(curve='P-256')
private_key_ecc = key_ecc
public_key_ecc = key_ecc.public_key()
print("\n--- ECDSA 签名示例 ---")
# 2. 准备要签名的数据
message_ecc = b"This is a message signed with ECDSA."
# 3. 对消息进行哈希
h_ecc = SHA256.new(message_ecc)
# 4. 创建签名器对象并签名
# 使用私钥创建签名器
signer_ecc = DSS.new(private_key_ecc, 'fips-186-3') # fips-186-3 是标准模式
signature_ecc = signer_ecc.sign(h_ecc)
print(f"原始消息: {message_ecc.decode()}")
print(f"签名 (hex): {signature_ecc.hex()}")
# 5. 验证签名
# 接收者使用公钥创建验证器
verifier_ecc = DSS.new(public_key_ecc, 'fips-186-3')
# 接收者也计算接收到消息的哈希值
h_verify_ecc = SHA256.new(message_ecc)
# 验证签名
try:
verifier_ecc.verify(h_verify_ecc, signature_ecc)
print("签名验证成功!消息未被篡改。")
except ValueError:
print("签名验证失败!消息可能被篡改或签名无效。")
使用 EdDSA (Ed25519) 算法进行数字签名
EdDSA 是一种现代的、高效且安全的椭圆曲线签名算法,尤其是 Ed25519 曲线,因其简单性、安全性和性能而受到广泛推荐。
from Crypto.PublicKey import Ed25519
from Crypto.Signature import EdDSA
from Crypto.Hash import SHA256 # 虽然 EdDSA 通常内置哈希,这里仍然展示哈希的概念
from Crypto.Random import get_random_bytes
# 1. 生成 Ed25519 密钥对
private_key_eddsa = Ed25519.generate()
public_key_eddsa = private_key_eddsa.public_key()
print("\n--- EdDSA (Ed25519) 签名示例 ---")
# 2. 准备要签名的数据
message_eddsa = b"This is a message signed with Ed25519."
# 3. 创建签名器对象并签名
# 注意:EdDSA 的签名过程通常会内置哈希,所以直接传入消息即可
signer_eddsa = EdDSA.new(private_key_eddsa)
signature_eddsa = signer_eddsa.sign(message_eddsa) # 直接对消息进行签名
print(f"原始消息: {message_eddsa.decode()}")
print(f"签名 (hex): {signature_eddsa.hex()}")
# 4. 验证签名
# 接收者使用公钥创建验证器
verifier_eddsa = EdDSA.new(public_key_eddsa)
# 验证签名
try:
verifier_eddsa.verify(message_eddsa, signature_eddsa) # 直接验证消息
print("签名验证成功!消息未被篡改。")
except ValueError:
print("签名验证失败!消息可能被篡改或签名无效。")
# 尝试篡改消息并验证
print("\n--- 尝试篡改消息 (Ed25519) ---")
tampered_message_eddsa = b"This is a tampered Ed25519 message!"
try:
verifier_eddsa.verify(tampered_message_eddsa, signature_eddsa)
print("篡改消息后签名验证成功 (不应发生)!")
except ValueError:
print("篡改消息后签名验证失败 (预期结果)!")
Crypto.Util
PyCryptodome 库中的 Crypto.Util
模块是一个实用工具的宝库,它提供了大量辅助函数,能帮你更方便、更安全地处理密码学操作。它不直接包含加密算法,而是提供了一些基础性的、在构建密码系统时经常需要的功能,比如数据填充、安全随机数生成、大数运算等。
Crypto.Util.Padding
:轻松搞定数据填充和去填充
在块密码(如 AES、DES)的某些模式(比如 ECB 和 CBC)中,数据长度必须是块大小的整数倍。如果你的数据不够长,就需要“填充”一下;解密后,还得把这些填充去掉。Crypto.Util.Padding
就是来干这个的,它支持几种标准的填充方案。
pad(data, block_size, style='pkcs7')
: 给数据添加填充。data
: 要填充的原始字节串。block_size
: 块密码的块大小(字节),比如 AES 就是 16 字节。style
: 填充方式,默认是pkcs7
(PKCS#7 标准),你也可以选iso7816
或x923
。
unpad(padded_data, block_size, style='pkcs7')
: 移除数据的填充。padded_data
: 已经填充过的字节串。block_size
: 块密码的块大小。style
: 必须和填充时用的方式一致。
示例:
from Crypto.Util.Padding import pad, unpad
from Crypto.Cipher import AES
block_size = AES.block_size # AES 的块大小是 16 字节
data_to_pad = b"A short message"
print(f"原始数据: {data_to_pad} (长度: {len(data_to_pad)})")
padded_data_pkcs7 = pad(data_to_pad, block_size, style='pkcs7')
print(f"PKCS7 填充后: {padded_data_pkcs7} (长度: {len(padded_data_pkcs7)})")
unpadded_data_pkcs7 = unpad(padded_data_pkcs7, block_size, style='pkcs7')
print(f"PKCS7 去填充后: {unpadded_data_pkcs7}\n")
Crypto.Util.Counter
:CTR 模式的好帮手
Crypto.Util.Counter
模块专门为 CTR (计数器模式) 块密码设计。CTR 模式需要一个不断递增的计数器来生成加密流,这个模块就提供了这样的计数器对象。
new(nbits, initial_value=0, prefix=None, suffix=None, allow_wraparound=False)
: 创建一个计数器。nbits
: 计数器的位数,通常是块大小的位数(比如 AES 就是 128 位)。initial_value
: 计数器的初始值。prefix
,suffix
: 可选的前缀和后缀,用于和计数器组合成完整的输入块。
示例:
from Crypto.Util import Counter
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
key = get_random_bytes(16)
nonce = get_random_bytes(8)
# 创建计数器,把 nonce 作为前缀
ctr = Counter.new(64, prefix=nonce, initial_value=0) # 64位计数器 + 8字节 nonce = 128位
cipher_aes_ctr = AES.new(key, AES.MODE_CTR, counter=ctr)
message = b"This is a message for CTR mode encryption."
ciphertext_ctr = cipher_aes_ctr.encrypt(message)
print(f"原始消息 (CTR): {message}")
print(f"加密后消息 (CTR): {ciphertext_ctr.hex()}")
# 解密时同样需要密钥和相同的计数器初始状态
ctr_decrypt = Counter.new(64, prefix=nonce, initial_value=0)
decipher_aes_ctr = AES.new(key, AES.MODE_CTR, counter=ctr_decrypt)
decrypted_message_ctr = decipher_aes_ctr.decrypt(ciphertext_ctr)
print(f"解密后消息 (CTR): {decrypted_message_ctr.decode('utf-8')}")
Crypto.Util.RFC1751
:让密钥变得“可读”
这个模块能把你的加密密钥(字节串)转换成一串容易记忆和口头传输的单词,符合 RFC 1751 标准。这主要用于辅助人工传递或记住短密钥,但记住,它不能替代安全的密钥存储方式。
byte_to_wordlist(key_bytes)
: 把字节串转成单词列表。wordlist_to_byte(word_list)
: 把单词列表转回字节串。
示例:
from Crypto.Util import RFC1751
from Crypto.Random import get_random_bytes
key_bytes = get_random_bytes(8) # 8 字节密钥
print(f"\n原始密钥 (字节): {key_bytes.hex()}")
word_list = RFC1751.byte_to_wordlist(key_bytes)
print(f"密钥单词列表: {' '.join(word_list)}")
recovered_key_bytes = RFC1751.wordlist_to_byte(word_list)
print(f"恢复的密钥 (字节): {recovered_key_bytes.hex()}")
print(f"原始密钥和恢复密钥是否匹配: {key_bytes == recovered_key_bytes}")
Crypto.Util.number
:大整数运算和素性检测利器
这个模块提供了一些处理大整数和进行素性检测的函数,这在实现像 RSA 这样的公钥密码算法时特别有用。
bytes_to_long(b)
: 将字节串转换为长整数。long_to_bytes(l, blocksize=None)
: 将长整数转换为字节串。isPrime(n)
: 判断一个数是否为素数(概率性判断)。getPrime(bits)
: 生成一个指定位数的随机素数。GCD(a, b)
: 计算两个数的最大公约数。inverse(a, b)
: 计算 a a a 模 b b b 的乘法逆元。
示例:
from Crypto.Util import number
byte_data = b'\x01\x02\x03\x04\x05'
long_int = number.bytes_to_long(byte_data)
print(f"\n字节串: {byte_data.hex()}")
print(f"转换为长整数: {long_int}")
p = number.getPrime(512) # 生成一个 512 位的素数
print(f"生成的 512 位素数: {p}")
print(f"是否是素数: {number.isPrime(p)}")
Crypto.Util.strxor
:字节串异或操作
这个模块提供了对字节串进行逐位异或操作的函数,这在一些密码模式(如 OFB、CFB)和密码学构造中会用到。
strxor(s1, s2)
: 对两个字节串进行逐位异或。如果长度不同,结果长度与较短的字节串一致。strxor_long(s1, s2)
: 对两个字节串进行逐位异或。如果长度不同,较短的字节串会循环填充,结果长度与较长的字节串一致。
示例:
from Crypto.Util import strxor
bytes1 = b'\x01\x02\x03'
bytes2 = b'\xff\xfe\xfd'
xor_result = strxor.strxor(bytes1, bytes2)
print(f"\n{bytes1.hex()} XOR {bytes2.hex()}: {xor_result.hex()}")
Crypto.Util.asn1
:处理 ASN.1 编码/解码
这个模块用来处理 ASN.1 (Abstract Syntax Notation One) 编码。ASN.1 是一种数据描述语言,经常用来定义和编码密码学对象,比如数字证书或密钥。
Crypto.Util.asn1
包含各种 ASN.1 类型的类,比如DerSequence
,DerInteger
,DerOctetString
等,用于解析和构建 ASN.1 结构。
示例 (简要,因为 ASN.1 相对复杂):
from Crypto.Util.asn1 import DerSequence, DerInteger, DerOctetString
# 构造一个简单的 ASN.1 序列
seq = DerSequence([
DerInteger(123),
DerOctetString(b"hello world")
])
encoded = seq.encode()
print(f"\nASN.1 编码: {encoded.hex()}")
# 解码
decoded_seq = DerSequence()
decoded_seq.decode(encoded)
print(f"解码后的整数: {DerInteger(decoded_seq[0]).value}")
print(f"解码后的八位字节串: {DerOctetString(decoded_seq[1]).value}")
Crypto.Hash
PyCryptodome 库中的 Crypto.Hash
模块专注于哈希函数(也称为散列函数或消息摘要函数)。哈希函数是一种将任意长度的输入(数据)转换为固定长度输出(哈希值或摘要)的数学算法。它的主要特点是:
- 确定性: 相同的输入总是产生相同的输出。
- 效率: 计算哈希值非常快速。
- 单向性: 几乎不可能从哈希值反推出原始输入数据。
- 抗碰撞性: 很难找到两个不同的输入能产生相同的哈希值(抗强碰撞性),或找到一个与给定输入哈希值相同的不同输入(抗弱碰撞性)。
哈希函数在密码学中扮演着至关重要的角色,常用于:
- 数据完整性验证: 检查文件或消息在传输或存储过程中是否被篡改。
- 数字签名: 作为生成签名的第一步,对消息计算哈希值。
- 密码存储: 存储用户密码的哈希值而不是明文,以增强安全性。
- 伪随机数生成: 作为某些随机数生成器的基础。
- 数据去重: 快速识别重复数据。
Crypto.Hash
模块的主要哈希算法
Crypto.Hash
模块提供了多种常用的密码学哈希算法,每种算法都有其特定的哈希值长度和安全特性。
Crypto.Hash.MD5
:MD5 哈希算法 (不推荐用于安全用途)
MD5 (Message-Digest Algorithm 5) 是一种较老的哈希算法,它产生一个 128 位(16 字节)的哈希值。由于其已被发现存在严重的安全漏洞(容易发生碰撞),因此不推荐将其用于安全性要求高的场景,比如数字签名或密码存储。它现在主要用于数据校验和(如文件下载后的完整性检查,但仍有风险)或非安全的数据索引。
示例:
from Crypto.Hash import MD5
# 1. 创建 MD5 哈希对象
h = MD5.new()
# 2. 更新哈希对象:可以多次调用 update()
message1 = b"Hello, world!"
h.update(message1)
message2 = b" This is a test."
h.update(message2) # 可以分块更新
# 3. 获取哈希值
md5_digest = h.digest() # 返回字节串形式的哈希值
md5_hexdigest = h.hexdigest() # 返回十六进制字符串形式的哈希值
print("--- MD5 哈希示例 ---")
print(f"原始消息: {message1.decode()}{message2.decode()}")
print(f"MD5 哈希 (字节): {md5_digest.hex()}")
print(f"MD5 哈希 (十六进制): {md5_hexdigest}")
# 简洁方式:一次性哈希
another_message = b"Just another message."
h2 = MD5.new(another_message)
print(f"另一条消息 '{another_message.decode()}' 的 MD5: {h2.hexdigest()}")
Crypto.Hash.SHA1
:SHA-1 哈希算法 (不推荐用于安全用途)
SHA-1 (Secure Hash Algorithm 1) 产生一个 160 位(20 字节)的哈希值。与 MD5 类似,SHA-1 也被发现存在实际的碰撞攻击,因此也不再推荐用于安全性要求高的应用。很多现代系统已经逐步淘汰了 SHA-1。
示例:
from Crypto.Hash import SHA1
# 1. 创建 SHA1 哈希对象并更新
h = SHA1.new()
data = b"This is some data for SHA1 hashing."
h.update(data)
# 2. 获取哈希值
sha1_digest = h.digest()
sha1_hexdigest = h.hexdigest()
print("\n--- SHA-1 哈希示例 ---")
print(f"原始数据: {data.decode()}")
print(f"SHA-1 哈希 (字节): {sha1_digest.hex()}")
print(f"SHA-1 哈希 (十六进制): {sha1_hexdigest}")
Crypto.Hash.SHA256
:SHA-256 哈希算法 (推荐)
SHA-256 是 SHA-2 系列哈希算法的一部分,它产生一个 256 位(32 字节)的哈希值。SHA-256 目前被广泛认为是安全可靠的哈希算法,在许多协议和应用中被使用,例如比特币、TLS/SSL 证书等。
示例:
from Crypto.Hash import SHA256
# 1. 创建 SHA256 哈希对象并更新
h = SHA256.new()
data = b"Encrypt this text with SHA256."
h.update(data)
# 2. 获取哈希值
sha256_digest = h.digest()
sha256_hexdigest = h.hexdigest()
print("\n--- SHA-256 哈希示例 ---")
print(f"原始数据: {data.decode()}")
print(f"SHA-256 哈希 (字节): {sha256_digest.hex()}")
print(f"SHA-256 哈希 (十六进制): {sha256_hexdigest}")
# 应用场景:验证文件完整性
def calculate_file_sha256(filepath):
hasher = SHA256.new()
with open(filepath, 'rb') as f:
for chunk in iter(lambda: f.read(4096), b''):
hasher.update(chunk)
return hasher.hexdigest()
# 假设你有一个文件 'test_file.txt'
# with open('test_file.txt', 'w') as f:
# f.write("This is a sample file for SHA256 hashing.\n")
# f.write("It contains multiple lines.")
# file_hash = calculate_file_sha256('test_file.txt')
# print(f"文件 'test_file.txt' 的 SHA-256 哈希: {file_hash}")
Crypto.Hash.SHA512
:SHA-512 哈希算法 (推荐)
SHA-512 同样是 SHA-2 系列的一部分,它产生一个 512 位(64 字节)的哈希值。提供比 SHA-256 更高的安全强度(因为哈希值更长),但计算速度通常会慢一些。
示例:
from Crypto.Hash import SHA512
# 1. 创建 SHA512 哈希对象并更新
h = SHA512.new()
data = b"A longer message for SHA512 hashing, providing more security."
h.update(data)
# 2. 获取哈希值
sha512_digest = h.digest()
sha512_hexdigest = h.hexdigest()
print("\n--- SHA-512 哈希示例 ---")
print(f"原始数据: {data.decode()}")
print(f"SHA-512 哈希 (字节): {sha512_digest.hex()}")
print(f"SHA-512 哈希 (十六进制): {sha512_hexdigest}")
Crypto.Hash.BLAKE2b
和 Crypto.Hash.BLAKE2s
(推荐)
BLAKE2 是比 SHA-2 和 SHA-3 更快的密码学哈希算法,同时提供了很强的安全性。它有两种主要版本:
BLAKE2b
: 适用于 64 位系统,可以产生 1 到 64 字节(512 位)的哈希值。BLAKE2s
: 适用于 8 到 32 位系统,可以产生 1 到 32 字节(256 位)的哈希值。
BLAKE2 支持键控哈希 (keyed hashing),可以用于创建类似 HMAC 的功能,从而提供消息认证码 (MAC)。
示例:
from Crypto.Hash import BLAKE2b, BLAKE2s
from Crypto.Random import get_random_bytes
# --- BLAKE2b 示例 ---
# 1. 创建 BLAKE2b 哈希对象
# 可以指定 digest_bytes 来控制输出哈希值的长度 (1到64字节)
# 可以指定 key 来实现键控哈希 (类似 HMAC)
key_b2b = get_random_bytes(BLAKE2b.KEY_SIZE) # 64字节
h_b2b = BLAKE2b.new(digest_bytes=32, key=key_b2b) # 32字节输出,使用键控哈希
# 2. 更新数据
data_b2b = b"Data for BLAKE2b keyed hashing."
h_b2b.update(data_b2b)
# 3. 获取哈希值
digest_b2b = h_b2b.hexdigest()
print("\n--- BLAKE2b 哈希示例 ---")
print(f"原始数据: {data_b2b.decode()}")
print(f"BLAKE2b 哈希 (十六进制, 32字节): {digest_b2b}")
# --- BLAKE2s 示例 ---
h_b2s = BLAKE2s.new(digest_bytes=20) # 20字节输出
data_b2s = b"Another piece of data for BLAKE2s."
h_b2s.update(data_b2s)
digest_b2s = h_b2s.hexdigest()
print("\n--- BLAKE2s 哈希示例 ---")
print(f"原始数据: {data_b2s.decode()}")
print(f"BLAKE2s 哈希 (十六进制, 20字节): {digest_b2s}")
Crypto.Hash.SHA3_256
和 Crypto.Hash.SHA3_512
等:SHA-3 系列 (推荐)
SHA-3 是最新的哈希算法标准,由 NIST 在 Keccak 算法家族中选出。它与 SHA-2 在结构上完全不同,旨在提供一个替代选项,以防 SHA-2 系列未来被攻破。PyCryptodome 提供了多种 SHA-3 变体:
SHA3_224
: 224 位哈希值SHA3_256
: 256 位哈希值SHA3_384
: 384 位哈希值SHA3_512
: 512 位哈希值
示例:
from Crypto.Hash import SHA3_256, SHA3_512
# --- SHA3-256 示例 ---
h_sha3_256 = SHA3_256.new()
data_sha3 = b"Data for SHA3-256 hashing."
h_sha3_256.update(data_sha3)
digest_sha3_256 = h_sha3_256.hexdigest()
print("\n--- SHA3-256 哈希示例 ---")
print(f"原始数据: {data_sha3.decode()}")
print(f"SHA3-256 哈希 (十六进制): {digest_sha3_256}")
# --- SHA3-512 示例 ---
h_sha3_512 = SHA3_512.new(data=b"Directly hash this data for SHA3-512.")
digest_sha3_512 = h_sha3_512.hexdigest()
print("\n--- SHA3-512 哈希示例 ---")
print(f"原始数据: Directly hash this data for SHA3-512.")
print(f"SHA3-512 哈希 (十六进制): {digest_sha3_512}")
Crypto.PublicKey
Crypto.PublicKey
模块是 PyCryptodome 库中用于处理非对称加密(也称为公钥加密)密钥的核心模块。与对称加密不同,非对称加密使用一对相关联的密钥:一个公钥和一个私钥。
- 公钥:可以公开分发,用于加密数据或验证数字签名。
- 私钥:必须严格保密,用于解密数据或生成数字签名。
非对称加密主要用于解决对称加密中的密钥分发问题,以及提供数字签名和身份认证功能。
Crypto.PublicKey
模块的主要功能
该模块提供了生成、加载、保存以及操作各种非对称密钥对的功能,包括:
- RSA: 最常用的非对称加密算法之一。
- DSA: 主要用于数字签名。
- ECC (椭圆曲线加密): 提供与 RSA 相同安全强度下更短的密钥和更快的运算速度。
- ElGamal: 另一种非对称加密算法。
- EdDSA (Edwards-curve Digital Signature Algorithm): 一种现代的高性能椭圆曲线签名算法。
主要密钥算法和用法示例
RSA 密钥
RSA 是应用最广泛的非对称加密算法。它的安全性基于大数因子分解的困难性。
1. 生成 RSA 密钥对
生成 RSA 密钥对是使用 RSA 进行加密和签名的第一步。密钥的长度(以位为单位)决定了其安全性。通常,推荐使用 2048 位或更长的密钥。
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
# 生成一个 2048 位的 RSA 密钥对
key = RSA.generate(2048)
# 访问私钥和公钥
private_key = key
public_key = key.public_key()
print("--- RSA 密钥生成 ---")
print(f"私钥类型: {type(private_key)}")
print(f"公钥类型: {type(public_key)}")
# 你可以打印出密钥的各个组成部分(模数 N, 公钥指数 E, 私钥指数 D 等),但不建议在生产环境中这样做
# print(f"RSA 私钥模数 (n): {private_key.n}")
# print(f"RSA 私钥指数 (d): {private_key.d}")
# print(f"RSA 公钥指数 (e): {private_key.e}")
2. 导出和导入 RSA 密钥
密钥通常需要导出为文件或字符串以便存储或传输。PyCryptodome 支持多种导出格式。
- PEM (Privacy-Enhanced Mail): 最常用的人类可读格式。
- DER (Distinguished Encoding Rules): 二进制格式。
导出时,私钥可以被密码保护,以增加安全性。
from Crypto.PublicKey import RSA
key = RSA.generate(2048)
# 导出私钥 (PEM 格式,无加密)
private_pem = key.export_key()
print("\n--- 导出 RSA 私钥 (PEM, 无加密) ---")
print(private_pem.decode())
# 导出私钥 (PEM 格式,带密码加密)
# pass_phrase = "mysecretpassword"
# private_pem_encrypted = key.export_key(passphrase=pass_phrase, pkcs=8, protection="scryptAndAES256Cbc")
# print("\n--- 导出 RSA 私钥 (PEM, 带密码加密) ---")
# print(private_pem_encrypted.decode())
# 导出公钥 (PEM 格式)
public_pem = key.public_key().export_key()
print("\n--- 导出 RSA 公钥 (PEM) ---")
print(public_pem.decode())
# 从 PEM 字符串导入私钥
imported_private_key = RSA.import_key(private_pem)
print(f"\n导入的私钥类型: {type(imported_private_key)}")
print(f"导入的私钥模数是否与原密钥相同: {imported_private_key.n == key.n}")
# 从 PEM 字符串导入公钥
imported_public_key = RSA.import_key(public_pem)
print(f"导入的公钥类型: {type(imported_public_key)}")
print(f"导入的公钥模数是否与原密钥相同: {imported_public_key.n == key.n}")
# 导入带密码保护的私钥
# try:
# imported_private_key_encrypted = RSA.import_key(private_pem_encrypted, passphrase=pass_phrase)
# print(f"成功导入加密私钥,模数是否与原密钥相同: {imported_private_key_encrypted.n == key.n}")
# except ValueError as e:
# print(f"导入加密私钥失败: {e}")
3. RSA 加密和解密
RSA 通常用于加密对称密钥,而不是直接加密大量数据,因为其效率较低且有数据长度限制。
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP # 推荐的 RSA 加密模式
from Crypto.Random import get_random_bytes
# 生成 RSA 密钥对
key = RSA.generate(2048)
public_key = key.public_key()
private_key = key
message = b"This is a short secret message."
# 使用公钥加密 (PKCS1_OAEP 模式是推荐的填充方案)
# new() 方法接收密钥和加密填充模式
cipher_rsa = PKCS1_OAEP.new(public_key)
encrypted_message = cipher_rsa.encrypt(message)
print("\n--- RSA 加密和解密 ---")
print(f"原始消息: {message.decode()}")
print(f"加密消息 (hex): {encrypted_message.hex()}")
# 使用私钥解密
decipher_rsa = PKCS1_OAEP.new(private_key)
decrypted_message = decipher_rsa.decrypt(encrypted_message)
print(f"解密消息: {decrypted_message.decode()}")
4. RSA 数字签名和验证
RSA 签名用于验证数据的完整性和来源。
from Crypto.PublicKey import RSA
from Crypto.Signature import PSS # 推荐的 RSA 签名模式
from Crypto.Hash import SHA256
from Crypto.Random import get_random_bytes
# 生成 RSA 密钥对
key = RSA.generate(2048)
private_key = key
public_key = key.public_key()
message_to_sign = b"Verify the integrity of this document."
# 计算消息的哈希值
h = SHA256.new(message_to_sign)
# 使用私钥签名 (PSS 模式)
signer = PSS.new(private_key)
signature = signer.sign(h)
print("\n--- RSA 数字签名和验证 ---")
print(f"待签名消息: {message_to_sign.decode()}")
print(f"签名 (hex): {signature.hex()}")
# 使用公钥验证签名
verifier = PSS.new(public_key)
try:
verifier.verify(h, signature)
print("签名验证成功!消息未被篡改。")
except (ValueError, TypeError):
print("签名验证失败!消息可能被篡改或签名无效。")
ECC (椭圆曲线加密) 密钥
ECC 密钥通常比 RSA 密钥短得多,但提供相同的安全强度。PyCryptodome 支持多种椭圆曲线。
1. 生成 ECC 密钥对
选择一个标准曲线(如 P-256
, P-384
, P-521
)是很重要的。
from Crypto.PublicKey import ECC
# 生成一个使用 P-256 曲线的 ECC 密钥对
key_ecc = ECC.generate(curve='P-256')
private_key_ecc = key_ecc
public_key_ecc = key_ecc.public_key()
print("\n--- ECC 密钥生成 ---")
print(f"私钥类型: {type(private_key_ecc)}")
print(f"公钥类型: {type(public_key_ecc)}")
print(f"使用的曲线: {key_ecc.curve}")
2. 导出和导入 ECC 密钥
ECC 密钥也可以导出和导入。
from Crypto.PublicKey import ECC
key_ecc = ECC.generate(curve='P-256')
# 导出 ECC 私钥 (PEM 格式)
private_pem_ecc = key_ecc.export_key(format='PEM')
print("\n--- 导出 ECC 私钥 (PEM) ---")
print(private_pem_ecc.decode())
# 导出 ECC 公钥 (PEM 格式)
public_pem_ecc = key_ecc.public_key().export_key(format='PEM')
print("\n--- 导出 ECC 公钥 (PEM) ---")
print(public_pem_ecc.decode())
# 从 PEM 字符串导入 ECC 私钥
imported_private_key_ecc = ECC.import_key(private_pem_ecc)
print(f"\n导入的 ECC 私钥曲线: {imported_private_key_ecc.curve}")
# 从 PEM 字符串导入 ECC 公钥
imported_public_key_ecc = ECC.import_key(public_pem_ecc)
print(f"导入的 ECC 公钥曲线: {imported_public_key_ecc.curve}")
3. ECDSA 签名和验证 (使用 Crypto.Signature.DSS
)
ECC 主要用于数字签名,对应的算法是 ECDSA (Elliptic Curve Digital Signature Algorithm)。
from Crypto.PublicKey import ECC
from Crypto.Signature import DSS
from Crypto.Hash import SHA256
# 生成 ECC 密钥对
key_ecc = ECC.generate(curve='P-256')
private_key_ecc = key_ecc
public_key_ecc = key_ecc.public_key()
message_to_sign_ecc = b"This document is signed by an ECC key."
# 计算消息的哈希值
h_ecc = SHA256.new(message_to_sign_ecc)
# 使用私钥签名
signer_ecc = DSS.new(private_key_ecc, 'fips-186-3') # 'fips-186-3' 是标准模式
signature_ecc = signer_ecc.sign(h_ecc)
print("\n--- ECDSA 签名和验证 ---")
print(f"待签名消息: {message_to_sign_ecc.decode()}")
print(f"签名 (hex): {signature_ecc.hex()}")
# 使用公钥验证签名
verifier_ecc = DSS.new(public_key_ecc, 'fips-186-3')
try:
verifier_ecc.verify(h_ecc, signature_ecc)
print("ECDSA 签名验证成功!")
except ValueError:
print("ECDSA 签名验证失败!")
EdDSA (Ed25519) 密钥
EdDSA 是一种更现代的椭圆曲线数字签名算法,特别是 Ed25519 曲线,因其简洁性、安全性和高性能而备受推崇。
1. 生成 Ed25519 密钥对
from Crypto.PublicKey import Ed25519
# 生成 Ed25519 密钥对
private_key_eddsa = Ed25519.generate()
public_key_eddsa = private_key_eddsa.public_key()
print("\n--- Ed25519 密钥生成 ---")
print(f"私钥类型: {type(private_key_eddsa)}")
print(f"公钥类型: {type(public_key_eddsa)}")
2. 导出和导入 Ed25519 密钥
from Crypto.PublicKey import Ed25519
private_key_eddsa = Ed25519.generate()
# 导出 Ed25519 私钥 (PEM 格式)
private_pem_eddsa = private_key_eddsa.export_key(format='PEM')
print("\n--- 导出 Ed25519 私钥 (PEM) ---")
print(private_pem_eddsa.decode())
# 导出 Ed25519 公钥 (PEM 格式)
public_pem_eddsa = private_key_eddsa.public_key().export_key(format='PEM')
print("\n--- 导出 Ed25519 公钥 (PEM) ---")
print(public_pem_eddsa.decode())
# 从 PEM 字符串导入 Ed25519 私钥
imported_private_key_eddsa = Ed25519.import_key(private_pem_eddsa)
print(f"\n导入的 Ed25519 私钥类型: {type(imported_private_key_eddsa)}")
# 从 PEM 字符串导入 Ed25519 公钥
imported_public_key_eddsa = Ed25519.import_key(public_pem_eddsa)
print(f"导入的 Ed25519 公钥类型: {type(imported_public_key_eddsa)}")
3. EdDSA 签名和验证
EdDSA 的签名过程通常会内置哈希,所以直接传入消息即可。
from Crypto.PublicKey import Ed25519
from Crypto.Signature import EdDSA
# 生成 Ed25519 密钥对
private_key_eddsa = Ed25519.generate()
public_key_eddsa = private_key_eddsa.public_key()
message_to_sign_eddsa = b"This is a message signed with Ed25519."
# 使用私钥签名
signer_eddsa = EdDSA.new(private_key_eddsa)
signature_eddsa = signer_eddsa.sign(message_to_sign_eddsa) # 直接对消息进行签名
print("\n--- EdDSA (Ed25519) 签名和验证 ---")
print(f"待签名消息: {message_to_sign_eddsa.decode()}")
print(f"签名 (hex): {signature_eddsa.hex()}")
# 使用公钥验证签名
verifier_eddsa = EdDSA.new(public_key_eddsa)
try:
verifier_eddsa.verify(message_to_sign_eddsa, signature_eddsa) # 直接验证消息
print("EdDSA 签名验证成功!")
except ValueError:
print("EdDSA 签名验证失败!")
Crypto.Protocol
Crypto.Protocol
模块是 PyCryptodome 库中用于实现各种密码学协议的集合。这些协议是更高级别的构建块,它们结合了多种密码学原语(如加密、哈希、密钥交换)来解决特定的安全问题,例如密钥派生、口令验证等。理解这些协议对于构建安全的应用程序至关重要,因为它们处理了如何安全地使用和管理密钥及凭证的复杂性。
Crypto.Protocol.KDF
:密钥派生函数 (KDF)
密钥派生函数 (Key Derivation Function, KDF) 用于从一个弱秘密(如用户密码)派生出一个强密码学密钥。直接使用用户密码作为加密密钥是非常不安全的,因为密码通常较短且容易被猜测。KDF 通过引入盐值 (salt) 和迭代次数 (iterations) 来增强安全性,使其对暴力破解和彩虹表攻击更具抵抗力。
常用的 KDF 包括 PBKDF2 和 Scrypt。
PBKDF2 (Password-Based Key Derivation Function 2)
PBKDF2 是一种广泛使用的 KDF,它通过重复哈希操作来增加计算密钥的时间成本。
PBKDF2(password, salt, dkLen=None, count=1000, prf=None)
password
: 用户的原始密码(字节串)。salt
: 一个随机的、唯一的盐值(字节串)。每次派生密钥时都应该使用不同的盐值。dkLen
: 派生密钥的所需长度(字节)。count
: 迭代次数,用于增加计算成本。值越大越安全,但速度越慢。prf
: 伪随机函数,通常是 HMAC-SHA256 或 HMAC-SHA512。
示例:
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Hash import SHA512
from Crypto.Random import get_random_bytes
password = b"my_super_secret_password"
salt = get_random_bytes(16) # 每次都生成一个唯一的盐值
iterations = 100000 # 迭代次数,应根据硬件性能调整,越大越好
# 派生一个 32 字节 (256 位) 的密钥
key_pbkdf2 = PBKDF2(password, salt, dkLen=32, count=iterations, prf=SHA512)
print("--- PBKDF2 密钥派生示例 ---")
print(f"原始密码: {password.decode()}")
print(f"盐值: {salt.hex()}")
print(f"迭代次数: {iterations}")
print(f"派生密钥 (PBKDF2): {key_pbkdf2.hex()}")
# 验证密码(当用户登录时)
# 假设存储了盐值和派生密钥的哈希值(或直接是派生密钥)
# 这里只演示派生过程,实际应用中会比较新派生出的密钥与存储的密钥是否一致
new_key_pbkdf2 = PBKDF2(password, salt, dkLen=32, count=iterations, prf=SHA512)
print(f"重新派生密钥是否相同: {key_pbkdf2 == new_key_pbkdf2}")
Scrypt
Scrypt 是一种专门为抵御 GPU 暴力破解而设计的 KDF,它在计算过程中需要大量的内存,这使得使用高性能硬件进行大规模攻击的成本更高。
Scrypt(password, salt, keyLen, N, r, p)
password
: 用户的原始密码(字节串)。salt
: 随机的、唯一的盐值(字节串)。keyLen
: 派生密钥的所需长度(字节)。N
: CPU/内存成本参数,必须是 2 的幂次。值越大,计算越慢,内存消耗越大。r
: 块大小参数。p
: 并行化参数。
示例:
from Crypto.Protocol.KDF import scrypt
from Crypto.Random import get_random_bytes
password = b"another_secret_password"
salt = get_random_bytes(16)
# Scrypt 参数 N, r, p 通常需要根据安全性和性能要求进行调整
# N 建议取 2 的幂次,r 通常是 8,p 通常是 1
N = 2**14 # CPU/内存成本,例如 16384
r = 8 # 块大小
p = 1 # 并行化参数
# 派生一个 32 字节 (256 位) 的密钥
key_scrypt = scrypt(password, salt, dkLen=32, N=N, r=r, p=p)
print("\n--- Scrypt 密钥派生示例 ---")
print(f"原始密码: {password.decode()}")
print(f"盐值: {salt.hex()}")
print(f"Scrypt 参数 (N, r, p): ({N}, {r}, {p})")
print(f"派生密钥 (Scrypt): {key_scrypt.hex()}")
# 验证密码
new_key_scrypt = scrypt(password, salt, dkLen=32, N=N, r=r, p=p)
print(f"重新派生密钥是否相同: {key_scrypt == new_key_scrypt}")
Crypto.Protocol.CFB
和 Crypto.Protocol.OFB
:流模式辅助
虽然这两个协议的名称与 Crypto.Cipher
中的模式重叠,但在 Crypto.Protocol
中它们通常是作为一个独立的辅助层,用于处理更复杂的流加密场景,或者作为底层加密原语的封装。不过,在 PyCryptodome 的最新版本中,大多数用户直接通过 Crypto.Cipher
中的 AES.new(..., mode=AES.MODE_CFB/OFB)
来使用这些模式,而无需直接调用 Crypto.Protocol.CFB
或 OFB
。它们的内部实现原理与 Crypto.Cipher
中的模式一致。
Crypto.Protocol.Chaining
:通用分组密码链模式
这个模块提供了构建通用分组密码链模式的抽象,使得你可以将任意块密码和填充方案结合起来实现 CBC、CFB、OFB 等模式。同样地,对于大多数用户而言,直接使用 Crypto.Cipher
中的高级接口会更方便和安全。这个模块更多是为库的内部实现提供灵活性,或者为需要自定义链模式的高级用户提供接口。
Crypto.Protocol.HMAC
:HMAC (基于哈希的消息认证码)
虽然 HMAC 的核心功能通常通过 Crypto.Hash.HMAC
来实现,但从协议层面理解,HMAC 是一种结合了哈希函数和秘密密钥来提供消息认证和完整性保护的协议。它确保消息来自指定的发送者且未被篡改。
示例:
from Crypto.Protocol.KDF import PBKDF2 # 使用KDF派生HMAC密钥
from Crypto.Hash import SHA256, HMAC
from Crypto.Random import get_random_bytes
# 从密码派生一个HMAC密钥
password = b"my_auth_password"
salt = get_random_bytes(16)
iterations = 100000
hmac_key = PBKDF2(password, salt, dkLen=32, count=iterations, prf=SHA256)
message_to_authenticate = b"This message needs authentication."
# 创建HMAC并生成消息认证码 (MAC)
h_mac = HMAC.new(hmac_key, digestmod=SHA256)
h_mac.update(message_to_authenticate)
generated_mac = h_mac.hexdigest()
print("\n--- HMAC 协议示例 ---")
print(f"消息: {message_to_authenticate.decode()}")
print(f"生成的 MAC: {generated_mac}")
# 验证 MAC (接收方使用相同的密钥和消息进行验证)
try:
h_mac_verify = HMAC.new(hmac_key, digestmod=SHA256)
h_mac_verify.update(message_to_authenticate)
h_mac_verify.verify(bytes.fromhex(generated_mac))
print("MAC 验证成功!消息未被篡改且来自合法发送方。")
except ValueError:
print("MAC 验证失败!消息可能被篡改或密钥不匹配。")
Crypto.Random
Crypto.Random
模块是 PyCryptodome 库中用于生成密码学安全随机数的核心组件。在密码学中,随机性至关重要——无论是生成加密密钥、初始化向量 (IV)、Nonce 还是在数字签名算法中,都需要高度不可预测和统计学随机的数值。如果随机数生成器存在可预测性或偏离,那么即使是最强的加密算法也可能变得脆弱。
为什么需要密码学安全随机数?
普通的随机数生成器(如 Python 内置的 random
模块)通常基于伪随机数生成器 (PRNG),它们虽然能产生看似随机的序列,但其内部状态是可以通过已知算法和一些输出推断出来的。这对于模拟、游戏等非安全应用是足够的,但在密码学中却是致命的缺陷。
密码学安全伪随机数生成器 (CSPRNG),如 Crypto.Random
提供的,旨在满足以下严格要求:
- 不可预测性:即使知道了生成器之前的输出,也无法预测未来的输出。
- 不可逆性:无法通过当前或未来的输出反推出生成器之前的状态。
- 统计随机性:输出序列应通过各种统计测试,表现出真正的随机性。
Crypto.Random
模块的主要函数
Crypto.Random
模块提供了一个主要的函数来满足大多数密码学随机数的需求:
get_random_bytes(n)
:生成指定长度的随机字节串
这是最常用和最重要的函数。它会返回一个包含 n
个密码学安全的随机字节的字节串。底层实现通常依赖于操作系统提供的熵源(如 /dev/urandom
或 CryptGenRandom
),这些熵源从物理世界(如鼠标移动、键盘输入、磁盘活动等)收集不可预测的数据来生成随机性。
参数:
n
(int):需要生成的随机字节的长度。
返回值:
- 一个包含
n
个随机字节的bytes
对象。
示例:
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES
print("--- 生成密码学安全随机数示例 ---")
# 1. 生成加密密钥
# AES-128 需要 16 字节密钥
aes_key = get_random_bytes(16)
print(f"生成的 AES 密钥 (16 字节): {aes_key.hex()}")
# AES-256 需要 32 字节密钥
aes_key_256 = get_random_bytes(32)
print(f"生成的 AES-256 密钥 (32 字节): {aes_key_256.hex()}")
# 2. 生成初始化向量 (IV)
# CBC 模式的 IV 长度应与块大小相同,AES 块大小是 16 字节
iv = get_random_bytes(AES.block_size)
print(f"生成的 AES CBC 模式 IV (16 字节): {iv.hex()}")
# 3. 生成 Nonce
# GCM 模式的 Nonce 通常是 16 字节(或 96 位/12 字节)
nonce_gcm = get_random_bytes(16)
print(f"生成的 AES GCM 模式 Nonce (16 字节): {nonce_gcm.hex()}")
# 4. 生成其他随机数据
random_data = get_random_bytes(10) # 任意长度的随机数据
print(f"生成的 10 字节随机数据: {random_data.hex()}")
# 实际应用中,使用这些随机数进行加密
key = get_random_bytes(16)
iv = get_random_bytes(16)
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = b"This is a secret message."
# 需要填充,这里简化
padded_plaintext = plaintext + b"\0" * (AES.block_size - len(plaintext) % AES.block_size)
ciphertext = cipher.encrypt(padded_plaintext)
print(f"\n使用随机密钥和 IV 加密后的密文: {ciphertext.hex()}")
Crypto.Random.Fortuna
在 PyCryptodome 内部,Crypto.Random
模块通常会利用一个名为 Fortuna 的密码学安全伪随机数生成器。Fortuna 是一种设计严谨的 CSPRNG,它通过定期从熵源收集新的熵来“再播种”自身,从而确保即使内部状态被泄露,未来生成的随机数也不会受到影响。
作为用户,你通常不需要直接与 Fortuna
模块交互,因为 get_random_bytes()
函数已经封装了所有底层复杂性,并确保返回的是密码学安全的随机数。它会自动处理熵的收集和 Fortuna 生成器的再播种过程。
Crypto.IO
Crypto.IO
模块在 PyCryptodome 库中扮演着一个相对次要但专门的角色,它主要关注安全 I/O 操作。具体来说,它提供了工具来帮助你以更安全的方式从文件中读取数据或向文件中写入数据,尤其是在处理密码学敏感信息时。这个模块旨在解决一些与文件操作相关的常见安全陷阱,例如确保文件内容在处理过程中不会意外地留在内存中。
然而,需要注意的是,Crypto.IO
模块的功能不是为了替代操作系统级别的文件权限管理或加密文件系统。它主要是一种辅助工具,用于处理应用程序层面的数据流。
Crypto.IO.File
:安全地读写文件流
Crypto.IO.File
是 Crypto.IO
模块中的核心组件,它提供了一个类来帮助你更安全地处理文件句柄。这个类的主要设计目标是:
- 内存管理:在处理大文件时,避免一次性将整个文件读入内存,而是通过流式处理,这对于内存受限的环境和处理敏感数据尤其重要。
- 安全覆盖:在关闭文件之前,对用于存储敏感数据(如密钥、明文)的内存区域进行擦除,防止敏感信息残留在内存中被其他程序读取。
File.BlockFile
类
BlockFile
类允许你以固定大小的块来读写文件。这对于需要逐块处理数据(例如,进行块密码加密或解密)的场景非常有用。
主要方法:
__init__(fd, block_size, offset=0)
:fd
: 一个文件描述符(例如file_obj.fileno()
)或一个文件对象。block_size
: 读写操作的块大小(字节)。offset
: 可选,文件开始读取或写入的偏移量。
read_block()
: 从文件中读取一个块的数据。write_block(data)
: 向文件中写入一个块的数据。close()
: 关闭文件句柄。
示例:
from Crypto.IO import File
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
import os
# 1. 准备一些数据写入文件
original_data = b"This is a secret message to be encrypted and written to a file using BlockFile."
key = get_random_bytes(16)
iv = get_random_bytes(16)
cipher = AES.new(key, AES.MODE_CBC, iv)
padded_data = pad(original_data, AES.block_size)
encrypted_data = cipher.encrypt(padded_data)
# 2. 将加密数据写入文件 (使用 BlockFile)
file_name = "encrypted_data.bin"
block_size = AES.block_size # 使用 AES 的块大小作为 BlockFile 的块大小
print("--- 使用 Crypto.IO.File.BlockFile 写入加密数据 ---")
with open(file_name, 'wb') as f_out:
# 创建 BlockFile 实例
block_writer = File.BlockFile(f_out, block_size)
# 逐块写入数据
for i in range(0, len(encrypted_data), block_size):
block = encrypted_data[i : i + block_size]
block_writer.write_block(block)
print(f"加密数据已写入 '{file_name}'")
# 3. 从文件读取加密数据并解密 (使用 BlockFile)
decrypted_chunks = []
print("\n--- 使用 Crypto.IO.File.BlockFile 读取加密数据并解密 ---")
with open(file_name, 'rb') as f_in:
block_reader = File.BlockFile(f_in, block_size)
while True:
block = block_reader.read_block()
if not block: # 文件读取完毕
break
decrypted_chunks.append(cipher.decrypt(block)) # 注意:CBC 解密也需要相同的 cipher 对象
# 4. 组合解密后的块并去除填充
decrypted_padded_data = b''.join(decrypted_chunks)
decrypted_original_data = unpad(decrypted_padded_data, AES.block_size)
print(f"从文件解密后的数据: {decrypted_original_data.decode()}")
print(f"原始数据与解密数据是否匹配: {original_data == decrypted_original_data}")
# 清理:删除生成的文件
if os.path.exists(file_name):
os.remove(file_name)
print(f"已删除文件: {file_name}")