加密 ≠ 安全:系統級失敗案例
Published: Sat Feb 01 2025 | Modified: Sat Feb 07 2026 , 8 minutes reading.
1. 為什麼要關心這個問題?
你的應用使用 AES-256-GCM。你的 TLS 配置在 SSL Labs 獲得 A+。你的密碼使用 Argon2id 雜湊。
然後你還是被攻破了。
加密是門上的鎖。如果窗戶開著、鑰匙在門墊下面、或者牆是紙糊的,鎖就沒用了。
2. 安全思維差距
開發者的想法
開發者的心智模型:
「我加密了資料,所以它是安全的。」
現實:
┌─────────────────────────────────────────────────────────────┐
│ 攻擊面 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 明文資料 ───────────────────────┐ │
│ ↓ │ │
│ [ 加密 ] ← 金鑰管理 ────────────┼── 金鑰暴露 │
│ ↓ │ │
│ 加密資料 ──────────────────────┼── 儲存洩露 │
│ ↓ │ │
│ [ 解密 ] ← 存取控制 ────────────┼── 認證繞過 │
│ ↓ │ │
│ 明文資料 ───────────────────────┘ │
│ │
│ 加密只保護中間部分! │
│ │
└─────────────────────────────────────────────────────────────┘真實的威脅模型
大多數洩露不是破解加密。它們:
- 竊取金鑰
- 在加密前/後存取資料
- 繞過認證
- 利用配置錯誤
- 使用社會工程
- 發現邏輯缺陷
強加密 + 弱運維 = 弱系統3. 明文暴露
案例 1:記錄敏感資料
# 災難:在加密前記錄明文
import logging
def process_payment(card_number, amount):
logging.info(f"處理支付: card={card_number}, amount={amount}")
encrypted_card = encrypt(card_number)
# 卡號現在永遠在日誌檔案中
# 日誌聚合服務
# 開發者筆記型電腦
# 備份系統
# 加密的資料庫是安全的。
# 日誌在 47 個不同的地方,未加密。案例 2:錯誤訊息
# 災難:包含敏感資料的例外
def decrypt_user_data(encrypted_data, key):
try:
return decrypt(key, encrypted_data)
except DecryptionError as e:
# 錯誤訊息包含金鑰!
raise Exception(f"使用金鑰 {key} 解密失敗: {e}")
# 錯誤追蹤服務(Sentry、Bugsnag)現在有你的金鑰了案例 3:記憶體傾印和核心傾印
當行程崩潰時,作業系統可能將記憶體傾印到磁碟:
- /var/crash/
- Windows 錯誤報告
- Docker 容器日誌
記憶體包含:
- 解密的資料
- 加密金鑰
- 傳輸中的密碼
「加密的」資料以明文形式存在於記憶體中。
崩潰 = 永久的明文記錄。案例 4:交換分區和休眠
作業系統可能將記憶體寫入磁碟:
交換空間:
- RAM 溢位寫入磁碟
- 包括解密的金鑰
- 重新開機後可能仍然存在
休眠:
- 整個 RAM 寫入磁碟
- 所有金鑰未加密儲存
- 可透過磁碟存取恢復
雲端虛擬機:
- 虛擬機管理程式可以讀取客戶機記憶體
- 即時遷移複製所有 RAM
- 快照包含記憶體狀態4. 金鑰管理失誤
案例 1:原始碼中的金鑰
# GitHub 搜尋:「AES_KEY」或「encryption_key」或「secret_key」
# 數百萬個結果
# 在真實儲存庫中發現:
AWS_SECRET_KEY = "AKIAIOSFODNN7EXAMPLE"
ENCRYPTION_KEY = "super_secret_key_12345"
DATABASE_PASSWORD = "admin123"
# 一旦提交,即使刪除:
# - 仍在 git 歷史中
# - 被 GitHub 搜尋快取
# - 儲存在開發者機器上
# - 被多次備份案例 2:環境變數中的金鑰(洩露的)
# docker-compose.yml 提交到儲存庫
services:
app:
environment:
- ENCRYPTION_KEY=aGVsbG8gd29ybGQK
- DATABASE_URL=postgres://user:password@db/prod
# CI/CD 日誌經常列印環境變數
# 容器檢查會暴露環境變數
# 行程列表會顯示環境變數案例 3:跨環境金鑰重用
常見反模式:
- 開發、測試和生產使用相同的金鑰
- 開發者筆記型電腦有生產金鑰
- 測試資料庫使用生產金鑰加密
開發環境洩露 = 生產環境洩露案例 4:不輪換金鑰
公司使用同一個加密金鑰 10 年:
- 多名離職員工曾有存取權限
- 金鑰可能在未被偵測的情況下洩露
- 如果金鑰被洩露,所有歷史資料都有風險
- 無法限制影響範圍
金鑰輪換提供:
- 有限的暴露視窗
- 撤銷被洩露金鑰的能力
- 合規性
- 密碼學衛生5. 運維安全失誤
案例 1:備份
生產資料庫:靜態加密 ✓
資料庫備份:未加密,儲存在 S3 ✗
真實事件(2019):
- 公司正確加密了他們的 MongoDB
- 備份指令碼傾印到未加密的 S3 儲存桶
- 儲存桶是公開的
- 2.5 億條記錄暴露
加密是完美的。
備份過程完全繞過了它。案例 2:開發和除錯存取
# 帶有除錯端點的生產程式碼
@app.route('/debug/user/<user_id>')
def debug_user(user_id):
if request.args.get('debug_key') == 'supersecret':
user = get_user(user_id)
return {
'encrypted_data': user.encrypted_data,
'encryption_key': user.encryption_key, # 😱
'decrypted_data': decrypt(user.encrypted_data, user.encryption_key)
}
# 「我們會在生產前刪除這個」
# 旁白:他們沒有。案例 3:支援和管理員存取
典型企業:
- 50+ 人有生產資料庫存取權限
- 200+ 人有解密金鑰存取權限
- 500+ 人理論上可以存取資料
每個人都是:
- 潛在的內部威脅
- 社會工程的目標
- 筆記型電腦被盜的風險
- 帳戶被入侵的風險
當授權使用者是威脅時,加密沒有幫助。案例 4:第三方整合
你的安全:
- 端到端加密儲存
- HSM 支援的金鑰管理
- 零信任架構
你的供應商整合:
「只需將客戶資料作為 JSON POST 到我們的 webhook」
// 在生產中發現的實際程式碼
async function sendToVendor(customer) {
await fetch('https://vendor.com/webhook', {
method: 'POST',
body: JSON.stringify({
ssn: customer.ssn, // 😱
bank_account: customer.bank_account, // 😱
password: customer.password // 😱😱😱
})
});
}6. 側通道洩露
實踐中的時序攻擊
# 認證時序洩露
def authenticate(username, password):
user = database.get_user(username)
if user is None:
return False # 快:使用者不存在
if not verify_password(password, user.password_hash):
return False # 慢:bcrypt 比較
return True
# 攻擊者可以列舉有效使用者名稱:
# 無效使用者名稱:1ms 回應
# 有效使用者名稱:500ms 回應(bcrypt)基於快取的洩露
AES 實現可能有時序變化:
- 表查找取決於快取狀態
- 不同的金鑰位元組 = 不同的快取模式
- 可從同一機器或虛擬機測量
研究已證明:
- 從共置虛擬機擷取 AES 金鑰
- 跨行程金鑰擷取
- JavaScript 快取時序攻擊
你「加密」資料的金鑰透過 CPU 快取時序洩露了。基於網路的洩露
HTTPS 保護內容,不保護中繼資料:
網路可觀察到的:
- 請求時序(你何時存取資料)
- 請求大小(大約你存取什麼)
- 請求頻率(你多久檢查一次)
- 存取的端點(你使用哪些功能)
流量分析可以揭示:
- 你存取什麼網站(透過封包大小)
- 你在輸入什麼(透過擊鍵時序)
- 你在觀看什麼(透過頻寬模式)7. 架構失誤
案例 1:客戶端「安全」
// 瀏覽器中「加密的」密碼儲存
function savePassword(password) {
const encrypted = btoa(password); // 這是 base64,不是加密!
localStorage.setItem('password', encrypted);
}
// 即使使用真正的加密:
const key = 'hardcoded_key_in_js'; // 在原始碼中可見
const encrypted = CryptoJS.AES.encrypt(password, key);
// 任何人都可以讀取原始碼並解密案例 2:透過隱蔽實現安全
物聯網裝置的真實例子:
- 通訊使用互斥或「加密」
- 金鑰是字串 "security"
- 對於更長的訊息重複
「沒人會逆向工程我們的協定」
旁白:只花了 15 分鐘。案例 3:先加密後認證 vs 先認證後加密
錯誤的順序(易受填充預言攻擊):
1. 加密明文
2. 計算密文的 MAC
3. 攻擊者修改密文
4. 伺服器先解密,再檢查 MAC
5. 解密錯誤洩露資訊!
正確的順序(或使用 AEAD):
1. 計算 MAC
2. 加密(明文 + MAC)
3. 攻擊者修改在解密前被偵測到
或者直接使用正確處理這一點的 AES-GCM。案例 4:混淆代理人
# 伺服器正確地按使用者加密資料
def get_document(user_id, doc_id):
doc = database.get_document(doc_id)
# 使用使用者的金鑰解密
key = get_user_key(user_id)
return decrypt(doc.encrypted_content, key)
# 但授權檢查是錯誤的!
def get_document(user_id, doc_id):
doc = database.get_document(doc_id)
# 忘記檢查 user_id 是否擁有 doc_id!
key = get_user_key(user_id) # 獲取錯誤使用者的金鑰
return decrypt(doc.encrypted_content, key) # 解密失敗...或者更糟
# 如果攻擊者用自己的內容替換加密內容會怎樣?
# 如果金鑰意外共享會怎樣?8. 真實世界洩露案例
Capital One(2019)
他們有的:
- AWS 靜態加密
- AWS 傳輸加密
- 正確的金鑰管理
出了什麼問題:
- WAF 中的 SSRF 漏洞
- 攻擊者存取執行個體中繼資料
- 獲得 IAM 憑證
- 使用憑證存取 S3
- 下載 1 億條客戶記錄
加密無關緊要。
存取控制失誤 = 洩露。Equifax(2017)
他們有的:
- 加密的資料庫
- 安全團隊
- 合規認證
出了什麼問題:
- 未打補丁的 Apache Struts(CVE 已知數月)
- 攻擊者獲得 shell 存取
- 透過應用程式存取資料(應用程式有解密權限)
- 1.47 億條記錄暴露
加密無關緊要。
應用程式是授權的存取者。Adobe(2013)
他們有的:
- 加密的密碼(但使用 ECB 模式)
- 所有密碼使用相同的金鑰
出了什麼問題:
- 資料庫洩露(獨立的漏洞)
- ECB 模式:相同密碼 = 相同密文
- 密碼提示以明文儲存
- 交叉參照提示和密文模式
使用者 1:密文 ABC,提示:「我貓的名字」
使用者 2:密文 ABC,提示:「和 fiskers 押韻」
使用者 3:密文 ABC,提示:「whiskers」
攻擊者在不破解加密的情況下解密了數百萬密碼。9. 什麼真正有效
縱深防禦
第 1 層:網路安全
- 防火牆、隔離、WAF
- 阻止基於網路的攻擊
第 2 層:認證和授權
- 強認證(MFA)
- 最小權限原則
- 阻止未授權存取
第 3 層:應用安全
- 輸入驗證
- 安全編碼實踐
- 阻止應用層攻擊
第 4 層:資料安全
- 靜態和傳輸加密
- 金鑰管理
- 在其他層失敗時阻止資料被盜
第 5 層:監控和回應
- 日誌、警報、事件回應
- 偵測和遏制洩露
每一層捕獲其他層遺漏的。實用檢查清單
部署前驗證:
[ ] 金鑰不在原始碼或日誌中
[ ] 加密金鑰定期輪換
[ ] 存取控制已測試(嘗試繞過它們!)
[ ] 備份使用不同的金鑰加密
[ ] 除錯端點已刪除
[ ] 第三方整合已保護
[ ] 監控和警報已配置
[ ] 事件回應計劃存在
[ ] 所有團隊成員接受過安全訓練
[ ] 定期安全稽核已安排10. 本章小結
三點要記住:
加密只保護一種狀態的資料。 資料存在於加密前、解密後、記憶體中、日誌中、備份中、錯誤訊息中。加密只保護加密的形式。
存取控制失誤繞過加密。 如果攻擊者可以透過你的應用程式存取資料(應用程式必須解密才能使用),你的加密就無關緊要了。大多數洩露是存取控制失誤,而不是密碼學被破解。
安全是系統屬性,不是功能。 你不能新增加密就「完成」安全。安全需要持續關注運維、監控、存取控制和人為因素。
11. 下一步
你理解了為什麼單靠加密是不夠的。但你如何像安全工程師一樣思考?你如何建構抵抗攻擊的系統?
在下一篇文章中:建立安全判斷能力——像攻擊者一樣思考、威脅建模,以及知道什麼時候「足夠好」真的足夠好。
