1.3 Kerckhoffs 原則與安全觀念#
Kerckhoffs 原則:現代密碼學的憲法#
1883 年,荷蘭語言學家 Auguste Kerckhoffs 發表〈軍事密碼學〉,提出軍用密碼體系的六項原則,其中第二條影響最深遠:
系統必須可被公開而不損害其安全性;系統的保密性只應依賴金鑰。
這句話在當時近乎反直覺:把加密方法公開,敵人不是更方便破解嗎?今日我們知道這條原則是對的,理由至少有四:
- 防不了內部與逆向工程。軟體可以被反編譯,硬體可以被拆解分析,演算法其實很難保密(Enigma 機一開始就是「商業產品」,其結構美國早已取得;四行 code 的加密法也擋不住自行查閱書籍的敵人。IBM 在 1970 年代開發的加密法被公開後反而幫助了自我檢驗)。
- 公開才能累積檢驗。只有經過全球密碼學家多年攻擊還站得住腳的演算法才值得信賴。AES 的制定過程就是公開競賽:全球學者提交 15 個候選,經 3 年公開分析才選出 Rijndael。反之,祕密演算法(例如很多「自家發明」的保密演算法、一些不容分析的手機加密)屢屢被證明脆弱。
- 金鑰可以隨時更換,演算法很難換。金鑰洩露換一把就好;演算法被破解要改寫所有系統。
- 「靠不公開保護」會欺騙設計者,讓人以為演算法表現良好而失去改進動力。
反例警示:把演算法寫成「混淆」過的程式碼(obfuscation)自認無人看懂,是業界常見陷阱,屢屢被快速逆向,正中 Kerckhoffs 所述。你是否聽過「我們的加密方法連我們自己都不知道」這種宣傳——要小心,那往往只是還沒人花時間破解。
安全等級的三個層次#
在「演算法可以公開」的前提下,我們如何度量安全?三個有用的層次:
- 無條件安全(unconditional security):即使攻擊者有無限的計算資源與時間也無法破解。一次性密碼本是唯一被證明的例子(3.1 節)。實務上幾乎收不到這種保證。
- 計算安全(computational security):以現有科技與預期未來資源,破解所需的計算量大到不划算。現代幾乎所有密碼學(AES、RSA、橢圓曲線)都在此層次。度量方式例如:「要多少年?設多少台電腦?」。人們常用「128-bit 安全等級」表達:「要花約 2^128 次基本操作」——以人類科技,目前的總運算能力也遠遠不及。
- 可證明安全(provable security):把破解系統化約到某個已被廣泛相信的數學難題上——「若能破解此加密,則你解開了 XX 難題」。這不是數學上證明安全,而是證明「你最壞的情況也只是碰上一個大家都認為很硬的問題」。RSA 安全性即化約到「大數分解」難題(7.2、8.3 節),橢圓曲線化約到「離散對數」(7.3、8.2 節)。
攻擊面的看見:安全不等於「沒有明顯洞」#
Kerckhoffs 原則的推論是:安全需要面對最強的攻擊者來設計。另一項常被忽略的事實是:「金鑰對,就不會被破解」是錯的,因為安全至少取決於四件事:
- 演算法本身是否足夠強(如 AES-256 對暴力搜尋而言)
- 金鑰生命週期是否安全(生成要夠隨機、儲存要保密、定期更換)
- 使用方式 / 模式是否恰當(同一段密文重用 IV、ECB 的積木圖案洩露見 4.2 節)
- 側頻道(side channels):不透過數學漏洞、而透過「執行時的物理洩露」破解。例如測量加密晶片的耗電曲線推算金鑰的功耗分析(DPA);偵測 RSA 運算耗時差異的時序攻擊(Kocher 於 1996 提出);甚至「三公尺外用行動電話聽鍵盤敲擊聲」。
第 3~4 類的洞常常比第 1 類致命:數學上完美的演算法也會被工程上的粗心毀掉,這是本書第 9 章的主軸。
計算成本的度量與 Python 示範#
計算安全最直觀的例子是「關鍵空間大小」。以下程式示範兩件事:(1) 估算 2^n 的暴力搜尋成本;(2) 示範「混淆≠安全」——我們寫一個看似複雜的教科書加密,再用極簡單的已知明文法破它,具體演練 Kerckhoffs 原則。
import math, time
def bits_to_years(bits: int, ops_per_sec: float = 1e12) -> float:
"""假設每秒 10^12 次嘗試(約等於多核 GPU 的量級),
求窮舉 bits 位元的平均時間(秒→年)。"""
seconds = 2**bits / 2 / ops_per_sec # 平均而言找到一半處
return seconds / (365.25 * 24 * 3600)
for bits in [40, 56, 80, 128, 256]:
print(f"{bits:3d} bits -> 約 {bits_to_years(bits):.3e} 年")
# 示範「混淆 ≠ 安全」:下面這個加密函式看起來很複雜,
# 但只是三層簡單替代,仍能被已知明文攻擊還原。
def confuse_encrypt(key: int, data: bytes) -> bytes:
# 教學示範:多層替代/異或堆疊以增加「混淆感」
out = bytearray(data)
for i, b in enumerate(out):
x = (b ^ (key & 0xFF)) & 0xFF
x = ((x << 3) | (x >> 5)) & 0xFF
x = (x + (key >> 4)) & 0xFF
out[i] = x
return bytes(out)
plain = b"A" * 16 # 已知明文
key = 0x1A2B3C4D
cipher = confuse_encrypt(key, plain)
print("密文:", cipher.hex())
觀察程式尾端的輸出:同樣的明文加密後密文完全相同,即使有三層替代與旋轉。一個拿到加密軟體(且能喂入明文)的攻擊者,只要先發出 "A"*16 這種已知明文,一次就掌握全部 16 個位元組的映射規則——因為這個函式本質上是逐字節的固定替換,與明文內容無關。
安全性分析#
範例的這個教學式加密具有三個致命缺陷,正是本章觀念的具體化:
- 沒有金鑰「混入」的意義:密文規律與 key 只差一個固定變換,攻擊者可破解(Kerckhoffs 原則警示:語言與結構一旦被逆,所謂祕密設計完全失效)。
- 確定性加密:同一明文永遠給出同一密文,在選定明文攻擊下零機密性(4.2 節將說明現代加密必須帶「非確定性」如隨機 IV)。
- 無資料複雜度:單一已知明文足以完全還原。設計者若誤信「多層混淆 = 混淆感高 = 安全」,便會重蹈覆轍。
練習#
- 執行程式,繪出各 bits 對應的破解年限,並說明 56-bit(DES 的關鍵空間)在今日 GPU 叢集下為何被評為不足(見 3.3 節)。
- 對
confuse_encrypt,已知"A"*16的密文,寫出還原 key 與逐字節替換表的方法並實作。 - 舉一個你生活或工作中聽過的「不公開加密法」,判斷它違反 Kerckhoffs 原則的哪一條假設,並估計若該演算法被洩露,多久時間會被公開逆向。