mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
4800 字
14 分钟
非对称加密:RSA 与 ECC
2026-03-09

当你用 SSH 连接远程服务器时,当你用 git push 推送代码时,当你在浏览器地址栏看到那把小锁图标时——非对称加密正在默默保护你的身份和数据。它的神奇之处在于:即使所有人都知道你的加密方式,没有私钥的人依然无法解密

对称加密 中理解了 AES 与 ChaCha20。对称加密很快,但有一个绕不过去的问题:加密和解密用同一个密钥,双方怎么安全地把密钥传给对方?非对称加密就是为解决这个难题而生的。

一、非对称加密基础#

1.1 公钥与私钥#

非对称加密使用一对密钥:公钥公开,私钥保密:

graph TB subgraph "加密通信" A["Alice"] -->|"用 Bob 公钥加密"| ENC["密文"] ENC -->|"用 Bob 私钥解密"| B["Bob"] end subgraph "数字签名" A2["Alice"] -->|"用 Alice 私钥签名"| SIG["签名"] SIG -->|"用 Alice 公钥验证"| B2["Bob"] end
操作使用的密钥目的
加密接收方公钥只有私钥持有者能解密
解密接收方私钥恢复明文
签名发送方私钥证明身份
验证发送方公钥确认签名

1.2 数学单向函数#

非对称加密的根基是数学单向函数——正向计算容易,逆向求解极难:

  • RSA:大数乘法容易(p×q=np \times q = n),大数分解困难(已知 nnppqq
  • ECC:标量乘法容易(k×G=Pk \times G = P),已知 PPGGkk 极难(ECDLP)
  • DH:模幂运算容易(gamodpg^a \mod p),求离散对数困难
graph LR EASY["正向:容易<br/>p × q → n<br/>k × G → P"] --> ONEWAY["单向函数 "] ONEWAY --> HARD["逆向:极难<br/>n → p, q<br/>P, G → k"] style ONEWAY fill:#fbbf24,stroke:#92400e,color:#000

1.3 完整生命周期#

graph TB KG[" 密钥生成"] --> DIST[" 公钥分发"] KG --> STORE[" 私钥存储<br/>HSM/KMS"] DIST --> ENC[" 加密/签名"] STORE --> DEC[" 解密/验证"] ENC --> TRANSIT[" 不安全信道"] TRANSIT --> DEC DEC --> RESULT["获得明文/验证结果"]

1.4 为什么非对称加密这么慢#

# 对比 RSA 和 AES 的加密速度
# AES-256-GCM 加密 1MB 数据
openssl speed -evp aes-256-gcm -bytes 1048576
# → ~3.5 GB/s
# RSA-2048 签名速度
openssl speed rsa2048
# → ~1000 signs/s (每秒约 1000 次签名)
# 性能差距:AES 比 RSA 快约 3500 倍
# 这就是为什么 TLS 用 RSA/ECDH 交换密钥,用 AES 加密数据
操作类型AES-128RSA-2048ECC P-256速度比
加密 1MB 数据~1ms~200,000ms不适用AES 快 200,000x
密钥交换不适用~2ms~2ms
签名不适用~2ms~1ms
Important

非对称加密从不直接加密大量数据。实际系统使用混合加密:非对称加密协商对称密钥,对称密钥加密数据。这就是 TLS、PGP、SSH 的工作方式。

1.5 两大算法家族#

维度RSAECC
数学基础大数分解椭圆曲线离散对数
密钥长度2048-4096 位256-521 位
性能快(10-100x)
密钥大小
时代1977 年1985 年

二、RSA 算法#

2.1 RSA 密钥生成:逐步推导#

第一步:选择两个大素数 p, q
p = 61, q = 53(教学用小素数,实际使用 1024 位素数)
第二步:计算模数 n
n = p × q = 61 × 53 = 3233
第三步:计算欧拉函数 φ(n)
φ(n) = (p-1)(q-1) = 60 × 52 = 3120
第四步:选择公钥指数 e
e = 17,满足 gcd(e, φ(n)) = 1
通常选择 65537(0x10001)
第五步:计算私钥指数 d
d = e⁻¹ mod φ(n) = 17⁻¹ mod 3120 = 2753
验证:e × d mod φ(n) = 17 × 2753 mod 3120 = 1
公钥 = (n=3233, e=17)
私钥 = (n=3233, d=2753)

2.2 RSA 加密与解密:小数示例#

用上面的密钥加密消息 m = 65(ASCII ‘A’):

加密:c = m^e mod n = 65^17 mod 3233 = 2790
解密:m = c^d mod n = 2790^2753 mod 3233 = 65
签名:s = m^d mod n
验证:m' = s^e mod n → m' == m
Note

实际 RSA 中消息必须先经过填充(padding)处理。上例省略了填充步骤仅用于教学演示。

2.3 RSA 的安全基础#

RSA 的安全性基于大整数分解问题

攻击思路说明难度
分解 n → 得到 p, q直接分解模数极难(2048 位约 2^112 次运算)
已知 φ(n) → 求 d等价于分解 n同上
已知 d → 分解 n可从 d 反推 p, q存在高效算法

目前最大 RSA 分解记录是 RSA-250(829 位),于 2020 年完成,耗费约 2700 CPU 核心年。2048 位 RSA 当前安全,但量子计算中的 Shor 算法可在多项式时间内分解大数——这是后量子密码学的研究动机。

2.4 RSA 填充方案#

填充方案安全性说明适用场景
PKCS#1 v1.5不安全易受 Bleichenbacher 攻击仅兼容旧系统
OAEP安全最优非对称加密填充加密
PSS安全概率签名方案签名
Warning

永远不要使用 PKCS#1 v1.5 填充——Bleichenbacher 攻击可以通过 Padding Oracle 恢复明文。加密使用 OAEP,签名使用 PSS。

OAEP 通过引入随机性,使相同明文每次加密产生不同密文,杜绝确定性加密带来的信息泄露。

2.5 RSA 密钥长度选择#

安全等级RSA 最小长度NIST 建议有效期备注
80 位1024 位已不安全2010 年后不再推荐
112 位2048 位到 2030 年当前最低要求
128 位3072 位2030+新系统推荐
192 位7680 位长期高安全场景
256 位15360 位最高安全极端场景
Tip

新系统建议直接使用 RSA-3072 或转向 ECC。Let’s Encrypt 从 2020 年起默认签发 ECC 证书。

2.6 RSA 代码示例#

# Python: RSA 完整操作——密钥生成、加密、签名
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization
# 1. 生成 RSA 密钥对(推荐 3072 位)
private_key = rsa.generate_private_key(public_exponent=65537, key_size=3072)
public_key = private_key.public_key()
# 2. 加密(OAEP 填充)
message = b"Hello, RSA!"
ciphertext = public_key.encrypt(
message,
padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(), label=None)
)
# 3. 解密
plaintext = private_key.decrypt(
ciphertext,
padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(), label=None)
)
assert plaintext == message
# 4. 签名(PSS 填充)
signature = private_key.sign(
message,
padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH),
hashes.SHA256()
)
# 5. 验证签名
public_key.verify(
signature, message,
padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH),
hashes.SHA256()
)

