openssl enc 执行 AES 对称加解密,用 openssl genpkey + pkeyutl 生成 RSA 密钥并执行非对称加解密,理解 ECB/CBC/GCM 三种分组模式的安全差异,并用 Python cryptography 库(v46+)实现 Fernet 对称加密。在模块0-2中,你学了 HTTP、DNS、ARP 等协议。这些协议负责"把数据从 A 送到 B",但送的过程中数据是明文的——任何人截获流量就能直接读取内容。密码学解决的是另一个问题:即使数据被截获,攻击者也无法读懂。
加密的核心概念很简单:把可读的明文(plaintext)通过算法变成不可读的密文(ciphertext),接收方再用密钥(key)把密文还原成明文。整个过程像把信件放进一个带锁的铁箱子里寄送——只有持有钥匙的人能打开。
密码学有一条铁律叫 Kerckhoffs 原则:加密算法应该是公开的,安全性只依赖于密钥的保密。也就是说,即使攻击者知道你用的是 AES-256-CBC 加密、知道算法的每一行代码,只要他不知道你的密钥,就无法解密。这就是为什么 AES、RSA 等算法可以公之于众而仍然安全——反过来,依赖"算法保密"来保证安全的系统(所谓 security through obscurity)迟早会被攻破。
对称加密是最古老的加密方式:加密和解密用同一把密钥。就像你家大门的钥匙——同一把钥匙锁门、同一把钥匙开门。通信双方必须事先共享这把密钥,所以叫"对称"。
历史上 DES(Data Encryption Standard)曾是对称加密的标准,但其 56 位密钥在现代算力下几小时内即可暴力破解。2001 年 NIST 发布 AES(Advanced Encryption Standard),支持 128/192/256 位密钥,至今未被有效破解。AES 是当前全世界最广泛使用的对称加密算法——你的 Wi-Fi(WPA2)、HTTPS(TLS)、手机磁盘加密(FileVault/File-Based Encryption)底层都在用它。
AES 是分组密码(block cipher),每次加密一块固定大小的数据(AES 的块大小是 128 位/16 字节)。但实际要加密的数据可能远超 16 字节,这就需要"工作模式"来定义如何把长数据切分成块逐个加密:
| 模式 | 原理 | 安全性 |
|---|---|---|
ECB |
每块独立加密,相同明文块→相同密文块 | 🔴 不安全,泄露模式 |
CBC |
每块与前一块密文异或后再加密,需 IV | 🟡 安全,但无完整性验证 |
GCM |
CBC 变体 + 自带认证标签(AEAD) | 🟢 推荐,加密+完整性 |
ECB 的问题在于:如果两块明文相同,加密后的密文也相同。假设你加密一张图片,ECB 模式下图片的轮廓仍然清晰可见——著名的"ECB 企鹅"就是最好的例子。CBC 通过引入初始向量(IV)和链式异或解决了这个问题。GCM 在 CBC 基础上增加了认证标签,能检测密文是否被篡改,是当前 TLS 1.3 的首选模式。
openssl enc -aes-256-cbc -salt -pbkdf2 -in file.txt -out enc.bin — 加盐 + PBKDF2 密钥派生
你已经理解对称加密的原理和分组模式。现在动手实践。先准备一个测试文件,然后用 openssl 进行 AES-256-CBC 加解密:
密文以 Salted__ 开头——openssl 的 -salt 参数在密文头部嵌入了 8 字节随机盐值,保证相同密码每次加密产生不同密文。解密时输入相同密码即可:
Python 的 cryptography 库(v46+)提供了 Fernet 高级接口,底层使用 AES-128-CBC + HMAC-SHA256,自动处理 IV 生成和完整性验证:
generate_key() 每次生成不同的随机密钥。Fernet 自动在密文中嵌入时间戳和 HMAC 标签,解密时会验证完整性——如果密文被篡改哪怕一个字节,decrypt() 会抛出 InvalidToken 异常。
对称加密有一个致命问题:双方必须事先共享密钥。如果 Alice 和 Bob 从未见过面,如何在不安全的信道上安全地传递密钥?非对称加密(又称公钥密码)解决了这个问题。
非对称加密用两把钥匙:公钥(public key)和私钥(private key)。公钥可以公开给任何人,私钥只有所有者保管。用公钥加密的数据只能用对应的私钥解密。这就像一个信箱——任何人都能把信从投信口塞进去(公钥加密),但只有持有信箱钥匙的主人能打开取信(私钥解密)。
RSA 是最著名的非对称加密算法,由 Rivest、Shamir、Adleman 三人于 1977 年提出。其安全性基于大数分解的数学难题:把两个大质数相乘很容易,但把乘积分解回两个质数极其困难。RSA 使用 2048 位或更长的密钥,目前没有已知的实际破解方法。
用 openssl 生成 RSA 密钥对。注意:OpenSSL 3.x 推荐使用 genpkey 替代旧版 genrsa,使用 pkeyutl 替代已废弃的 rsautl:
现在用公钥加密一段消息,只有持有私钥的人才能解密。注意 OpenSSL 3.x 使用 pkeyutl 而非已废弃的 rsautl:
openssl pkeyutl -encrypt -pubin -inkey public.pem ... — 使用 pkeyutl,支持所有非对称算法
RSA 有一个限制:加密数据长度不能超过密钥长度减去填充开销。2048 位 RSA 最多加密约 190 字节(OAEP 填充后)。这就是为什么 RSA 不直接用于加密大文件——它太慢了,而且有长度限制。实际应用中 RSA 用于加密对称密钥,而非加密数据本身(见第六章混合加密)。
你已经会生成 RSA 密钥对并用公钥加密了。但非对称加密不是唯一解决密钥分发的方法。Diffie-Hellman(DH)密钥交换协议另辟蹊径:让两个人在完全公开的信道上协商出一个共享密钥,即使窃听者记录了所有通信内容也无法推算出这个密钥。
DH 的原理用颜色混合来理解最直观:Alice 和 Bob 公开约定一个基础颜色(比如黄色)。各自选一个秘密颜色(Alice 选红色,Bob 选蓝色),把秘密颜色和基础颜色混合后公开交换(Alice 传出橙色,Bob 传出绿色)。然后各自把自己的秘密颜色加到收到的混合色里——Alice 把红色加到绿色里,Bob 把蓝色加到橙色里——两人最终得到相同的颜色(黄+红+蓝=棕褐色)。窃听者看到了黄色、橙色、绿色,但无法"分离"混合色来还原红色或蓝色。
数学上,DH 使用离散对数问题代替颜色分离:给定质数 p、生成元 g 和公开值 g^a mod p,反推 a 在计算上不可行。DH 是 TLS、SSH、IPSec 等协议中密钥协商的核心——每次你访问 HTTPS 网站时,浏览器和服务器就在用 DH(或其椭圆曲线版本 ECDH)协商会话密钥。
你已经掌握了对称加密(快但密钥分发难)和非对称加密(解决分发但慢且有长度限制)。实际系统不会二选一,而是取长补短——这就是混合加密。
| 特性 | 对称加密 | 非对称加密 |
|---|---|---|
| 速度 | 极快(AES ~1GB/s) | 慢(RSA 约慢 1000 倍) |
| 密钥分发 | 困难(需事先共享) | 简单(公钥可公开) |
| 加密长度 | 无限制 | 有限(RSA ≤190字节) |
混合加密的策略是:用非对称加密协商/传递一个对称密钥,然后用对称密钥加密实际数据。HTTPS(TLS)就是这样做的:
非对称加密只在握手阶段使用一次(慢但只传少量数据),之后全部用对称加密(快且无长度限制)。你在模块0-2第09篇学的 TLS 握手过程,本质上就是这个混合加密策略的实现。
openssl speed aes-256-cbc rsa2048 可以直观对比 AES 和 RSA 的性能差异。AES 通常在每秒 GB 级别,RSA 2048 位每秒只能处理几百次操作——这就是为什么 HTTPS 只在握手阶段用 RSA。openssl enc -aes-256-cbc -salt -pbkdf2 加密,然后用相同密码解密验证内容一致。尝试不加 -salt 加密两次,对比密文是否相同。-salt 时每次密文不同(Salted__ 后跟随机盐),不加 -salt 时相同密码+相同明文产生完全相同的密文,容易被彩虹表攻击。openssl speed rsa2048 测试 RSA 性能,再运行 openssl speed aes-256-cbc 测试 AES 性能,计算两者速度差。openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048 生成密钥,openssl rsa -pubout -in private.pem -out public.pem 提取公钥。speed 测试会显示 AES 每秒处理数百 MB,RSA 每秒仅几百次操作。from cryptography.fernet import Fernet 处理 AES 部分,from cryptography.hazmat.primitives.asymmetric import rsa, padding 处理 RSA 部分。RSA 加密的 AES 密钥通常只有 32 字节,远低于 RSA 的 190 字节上限。这就是 TLS/HTTPS 的核心设计思路。