你是不是觉得只要对数据加密后数据就一定不会被篡改了?那你就大错特错了。
一个意想不到的实验结果
首先,我们来看一个神奇的实验:
from __future__ import annotations
from cryptography.hazmat.primitives.ciphers import Cipher , algorithms , modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
# ==================================================
# AES-CBC-256 核心加解密(非AEAD)
# ==================================================
def aes_cbc_encrypt(plain_data: bytes , key: bytes , iv: bytes) -> bytes:
"""CBC加密(需要PKCS7填充)"""
# 添加填充
padder = padding.PKCS7(128).padder()
padded_data = padder.update(plain_data) + padder.finalize()
# 加密
cipher = Cipher(algorithms.AES(key) , modes.CBC(iv) , backend = default_backend())
encryptor = cipher.encryptor()
return encryptor.update(padded_data) + encryptor.finalize()
def aes_cbc_decrypt(ciphertext: bytes , key: bytes , iv: bytes) -> bytes:
"""CBC解密(无完整性验证)"""
# 解密
cipher = Cipher(algorithms.AES(key) , modes.CBC(iv) , backend = default_backend())
decryptor = cipher.decryptor()
padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()
# 移除填充
unpadder = padding.PKCS7(128).unpadder()
return unpadder.update(padded_plaintext) + unpadder.finalize()
if __name__ == "__main__":
# 固定输入值(实际使用中应为随机值)
plaintext = b"Transfer $100 to Alice"
key = b"12345678901234567890123456789012" # 256-bit key,仅用作测试
iv = b"1234567890123456" # 16 字节 CBC IV
print("===== AES-CBC-256 演示 =====")
print(f'原始明文数据:{plaintext.hex()}')
# CBC加密
cbc_ciphertext = aes_cbc_encrypt(plaintext , key , iv)
print(f"密文: {cbc_ciphertext.hex()}")
# CBC解密(原始数据)
cbc_decrypted = aes_cbc_decrypt(cbc_ciphertext , key , iv)
print(f"对正常的密文解密后的结果: {cbc_decrypted.hex()}, 解密成功:{cbc_decrypted.hex() == plaintext.hex()}")
# 篡改第1个字节(在真实攻击中更隐蔽)
# 如果改末尾的字节则可能会解填充失败,会显式的抛出异常
tampered_ciphertext = bytes([ cbc_ciphertext[ 0 ] ^ 0x01 ]) + cbc_ciphertext[ 1: ]
# 解密被篡改的密文 - 不报错但得到错误结果
tampered_decrypted = aes_cbc_decrypt(tampered_ciphertext , key , iv)
print(
f"篡改后的密文解密出的结果: {tampered_decrypted.hex()},"
f"解密成功: {tampered_decrypted.hex() == plaintext.hex()},",
f"解出的数据错误但解密过程无异常")
猜一猜运行代码后会不会抛出异常?
答案是:不会!!!
运行代码后我们可以得到如下的输出:
看到没,在测试代码中,我们只改动了一个字节:
最后解密出来的结果却大相径庭,并且结果过程没有丝毫的错误提示。
这就是非 AEAD加密模式的致命缺陷:只管加密,不防篡改。(例如AES-CBC 是典型的非 AEAD 加密算法)
为什么加密不等于安全?
AES-CBC等传统加密模式统统都面临着以下的困境:
- 解密不验证完整性 - 就像保险箱没有防伪标签
- 篡改后仍输出数据 - 接收方拿到假货而不自知
- 攻击成本极低 - 黑客只需修改几个字节就能改变语义 金融系统、身份认证、医疗数据... 这些关键系统如果只用传统加密,那么数据被恶意篡改的风险极高。
划重点:什么是AEAD?
AEAD(Authenticated Encryption with Associated Data)的出现彻底改变了游戏规则,它同时做到三件事:
- 保密性 - 数据加密(Encryption)
- 完整性 - 防篡改(Authentication)
- 身份验证 - 防重放攻击(Nonce机制)
AES-GCM 是典型的 AEAD 加密算法
让我们再看一个实验:from __future__ import annotations
from typing import Tuple
from cryptography.hazmat.primitives.ciphers import Cipher , algorithms , modes
from cryptography.hazmat.backends import default_backend
# ==================================================
# AES-GCM-256 核心加解密(AEAD模式)
# ==================================================
def aes_gcm_encrypt(plain_data: bytes , key: bytes , nonce: bytes , ad: bytes) -> Tuple[ bytes , bytes ]:
"""GCM加密(返回密文和认证标签)"""
cipher = Cipher(algorithms.AES(key) , modes.GCM(nonce) , backend = default_backend())
encryptor = cipher.encryptor()
# 添加关联数据认证
if ad:
encryptor.authenticate_additional_data(ad)
# 加密并获取认证标签
ciphertext = encryptor.update(plain_data) + encryptor.finalize()
return ciphertext , encryptor.tag
def aes_gcm_decrypt(ciphertext: bytes , tag: bytes , key: bytes , nonce: bytes , ad: bytes) -> bytes:
"""GCM解密(带完整性验证)"""
cipher = Cipher(algorithms.AES(key) , modes.GCM(nonce , tag) , backend = default_backend())
decryptor = cipher.decryptor()
# 添加关联数据认证
if ad:
decryptor.authenticate_additional_data(ad)
# 解密(如果验证失败会抛出异常)
return decryptor.update(ciphertext) + decryptor.finalize()
if __name__ == "__main__":
# 固定输入值(实际使用中应为随机值)
plaintext = b"Secret message! AEAD is awesome!"
key = b"12345678901234567890123456789012" # 256-bit key 仅用作测试
nonce = b"123456789012" # GCM 12 字节 nonce
ad = b"Additional data" # GCM关联数据
print("\n===== AES-GCM-256 演示 =====")
print(f'原始明文数据: {plaintext.hex()}')
# GCM加密
gcm_ciphertext , tag = aes_gcm_encrypt(plaintext , key , nonce , ad)
print(f"密文数据: {gcm_ciphertext.hex()}")
print(f"认证标签: {tag.hex()}")
# GCM解密(原始数据)
gcm_decrypted = aes_gcm_decrypt(gcm_ciphertext , tag , key , nonce , ad)
print(f"正常的密文解密结果: {gcm_decrypted.decode()}, 解密成功:{gcm_decrypted.hex() == plaintext.hex()}")
# 尝试篡改密文的第一个字节
tampered_gcm_ciphertext = bytes([ gcm_ciphertext[ 0 ] ^ 0x01 ]) + gcm_ciphertext[ 1: ]
# 解密篡改后的密文 - 应该失败
try:
aes_gcm_decrypt(tampered_gcm_ciphertext , tag , key , nonce , ad)
print("解密成功 (不应该发生)")
except Exception as e:
print(f"篡改测试: {e} 解密失败,AEAD检测到密文篡改!")
# 尝试篡改关联数据
try:
aes_gcm_decrypt(gcm_ciphertext , tag , key , nonce , b"tampered_ad")
print("解密成功 (不应该发生)")
except Exception as e:
print(f"AD篡改测试: {e} 解密失败,AEAD检测到AD篡改!")
运行代码后我们得到以下结果:
我们可以清楚的看到,对于正确的密文,可以正确解密出明文;而对于被篡改后的密文,则会捕获解密失败的异常。
这就是 AEAD 的特殊功效:
- 认证标签(Tag):数据的"数字指纹",由密钥+密文生成,哪怕改动1个比特,标签就会失效
- 关联数据(AD):若伪造关联数据,则解密立即失败
- Nonce防御重放攻击:每次加密使用唯一Nonce(编号)防重放攻击
当前支持AEAD的加密算法
目前超过90%的HTTPS流量使用AES-GCM或ChaCha20-Poly1305,而TLS 1.3更是强制使用AEAD技术!
安全不是产品,而是持续的过程。
真正的安全不是相信数据未被篡改,而是能证明它未被篡改。
虽然AEAD提供终极防护,但如果实现不当(如密钥泄漏或随机数重用),仍然可能导致各种数据风险,因此,AEAD不是银弹,因为黑客从不攻击算法本身,而是寻找人犯错留下的缝隙。 |