2.7 RSA 性能与混合加密#

操作RSA-2048RSA-3072RSA-4096
密钥生成~1s~5s~10s
加密~0.1ms~0.3ms~0.5ms
解密~2ms~7ms~10ms
签名~2ms~7ms~10ms
验证~0.1ms~0.3ms~0.5ms

RSA 不适合加密大量数据——通常用于加密对称密钥(混合加密):

graph LR MSG["大量数据"] -->|"AES-GCM<br/>(对称加密)"| ENC["加密数据"] KEY["对称密钥"] -->|"RSA-OAEP<br/>(非对称加密)"| ENC_KEY["加密密钥"] ENC --> COMBINED["加密数据 + 加密密钥"] ENC_KEY --> COMBINED

三、椭圆曲线密码学(ECC)#

3.1 为什么 ECC 密钥更短#

ECC 的底层数学问题难度随密钥长度指数级增长,而 RSA 只能亚指数级增长:

达到 128 位安全:RSA 需要 3072 位,ECC 只需 256 位(12:1)
达到 256 位安全:RSA 需要 15360 位,ECC 只需 521 位(29:1)

密钥越长,ECC 的优势越明显。

3.2 椭圆曲线数学基础#

椭圆曲线方程:y2=x3+ax+by^2 = x^3 + ax + b

  • 点加法:曲线上 P + Q = R,几何意义是连接 P、Q 的直线与曲线的第三个交点关于 x 轴的对称点
  • 标量乘法k×P=P+P++Pk \times P = P + P + \ldots + P(k 次),使用倍加算法只需 O(logk)O(\log k) 次运算
  • ECDLP:已知 P=k×GP = k \times G,求 kk 极难——这就是 ECC 的安全基础
标量乘法示例(倍加法):
计算 13 × P:13 = 1101(二进制)
P → 2P → 4P → 8P(逐次倍加)
13P = 8P + 4P + P(按二进制位累加)
只需 log₂(13) ≈ 4 次运算,而非 13 次
Note

实际运算在有限域(素数域 )上进行,点的坐标是整数对 ,其中 。

3.3 常用曲线对比#

曲线密钥长度安全等级设计者特点典型用途
P-256256 位128 位NIST最广泛支持TLS 证书、代码签名
P-384384 位192 位NIST更高安全等级政府级应用
Curve25519256 位128 位Bernstein高性能、安全实现密钥交换(ECDH)
Ed25519256 位128 位Bernstein确定性签名数字签名(SSH、Git)
Ed448448 位224 位Hamburg更高安全等级高安全签名
Tip

Curve25519/Ed25519 是 Bernstein 设计的”安全曲线”,避免了 NIST 曲线的实现陷阱(如不确定的随机数种子来源)。新项目推荐优先使用。

3.4 ECDH 密钥交换#

sequenceDiagram participant A as Alice participant B as Bob Note over A,B: 公共参数:椭圆曲线基点 G A->>A: 生成私钥 a,公钥 A = a×G B->>B: 生成私钥 b,公钥 B = b×G A->>B: 发送公钥 A B->>A: 发送公钥 B Note over A: 窃听者截获 A,B<br/>也无法计算 a 或 b A->>A: S = a×B = a×b×G B->>B: S = b×A = b×a×G Note over A,B: S 相同!用于派生对称密钥
# Python: ECDH 密钥交换(Curve25519)
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
alice_private = X25519PrivateKey.generate()
bob_private = X25519PrivateKey.generate()
# 交换公钥,计算共享密钥
alice_shared = alice_private.exchange(bob_private.public_key())
bob_shared = bob_private.exchange(alice_private.public_key())
assert alice_shared == bob_shared
# 从共享密钥派生对称密钥(不要直接使用原始共享密钥!)
derived_key = HKDF(
algorithm=hashes.SHA256(), length=32,
salt=None, info=b"handshake data",
).derive(alice_shared)

