RSA 在現代系統中的角色:真實用途與為何被替代
Published: Sat Feb 01 2025 | Modified: Sat Feb 07 2026 , 9 minutes reading.
1. 為什麼要關心這個問題?
你已經學會了 RSA 的數學基礎。但如果你觀察今天的實際系統:
- TLS 1.3 完全移除了 RSA 金鑰交換
- 大多數網站使用 ECDSA 或 EdDSA 憑證
- SSH 預設使用 Ed25519 金鑰
- Signal、WhatsApp 使用 Curve25519
RSA 怎麼了?它過時了嗎?理解 RSA 的演變能幫助你為系統做出更好的選擇。
2. RSA 的三種角色
RSA 技術上能做三件事,但現代用途很有限:
┌─────────────────────────────────────────────────────────────┐
│ 角色 1:直接加密 │
│ 狀態:絕對不要用 │
│ 原因:大小限制、效能問題、安全問題 │
├─────────────────────────────────────────────────────────────┤
│ 角色 2:金鑰交換(加密會話金鑰) │
│ 狀態:已廢棄(TLS 1.3 移除了它) │
│ 原因:沒有前向保密 │
├─────────────────────────────────────────────────────────────┤
│ 角色 3:數位簽章 │
│ 狀態:仍在使用(但 ECC 更受歡迎) │
│ 用途:程式碼簽章、憑證、遺留系統 │
└─────────────────────────────────────────────────────────────┘3. 為什麼不能用 RSA 直接加密資料
大小限制
RSA-2048 只能加密:
最大原始資料:256 位元組
使用 OAEP 填充後:214 位元組
你的 1MB 檔案?做不到。
你的 1KB JSON?還是做不到。效能災難
# 基準測試:加密 1MB 資料
import time
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
# 產生金鑰
rsa_private = rsa.generate_private_key(public_exponent=65537, key_size=2048)
rsa_public = rsa_private.public_key()
aes_key = os.urandom(32)
data = os.urandom(1024 * 1024) # 1MB
# RSA:必須分成 190 位元組的區塊(為了填充安全)
def rsa_encrypt_chunked(data):
chunks = [data[i:i+190] for i in range(0, len(data), 190)]
return [rsa_public.encrypt(
chunk,
padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(), label=None)
) for chunk in chunks]
# AES-GCM:一次加密全部
def aes_encrypt(data):
nonce = os.urandom(12)
return AESGCM(aes_key).encrypt(nonce, data, None)
# 時間對比
start = time.time()
rsa_encrypt_chunked(data)
rsa_time = time.time() - start
start = time.time()
aes_encrypt(data)
aes_time = time.time() - start
print(f"RSA 分塊: {rsa_time:.2f}s")
print(f"AES-GCM: {aes_time:.4f}s")
print(f"RSA 慢了 {rsa_time/aes_time:.0f} 倍")
# 典型輸出:
# RSA 分塊: 15.23s
# AES-GCM: 0.0021s
# RSA 慢了 7252 倍正確的方式:混合加密
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
def hybrid_encrypt(public_key, plaintext: bytes) -> dict:
"""使用混合加密加密資料"""
# 1. 產生隨機對稱金鑰
session_key = os.urandom(32)
# 2. 用對稱金鑰加密資料(快)
nonce = os.urandom(12)
ciphertext = AESGCM(session_key).encrypt(nonce, plaintext, None)
# 3. 用 RSA 加密對稱金鑰(小,所以快)
encrypted_key = public_key.encrypt(
session_key,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
return {
'encrypted_key': encrypted_key,
'nonce': nonce,
'ciphertext': ciphertext
}
def hybrid_decrypt(private_key, encrypted: dict) -> bytes:
"""解密混合加密的資料"""
# 1. 用 RSA 解密對稱金鑰
session_key = private_key.decrypt(
encrypted['encrypted_key'],
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# 2. 用對稱金鑰解密資料
plaintext = AESGCM(session_key).decrypt(
encrypted['nonce'],
encrypted['ciphertext'],
None
)
return plaintext4. TLS 中的 RSA 金鑰交換(舊方式)
TLS 1.2 中的工作方式
用戶端 伺服器
│ │
│───── ClientHello ─────────────────>│
│ │
│<──── ServerHello + 憑證 ───────────│
│ (包含 RSA 公鑰) │
│ │
│ │
│ 用戶端產生 PreMasterSecret │
│ 用伺服器的 RSA 金鑰加密 │
│ │
│───── ClientKeyExchange ───────────>│
│ (RSA 加密的金鑰) │
│ │
│ 雙方從 PreMasterSecret │
│ 衍生會話金鑰 │
│ │
│<════ 加密通訊 ════════════════════>│致命缺陷:沒有前向保密
問題所在:
1. 攻擊者記錄你所有的加密流量(儲存很便宜)
2. 多年後,攻擊者竊取了伺服器的私鑰
3. 攻擊者解密所有歷史流量
這叫做「現在收集,未來解密」
RSA 金鑰交換意味著:
- 一個金鑰洩露 = 所有過去的會話都被破解
- 沒有前向保密TLS 1.3 的解決方案:只允許臨時金鑰交換
TLS 1.3 完全移除了 RSA 金鑰交換
只允許 (EC)DHE - 臨時 Diffie-Hellman
用戶端 伺服器
│ │
│───── ClientHello + KeyShare ──────>│
│ (臨時 ECDH 公鑰) │
│ │
│<──── ServerHello + KeyShare ───────│
│ (臨時 ECDH 公鑰) │
│ │
│ 雙方計算共享金鑰 │
│ 金鑰用後即棄 │
│ │
│<════ 前向保密的加密 ═══════════════>│
即使伺服器的私鑰之後洩露,
過去的會話仍然安全!5. RSA 簽章仍然存在
雖然 RSA 加密已被廢棄,RSA 簽章仍在使用:
當前用途:
├── 程式碼簽章
│ ├── Windows Authenticode(RSA 常見)
│ ├── macOS codesign(RSA 或 ECDSA)
│ └── Android APK(正在轉向 ECDSA)
│
├── TLS 憑證
│ ├── 遺留:RSA 簽章
│ ├── 現代:ECDSA 優先
│ └── 最新:EdDSA (Ed25519)
│
├── SSH 金鑰
│ ├── 遺留:ssh-rsa(OpenSSH 8.8 已廢棄)
│ ├── 現代:rsa-sha2-256, rsa-sha2-512
│ └── 推薦:ssh-ed25519
│
└── JWT/JWS
├── RS256, RS384, RS512 (RSA)
└── ES256, ES384, ES512 (ECDSA,推薦)RSA 簽章範例
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa, padding
# 產生金鑰對
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048
)
public_key = private_key.public_key()
# 簽章
message = b"Important document content"
signature = private_key.sign(
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
# 驗證
try:
public_key.verify(
signature,
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
print("簽章有效!")
except Exception:
print("簽章無效!")6. 真實世界的 RSA 漏洞
填充預言攻擊(Bleichenbacher 攻擊)
針對 PKCS#1 v1.5 填充的攻擊:
1. 攻擊者截獲 RSA 密文
2. 向伺服器發送修改過的密文
3. 伺服器根據填充有效性給出不同回應
4. 經過數百萬次查詢後,攻擊者恢復明文
著名案例:
- DROWN 攻擊(2016)
- ROBOT 攻擊(2017)
防禦:使用 OAEP 填充,不要用 PKCS#1 v1.5時序攻擊
# 有漏洞:解密過程中的時序洩露
def vulnerable_decrypt(ciphertext, private_key):
plaintext = rsa_decrypt(ciphertext, private_key)
if not valid_padding(plaintext): # 這個時間會變化!
raise PaddingError()
return plaintext
# 安全:常數時間操作
# 使用處理了這個問題的函式庫實作常見實作錯誤
錯誤 1:使用不帶填充的原始 RSA
C = M^e mod n
→ 乘法性質允許操縱
→ 攻擊者可以偽造:C' = C × 2^e = (2M)^e
錯誤 2:小公開指數配合小訊息
如果 M^e < n,攻擊者直接計算 e 次方根即可
錯誤 3:相同訊息發給多個接收者
當 e=3 且相同的 M 發給 3 個不同的 n 值時,
中國餘數定理可以恢復 M
錯誤 4:相關訊息
用同一個金鑰加密 M 和 M+1
→ Franklin-Reiter 攻擊可以恢復兩者7. RSA vs ECC 對比
屬性 | RSA-2048 | ECC P-256
------------------+-----------------+------------------
金鑰大小 | 256 位元組 | 32 位元組
簽章大小 | 256 位元組 | 64 位元組
安全等級 | 112 位元 | 128 位元
金鑰產生 | 慢(找質數) | 快
簽章速度 | 中等 | 快
驗證速度 | 快(e 小) | 中等
效能趨勢 | 越來越差 | 保持良好
等效安全性:
RSA-3072(384 位元組)≈ ECC P-256(32 位元組)
RSA-15360(1920 位元組)≈ ECC P-521(66 位元組)8. 何時仍應使用 RSA
合理的使用場景
1. 遺留系統相容性
- 舊系統只支援 RSA
- 政府/企業要求
2. 硬體限制
- 某些 HSM 針對 RSA 最佳化
- 某些智慧卡只支援 RSA
3. 特定協定要求
- 某些簽章 PDF 標準
- 某些企業 PKI 系統建議
新專案檢查清單:
□ 金鑰交換:使用 ECDH(X25519 優先)
□ 簽章:使用 EdDSA (Ed25519) 或 ECDSA (P-256)
□ TLS:使用 TLS 1.3(沒有 RSA 金鑰交換)
□ SSH:使用 Ed25519 金鑰
□ JWT:使用 ES256 代替 RS256
只有在以下情況使用 RSA:
□ 被遺留相容性強制要求
□ 被外部法規要求
□ 硬體只支援 RSA9. 從 RSA 遷移的路徑
TLS 憑證遷移
步驟 1:產生新的 ECC 金鑰
openssl ecparam -genkey -name prime256v1 -out ecdsa.key
步驟 2:建立 CSR
openssl req -new -key ecdsa.key -out ecdsa.csr
步驟 3:部署雙憑證(過渡期)
- 主要:ECDSA 憑證
- 備用:RSA 憑證(用於舊用戶端)
步驟 4:監控並在安全時移除 RSASSH 金鑰遷移
# 產生新的 Ed25519 金鑰
ssh-keygen -t ed25519 -C "[email protected]"
# 新增到伺服器的 authorized_keys
# 暫時保留 RSA 金鑰以確保相容性
# 測試 Ed25519 存取
ssh -i ~/.ssh/id_ed25519 user@server
# 確認無誤後刪除 RSA 金鑰10. 常見誤區
| 誤區 | 現實 |
|---|---|
| 「RSA 被破解了」 | RSA 數學沒問題,但有更好的替代品 |
| 「更長的金鑰 = 更好」 | RSA 金鑰增長是不可持續的 |
| 「RSA 加密是標準」 | 混合加密才是標準 |
| 「TLS 使用 RSA 加密」 | TLS 1.3 只用 RSA 做簽章 |
| 「為了安全我應該用 RSA」 | 你應該用 ECC |
11. 本章小結
三點要記住:
RSA 不是用來直接加密的。 使用混合加密:RSA 加密對稱金鑰,對稱金鑰加密資料。
RSA 金鑰交換缺乏前向保密。 TLS 1.3 移除了它。使用臨時 (EC)DH 代替。
RSA 簽章仍然有效,但 ECC 更好。 對於新專案,優先使用 Ed25519 或 ECDSA 而不是 RSA。
12. 下一步
RSA 服務了我們數十年,但它的金鑰尺寸不斷增長,而 ECC 保持緊湊。橢圓曲線密碼學如何用更小的金鑰達到相同的安全性?
在下一篇文章中:橢圓曲線密碼學——曲線上的點如何能替代質因數分解,以及為什麼密碼學世界正在轉向曲線。