3.5 ECDSA 数字签名#

签名过程:

1. h = Hash(message)
2. 生成随机数 k(关键!k 泄露 = 私钥泄露)
3. (x₁, y₁) = k × G
4. r = x₁ mod n
5. s = k⁻¹ × (h + r × d) mod n(d 为私钥)
6. 签名 = (r, s)

验证过程:

1. h = Hash(message)
2. w = s⁻¹ mod n
3. (x₁, y₁) = (h × w) × G + (r × w) × Q(Q 为公钥)
4. r ≡ x₁ mod n → 签名有效
# Python: ECDSA 签名与验证
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
message = b"Important document"
private_key = ec.generate_private_key(ec.SECP256R1())
signature = private_key.sign(message, ec.ECDSA(hashes.SHA256()))
public_key = private_key.public_key()
public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))

3.6 EdDSA 签名(Ed25519)#

EdDSA 的核心改进是确定性签名——不需要随机数:

# Python: Ed25519 签名(推荐用于新系统)
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
private_key = Ed25519PrivateKey.generate()
signature = private_key.sign(b"Important document")
private_key.public_key().verify(signature, b"Important document")
维度ECDSAEdDSA
曲线P-256 等Ed25519
随机数需要(k 泄露=私钥泄露)确定性(无需随机数)
性能
签名长度64 字节64 字节
推荐兼容性优先新系统优先
Warning

2010 年 Sony PS3 被破解,就是因为签名时使用了固定的 k 值,攻击者从两个签名中反推出私钥。EdDSA 通过确定性派生 k 值彻底消除了这个风险。

四、密钥交换协议#

4.1 Diffie-Hellman 密钥交换#

DH 是非对称密码学最优雅的发明——让两个从未谋面的人在不安全信道上协商出共享密钥:

sequenceDiagram participant A as Alice participant E as 公共参数 participant B as Bob E->>A: 大素数 p, 生成元 g E->>B: 大素数 p, 生成元 g A->>A: 选 a,计算 A = g^a mod p B->>B: 选 b,计算 B = g^b mod p A->>B: 发送 A B->>A: 发送 B A->>A: S = B^a = g^(ab) mod p B->>B: S = A^b = g^(ab) mod p Note over A,B: 窃听者只有 g, p, g^a, g^b<br/>无法计算 g^(ab)

DH 的数学保证:已知 gag^agbg^b 计算 gabg^{ab}CDH 问题,目前没有高效算法。

4.2 为什么需要临时密钥(Ephemeral)#

静态 DH 密钥交换的致命问题:私钥一旦泄露,所有历史通信都可被解密。前向安全性(Forward Secrecy)要解决的就是这个问题。

模式缩写前向安全说明
静态 DHDH密钥固定,泄露后历史通信可解密
临时 DHDHE每次生成新密钥对,泄露不影响历史
静态 ECDHECDH椭圆曲线版本,同上
临时 ECDHECDHETLS 1.3 强制使用
Important

前向安全意味着”即使今天的密钥泄露,昨天的通信仍然安全”。TLS 1.3 强制使用 ECDHE,不再支持静态密钥交换。

4.3 密钥交换在 TLS 中的应用#

TLS 1.3 握手精简为 1-RTT,核心就是 ECDHE:

sequenceDiagram participant C as Client participant S as Server C->>S: ClientHello + key_share (ECDHE 公钥) S->>C: ServerHello + key_share + 证书 + 签名 Note over C,S: 双方计算共享密钥<br/>后续通信使用对称加密 C->>S: Finished (加密) S->>C: Finished (加密)
密钥交换组曲线安全等级推荐度
x25519Curve25519128 位首选
secp256r1P-256128 位兼容
secp384r1P-384192 位高安全
ffdhe2048DH 2048 位112 位不推荐

五、RSA vs ECC 全面对比#

5.1 性能基准#

操作RSA-2048RSA-3072RSA-4096P-256Curve25519
密钥生成1000ms5000ms10000ms1ms0.5ms
加密/验证0.1ms0.3ms0.5ms
解密/签名2ms7ms10ms1ms0.5ms
密钥交换2ms0.3ms

5.2 安全等级与密钥长度映射#

安全等级对称密钥RSAECCDH有效期
80 位8010241601024已不安全
112 位11220482242048到 2030
128 位128307225630722030+
192 位19276803847680长期
256 位2561536052115360最高安全

5.3 传输开销对比#

操作RSA-2048RSA-4096P-256Curve25519
公钥大小256 字节512 字节64 字节32 字节
签名大小256 字节512 字节64 字节64 字节
密文开销256 字节512 字节

ECC 的密钥和签名体积远小于 RSA,在物联网、移动端等带宽受限场景优势明显。

5.4 迁移建议#

场景当前方案推荐方案优先级
TLS 证书RSA-2048ECC P-256/Ed25519
SSH 密钥RSA-2048Ed25519
代码签名RSA-2048ECC P-256
API 认证RSA-2048Ed25519
邮件加密RSA-2048Curve25519 + Ed25519
Tip

迁移时注意兼容性:部分旧客户端不支持 ECC 证书。建议过渡期同时支持 RSA 和 ECC 证书(双证书部署)。

六、非对称加密在真实系统中的应用#

6.1 SSH 身份认证#

每次 ssh user@server 背后都在使用非对称加密:

# 生成 Ed25519 SSH 密钥(推荐)
ssh-keygen -t ed25519 -C "your@email.com"
# 生成 RSA SSH 密钥(兼容旧系统)
ssh-keygen -t rsa -b 4096 -C "your@email.com"
sequenceDiagram participant C as SSH Client participant S as SSH Server C->>S: 连接请求 S->>C: 发送服务器公钥 Note over C: 验证服务器指纹 C->>S: ECDHE 密钥交换 Note over C,S: 建立加密通道 C->>S: 用私钥签名挑战值 S->>S: 用 authorized_keys 验证 Note over C,S: 认证成功!

6.2 Git 提交签名#

# 配置 Git 使用 SSH 签名
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
# 签名提交与标签
git commit -S -m "Signed commit"
git tag -s v1.0.0 -m "Signed release"
# 验证签名
git verify-commit HEAD
git verify-tag v1.0.0

6.3 代码签名与软件分发#

# Python: 使用 Ed25519 对软件包签名
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
signing_key = Ed25519PrivateKey.generate()
verify_key = signing_key.public_key()
with open("release-v1.0.tar.gz", "rb") as f:
package_data = f.read()
signature = signing_key.sign(package_data)
# 用户验证
try:
verify_key.verify(signature, package_data)
print("签名验证通过,未被篡改")
except Exception:
print("签名验证失败,文件可能被篡改!")

六·附、实践:RSA 与 ECC 密钥生成与加密#

在前面六节中,从理论层面理解了非对称加密的数学基础、算法原理和性能对比。但密码学从来不是纸上谈兵的学科——只有亲手生成密钥、加密解密、观察输出,才能真正建立对安全工程的直觉。本节将带你用 OpenSSL 和 Python 逐步完成 RSA 与 ECC 的核心操作。

在动手之前,先回顾这段技术演进的历史脉络。

对称加密体系有一个绕不过去的困境:加密和解密使用同一个密钥,双方必须先安全地共享密钥才能通信。但在开放网络中,从未谋面的两个人如何安全地传递密钥?这就是著名的密钥分发问题。1976 年,Whitfield Diffie 和 Martin Hellman 在划时代论文《New Directions in Cryptography》中首次提出了公钥密码学的概念——使用一对密钥,公钥加密、私钥解密,从根本上解决了密钥分发困境。这一思想被誉为”密码学史上最伟大的革命”。

仅仅一年后的 1977 年,MIT 的 Ron Rivest、Adi Shamir 和 Leonard Adleman 提出了 RSA 算法——第一个可用的公钥加密方案。RSA 基于大整数分解的困难性,用简单的数学(模幂运算)实现了惊人的安全保证。此后二十年间,RSA 几乎成为非对称加密的代名词,广泛部署于 SSL/TLS、PGP、SSH 等协议中。

然而 RSA 的密钥长度随安全等级增长过快:128 位安全需要 3072 位密钥,256 位安全则需要 15360 位。1985 年,Neal Koblitz 和 Victor Miller 各自独立提出了椭圆曲线密码学(ECC)——用椭圆曲线上的离散对数问题替代大数分解,以更短的密钥达到同等安全等级。进入 21 世纪,Daniel J. Bernstein 设计的 Curve25519 和 Ed25519 进一步解决了 NIST 曲线的实现陷阱问题,凭借高性能、安全实现和抗侧信道攻击的特性,成为现代密码学的首选。今天,TLS 1.3 强制使用 ECDHE,SSH 默认推荐 Ed25519,Let’s Encrypt 默认签发 ECC 证书——ECC 已成为非对称加密的主流。

附.1 前置知识#

  • OpenSSL 3.0+ 命令行工具(本文基于 OpenSSL 3.0.13)
  • Python 3.8+ + cryptography 库(本文基于 Python 3.13.0 + cryptography 43.0.3)
  • 理解本章前六节的理论内容,特别是 RSA 填充方案(OAEP)、ECDH 密钥交换和混合加密的概念

注意:本节所有密钥均为演示用途生成,绝不可用于生产环境。演示私钥已完整展示以便理解格式,生产环境中私钥必须严格保护(权限 600、存储于 HSM/KMS)。

附.2 RSA 密钥生成与加密解密#

在第二章中学习了 RSA 的数学原理:密钥生成的核心是选择两个大素数 ppqq,计算模数 n=p×qn = p \times q。OpenSSL 将这些步骤封装为一条命令,实际操作一遍。

1.1 生成 RSA-2048 密钥对#

# 生成 RSA-2048 私钥(PKCS#8 格式)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out rsa_private.pem
..................................................................................................+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*..+.+......+......+...+.....+....+..+...+......+.+........+.+......+......+..+...+...+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*........+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.....+...+........+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*..+.+......+...+..+.......+..+.+..+...+.........+...+......+.+...+......+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*..+..............+...+.+.....+......+.+.....+...+....+..+......+...+....+......+.................+.........+...+.+..+........................+.........+.......+...+..+....+...+..+......+...+.......+............+.....+................+.....+.+........+...............+.............+..+.+..+....+......+.....+.............+.....+..........+..............+.+............+..+...+..........+........+.+........+.+..+...+.+...+..+.........+.+.....+.+...+.........+.....+.+...........+...+......................+......+..+.+.....+.+....................+...+..........+...+.....+.......+.....+............+...+.......+..............+.........+......+......+..................+...+.........+.+...+........................+..+.............+...+...+..+....+...+...........+.......+.....+...+.+......+.....+.......+...........+....+..+.+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# 从私钥导出公钥
openssl rsa -in rsa_private.pem -pubout -out rsa_public.pem
writing RSA key

查看生成的密钥文件内容:

# 查看私钥(演示用途,生产环境切勿展示!)
cat rsa_private.pem
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDLQtG6smr5pJMN
hUYc8QlHRjLD/gEyzm5OA7QSoBfpgS7aH8RgPTyGyHadkwBeARIh6h3REttlh3un
CPEiltDV3EH1IS9lubbB4eglfuN7tPpqyZ118m7nzyF1IsCJ+c3TGrXW+q3LjLXK
NW9ea/S1ufhhkvkMQ3TZtX5ODIgZGtybY2h5hc3A+FH5i6z6dbC42GNzesp96jWU
3cSVeo/19Wu5Bdy2E6hEsdsynyCs7gB7B60cVsX8WBzERDzmXUzrOQHwas/pT28d
LUboYbddhhRaTQA1WvihacYEL7dru+6OysgZ94ZRUxAjK+l9FtSF9+VdfsuNdnET
6sNpo0VNAgMBAAECggEACguwX+DHhrUKi4WFtJJ5GeuvQJ6CIqevLe3i7XS6GdFH
poIha3w7TJIFwr3sK7kz0zdF5/pdm4P5qYS4njQD+D3diKbuN8HY9XYbyhaeWZPV
OIfkX1ru/kfvaGGiUG9nK8LLL+fNxRycM3U2nt0JCB47116zLlb7JHNi3aqi41Ko
69UrtFHRRz7Gfw6wP2nVyvx940RI4qHsKaRaBw5QDTCBTD8Q4+5xeqxSoxjUNR60
tjmyr6MhCwKCcuoEFGrbnlqgJsDuYUZFDJBt+lI6fAGkOpm1AlqI6h35pG0NbNz3
4xUVfFkZ4Ri40udpShs9Z2XMquebbCPmN4s5UiyPoQKBgQD3ExhTQ31SpLxadWnF
u2WI8gI5N9+uk7/nr831J3fNf63yQLjIMwhMx/9/quGsc8yNp9qGnJMACtSWfm9z
JeQSic21A2nBEgz44HlUf0nktnIQwJtS1rD8aOem9Cn8LcO/CobsG0PJrPwOxSfe
2ZOhv6eTeFMFHrfhNwMN0+muOQKBgQDSmosjotNSKg+6Ce1R292OOPFxTlLvvV3a
OCryLHZhwqnqbt3xk+6OODAAXiFA2nOUvLKSR1jcnbDvMziIbumdjg47+2HeDxZV
czNPsY2ul+SnB3ssOA0TfCwl/HIGBHqJdNruxYMcf3YStLNUawnWLBjUuXEkvk8X
iLI2TePPtQKBgA3PPGnS+Ns4p+pqa5oIqlkoJaWrEM6CJZS+h8VvCkwrGb7TjUBr
Uchw2T/6fhiUdFdqjVU+xRyYfl9W+8W01XWPzDaU1UFcT+tNKJrnbmrhI6j6CvRr
WYMSHfh/5yTYLfusi/QAZh6Mk54DuQHEqXK98pDUhddgayikWYBZWtNJAoGBAL1x
VwfPR9f5DfyDQX8JD9sHTehmVCRjazEIpVkCtdpqiP3gjLTqJQS5djRed/smhiGd
O+RpWX3aO3Zv1MKlQm//gL5j+j0NxUFIRzfAt3n+GChHinzUTq7gW7Yvx0Gydad/
/hAaiz3vSf65nTaqskZ9lDiGN0ieB/dbdzNyDSKhAoGBAMj7YE9rv9Aci+nkjAy7
2mq1NiGg7J1ogS/JRzAStqpwvJZHbqQv6H2xF573WRSwOSC2pZJmztfC0w8MmF/B
L9Kxxoob6HxWyI9LX6ffxeFyYOtna324PFDNchz/HGWtNl/VFrb76j0TUsnZdWZI
ea0jM/aYtE6vRxPwL08TB+WR
-----END PRIVATE KEY-----
# 查看公钥
cat rsa_public.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0LRurJq+aSTDYVGHPEJ
R0Yyw/4BMs5uTgO0EqAX6YEu2h/EYD08hsh2nZMAXgESIeod0RLbZYd7pwjxIpbQ
1dxB9SEvZbm2weHoJX7je7T6asmddfJu588hdSLAifnN0xq11vqty4y1yjVvXmv0
tbn4YZL5DEN02bV+TgyIGRrcm2NoeYXNwPhR+Yus+nWwuNhjc3rKfeo1lN3ElXqP
9fVruQXcthOoRLHbMp8grO4AewetHFbF/FgcxEQ85l1M6zkB8GrP6U9vHS1G6GG3
XYYUWk0ANVr4oWnGBC+3a7vujsrIGfeGUVMQIyvpfRbUhfflXX7LjXZxE+rDaaNF
TQIDAQAB
-----END PUBLIC KEY-----
# 查看密钥详细信息
openssl rsa -in rsa_private.pem -text -noout | head -3
Private-Key: (2048 bit, 2 primes)
modulus:
00:cb:42:d1:ba:b2:6a:f9:a4:93:0d:85:46:1c:f1:

注:genpkey 生成的是 PKCS#8 格式私钥(BEGIN PRIVATE KEY),兼容性优于旧版 genrsa 生成的 PKCS#1 格式(BEGIN RSA PRIVATE KEY)。新项目推荐统一使用 PKCS#8。

1.2 使用 RSA-OAEP 加密与解密#

正如第二章所述,RSA 加密必须使用 OAEP 填充——PKCS#1 v1.5 已被证明不安全。下面用刚才生成的密钥对加密一条消息:

# 准备明文
echo "Hello, RSA-OAEP!" > plaintext.txt
# 用公钥加密(RSA-OAEP + SHA-256)
openssl pkeyutl -encrypt \
-pubin -inkey rsa_public.pem \
-in plaintext.txt \
-out ciphertext.bin \
-pkeyopt rsa_padding_mode:oaep \
-pkeyopt rsa_oaep_md:sha256
# 查看密文信息
wc -c ciphertext.bin
xxd -l 64 ciphertext.bin
256 bytes
00000000: 2c37 3e58 3396 385e b2fa bbc8 3718 37e9 ,7>X3.8^....7.7.
00000010: 2a9e 46e2 087f 8713 7e8f 55b6 70e9 9ce2 *.F.....~.U.p...
00000020: 9763 2493 6558 b6ed 95bc cbe2 35d2 8a83 .c$.eX......5...
00000030: e714 2716 ea8b a184 69e9 af38 01f1 e7f7 ..'.....i..8....

注:RSA-2048 的密文固定为 256 字节(2048 位),无论明文多短。这就是 RSA 不适合加密大量数据的原因之一。

# 用私钥解密
openssl pkeyutl -decrypt \
-inkey rsa_private.pem \
-in ciphertext.bin \
-out decrypted.txt \
-pkeyopt rsa_padding_mode:oaep \
-pkeyopt rsa_oaep_md:sha256
# 验证解密结果
cat decrypted.txt
Hello, RSA-OAEP!

解密成功!回顾一下刚才的操作流程:公钥加密 → 产生 256 字节密文 → 私钥解密 → 恢复原始明文。这正是第一章中”加密通信”模式的实际体现。

附.3 ECC 密钥生成#

第三章中了解到,ECC 以更短的密钥达到与 RSA 同等的安全等级。用 OpenSSL 生成一条 ECC 密钥,直观感受密钥大小的差异。

# 生成 ECC 密钥(P-256 曲线,即 prime256v1)
openssl ecparam -name prime256v1 -genkey -noout -out ecc_private.pem
# 从私钥导出公钥
openssl ec -in ecc_private.pem -pubout -out ecc_public.pem
read EC key
writing EC key
# 查看 ECC 私钥详情
openssl ec -in ecc_private.pem -text -noout
Private-Key: (256 bit)
priv:
b5:2a:6f:b0:ce:7d:67:a9:16:85:20:00:c8:6f:8a:
44:c6:0c:74:4c:06:e9:c4:a3:94:28:b4:6e:c7:ec:
4d:54
pub:
04:ec:2f:8b:ff:66:2c:c6:9c:a6:b2:f0:f8:79:2d:
5a:51:7a:13:d1:70:3d:4d:f4:b8:49:41:4a:a3:78:
c3:2b:98:7a:80:3d:dd:a2:49:37:d1:49:c6:03:ad:
66:d5:f3:45:ee:4f:fe:d8:f9:de:49:31:92:31:55:
c5:5c:9c:34:57
ASN1 OID: prime256v1
NIST CURVE: P-256
# 查看 ECC 公钥
cat ecc_public.pem
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7C+L/2YsxpymsvD4eS1aUXoT0XA9
TfS4SUFKo3jDK5h6gD3dokk30UnGA61m1fNF7k/+2PneSTGSMVXFXJw0Vw==
-----END PUBLIC KEY-----

现在对比 RSA 和 ECC 的密钥文件大小:

echo "RSA 私钥: $(wc -c < rsa_private.pem) bytes"
echo "RSA 公钥: $(wc -c < rsa_public.pem) bytes"
echo "ECC 私钥: $(wc -c < ecc_private.pem) bytes"
echo "ECC 公钥: $(wc -c < ecc_public.pem) bytes"
RSA 私钥: 1704 bytes
RSA 公钥: 451 bytes
ECC 私钥: 227 bytes
ECC 公钥: 178 bytes

ECC 的私钥仅为 RSA 的 1/7.5,公钥仅为 RSA 的 1/2.5——这就是第三章”为什么 ECC 密钥更短”的直观证据。在物联网设备、移动端等带宽和存储受限的场景中,这个差异意义重大。

注:ECC(P-256)与 RSA-2048 提供相近的安全等级(约 112-128 位),但 ECC 的密钥体积小得多。如果需要 128 位安全,RSA 需要 3072 位密钥,而 ECC 只需 256 位。

附.4 ECDH 密钥交换#

第四章中学习了 Diffie-Hellman 密钥交换的原理和 ECDHE 在 TLS 中的核心地位。现在用 Python 的 cryptography 库实际演示 ECDH 密钥交换过程——Alice 和 Bob 如何在不安全的信道上协商出共享密钥:

from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives import serialization
# Alice 生成密钥对
alice_private = X25519PrivateKey.generate()
alice_public = alice_private.public_key()
# Bob 生成密钥对
bob_private = X25519PrivateKey.generate()
bob_public = bob_private.public_key()
# 序列化公钥用于传输
alice_pub_bytes = alice_public.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
bob_pub_bytes = bob_public.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
print(f"Alice 公钥 (hex): {alice_pub_bytes.hex()}")
print(f"Bob 公钥 (hex): {bob_pub_bytes.hex()}")
print(f"公钥长度: {len(alice_pub_bytes)} bytes")
print()
# ECDH 密钥交换
alice_shared = alice_private.exchange(bob_public)
bob_shared = bob_private.exchange(alice_public)
print(f"Alice 计算的共享密钥 (hex): {alice_shared.hex()}")
print(f"Bob 计算的共享密钥 (hex): {bob_shared.hex()}")
print(f"共享密钥长度: {len(alice_shared)} bytes")
print(f"双方共享密钥一致: {alice_shared == bob_shared}")

运行结果:

Alice 公钥 (hex): b6f855339e6ecd37f1d5d1062610e98c35ce49054fa97a9f7a31c543453d6417
Bob 公钥 (hex): dbc96c20304a67eab400103fe2a282ef7b428f79f61bfaf40750684a60ab192b
公钥长度: 32 bytes
Alice 计算的共享密钥 (hex): f097fc615fb6d46459fe9d670e9187cf24f345e2418a7c0e15c4286f67ff6525
Bob 计算的共享密钥 (hex): f097fc615fb6d46459fe9d670e9187cf24f345e2418a7c0e15c4286f67ff6525
共享密钥长度: 32 bytes
双方共享密钥一致: True

注意几个关键点:

  • Curve25519 的公钥仅 32 字节,比 P-256 的 64 字节(未压缩)还短,比 RSA-2048 的 256 字节更是小了一个数量级
  • 双方从未传输私钥,仅交换了公钥,却计算出了相同的共享密钥——这就是 ECDH 的数学魔法
  • 每次运行结果不同,因为密钥对是临时生成的(Ephemeral),这正是前向安全的保证

注意:原始共享密钥不应直接用作对称加密密钥,必须经过 KDF(如 HKDF)派生。直接使用原始共享密钥可能导致微妙的安全问题——下一节的混合加密演示将展示正确的做法。

附.5 混合加密:ECDH + AES-GCM#

第一章中强调了”非对称加密从不直接加密大量数据”,实际系统使用混合加密。第五章的对比也表明,RSA/ECC 的价值在于密钥协商,而非数据加密。现在把 ECDH 和 AES-GCM 组合起来,实现一个完整的混合加密流程——这正是 TLS、PGP、Signal 等协议的核心工作方式:

import os
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
# ============================================================
# 第一阶段:ECDH 密钥交换(协商对称密钥)
# ============================================================
print("=" * 60)
print("第一阶段:ECDH 密钥交换")
print("=" * 60)
# Alice 生成临时密钥对
alice_private = X25519PrivateKey.generate()
alice_public = alice_private.public_key()
# Bob 生成临时密钥对
bob_private = X25519PrivateKey.generate()
bob_public = bob_private.public_key()
alice_pub_bytes = alice_public.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
bob_pub_bytes = bob_public.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
print(f"Alice 公钥: {alice_pub_bytes.hex()}")
print(f"Bob 公钥: {bob_pub_bytes.hex()}")
print()
# 双方交换公钥,计算共享密钥
alice_raw_shared = alice_private.exchange(bob_public)
bob_raw_shared = bob_private.exchange(alice_public)
assert alice_raw_shared == bob_raw_shared
print(f"原始共享密钥: {alice_raw_shared.hex()}")
print()
# 使用 HKDF 从共享密钥派生 AES-256 密钥
# 注意:永远不要直接使用原始共享密钥!
hkdf = HKDF(
algorithm=hashes.SHA256(),
length=32, # AES-256 需要 32 字节密钥
salt=None,
info=b"hybrid-encryption-demo",
)
aes_key = hkdf.derive(alice_raw_shared)
print(f"HKDF 派生的 AES-256 密钥: {aes_key.hex()}")
print()
# ============================================================
# 第二阶段:AES-GCM 加密数据
# ============================================================
print("=" * 60)
print("第二阶段:AES-GCM 对称加密")
print("=" * 60)
plaintext = b"Hello, hybrid encryption! This message is protected by ECDH + AES-256-GCM."
print(f"明文: {plaintext.decode()}")
print(f"明文长度: {len(plaintext)} bytes")
print()
# Alice 使用派生密钥加密
aesgcm = AESGCM(aes_key)
nonce = os.urandom(12) # AES-GCM 推荐 12 字节 nonce
ciphertext = aesgcm.encrypt(nonce, plaintext, None)
print(f"Nonce (hex): {nonce.hex()}")
print(f"密文+Tag (hex): {ciphertext.hex()}")
print(f"密文+Tag 长度: {len(ciphertext)} bytes")
print(f" 其中密文: {len(plaintext)} bytes")
print(f" 其中认证标签: 16 bytes (AES-GCM 固定)")
print()
# Bob 使用相同的派生密钥解密
aesgcm_bob = AESGCM(aes_key)
decrypted = aesgcm_bob.decrypt(nonce, ciphertext, None)
print(f"Bob 解密结果: {decrypted.decode()}")
print(f"解密成功: {decrypted == plaintext}")
print()
# ============================================================
# 篡改检测演示
# ============================================================
print("=" * 60)
print("篡改检测:AES-GCM 认证标签验证")
print("=" * 60)
# 模拟密文被篡改
tampered = bytearray(ciphertext)
tampered[0] ^= 0xFF # 翻转第一个字节
try:
aesgcm_bob.decrypt(nonce, bytes(tampered), None)
print("错误:篡改密文未被检测到!")
except Exception as e:
print(f"检测到篡改!解密失败: {type(e).__name__}")
print("AES-GCM 的认证标签成功阻止了篡改攻击。")

运行结果:

============================================================
第一阶段:ECDH 密钥交换
============================================================
Alice 公钥: 18709306e16fb52c2656efa7d929d41163127ace3ddcf7a1dda5f2d1d9a68377
Bob 公钥: a723dd1fb47cf2a0e78718dbbd88142d5d6d1d6684f3508f02cf599f47faa303
原始共享密钥: 605b03e077ae960c3dbf2f52088fe1b49ac73788f1b9828ccb1dfdc5b5b65c25
HKDF 派生的 AES-256 密钥: e5cf9be844f2ff60984982a23ba813e36ae8b3666282fb693890d1523c48df3f
============================================================
第二阶段:AES-GCM 对称加密
============================================================
明文: Hello, hybrid encryption! This message is protected by ECDH + AES-256-GCM.
明文长度: 74 bytes
Nonce (hex): 8b2b4f2b99fa528a93391090
密文+Tag (hex): 04928f5a9a432585063cacab6034b8712ed231c04a734026102d1630290094c150bddba7eaa879c4d9c3ff1d9e465c481b563328c9b398f319a3bd0bbe13758cc33046879eecabfffa0a2fae41f9e612a3d76fec856d8c6ecd36
密文+Tag 长度: 90 bytes
其中密文: 74 bytes
其中认证标签: 16 bytes (AES-GCM 固定)
Bob 解密结果: Hello, hybrid encryption! This message is protected by ECDH + AES-256-GCM.
解密成功: True
============================================================
篡改检测:AES-GCM 认证标签验证
============================================================
检测到篡改!解密失败: InvalidTag
AES-GCM 的认证标签成功阻止了篡改攻击。

这个演示完整展示了混合加密的两个阶段:

  1. ECDH 协商密钥:Alice 和 Bob 各自生成临时密钥对,交换公钥后计算出相同的共享密钥,再通过 HKDF 派生出 AES-256 密钥。窃听者即使截获了双方的公钥,也无法计算共享密钥(这就是 ECDLP 的困难性保证)。
  2. AES-GCM 加密数据:用派生的对称密钥加密实际数据。AES-GCM 同时提供机密性和完整性——任何对密文的篡改都会在解密时被检测到(InvalidTag)。

注:完整的混合加密方案还需要将 Alice 的公钥和 nonce 随密文一起传输给 Bob,以便 Bob 执行 ECDH 和解密。实际协议(如 TLS)还包含证书验证、握手完整性校验等步骤。

附.6 实践小结#

通过以上四个实操步骤,我们亲手完成了非对称加密的核心操作。下表总结了 RSA 与 ECC 在实践中的关键差异:

维度RSA-2048ECC P-256 / Curve25519
密钥生成速度慢(~1s)极快(~1ms)
私钥文件大小~1700 bytes~230 bytes
公钥文件大小~450 bytes~180 bytes(P-256)/ 32 bytes(Curve25519)
加密能力可直接加密(≤245 bytes)不直接加密,仅用于密钥协商
加密填充OAEP(必须)不适用
密钥交换RSA-KM(无前向安全)ECDHE(前向安全)
混合加密模式RSA-OAEP 加密对称密钥ECDH 协商 + HKDF 派生对称密钥
安全等级~112 位~128 位
推荐场景兼容旧系统、证书签名新系统首选(密钥交换、签名)

核心结论:新系统应优先选择 ECC——更短的密钥、更快的速度、更小的传输开销,且天然支持前向安全的密钥交换。RSA 仍在证书签名和兼容性场景中占有一席之地,但加密和密钥交换已逐步让位于 ECC。

七、总结#

上一章建立了对称加密与 AEAD的认知框架。

7.1 核心要点#

维度关键要点
RSA大数分解,OAEP 加密,PSS 签名,至少 2048 位
ECC椭圆曲线,密钥短性能好,Curve25519/Ed25519 推荐
ECDH密钥交换,前向安全,TLS 1.3 核心组件
ECDSA/EdDSA数字签名,EdDSA 确定性更安全
密钥长度RSA-2048 ≈ ECC-256 ≈ AES-128
混合加密RSA/ECC 加密对称密钥,AES 加密数据

7.2 选型决策流程#

graph TD START["需要非对称加密?"] --> Q1{"用途?"} Q1 -->|"加密数据"| ENC["混合加密<br/>ECDH + AES-GCM"] Q1 -->|"数字签名"| Q2{"新系统?"} Q2 -->|"是"| ED25519["Ed25519"] Q2 -->|"否/需兼容"| ECDSA["ECDSA P-256"] Q1 -->|"密钥交换"| Q3{"需要前向安全?"} Q3 -->|"是(推荐)"| ECDHE["ECDHE Curve25519"] Q3 -->|"否"| RSA_KX["RSA 密钥交换 "] ENC --> FINAL["安全方案"] ED25519 --> FINAL ECDSA --> FINAL ECDHE --> FINAL

7.3 最佳实践清单#

  • 新系统优先选择 ECC(Curve25519/Ed25519)
  • RSA 加密使用 OAEP 填充,签名使用 PSS 填充
  • 密钥交换使用 ECDHE,确保前向安全
  • 从共享密钥派生对称密钥时使用 HKDF
  • RSA 密钥至少 2048 位,推荐 3072 位
  • 不要使用 PKCS#1 v1.5 填充
  • 不要直接用非对称加密加密大量数据
  • 不要在 ECDSA 中重复使用随机数 k
  • 不要使用静态 DH 密钥交换

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

非对称加密:RSA 与 ECC
https://blog.souloss.com/posts/cryptography/asymmetric-encryption/
作者
Souloss
发布于
2026-03-09
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时