只要你打算加密任何東西,就得產生金鑰——無論是暫時的「工作階段金鑰」(session keys,例如瀏覽 HTTPS 網站時產生的那種),還是長期的公鑰。

回想第 2 章:秘密金鑰是密碼學安全性的核心,必須隨機產生,才能既不可預測又保密。

當你瀏覽 HTTPS 網站時,瀏覽器收到網站的公鑰,並用它建立一把只在當前工作階段有效的對稱金鑰。而該網站的公鑰及其對應私鑰可能好幾年都有效——因此它最好要讓攻擊者難以找到。

但產生秘密金鑰不總是像倒出足夠的偽隨機位元那麼簡單。密碼學金鑰可以用三種方式產生:

  • 隨機產生:使用偽隨機數產生器(PRNG),必要時搭配金鑰產生演算法。
  • 從密碼衍生:使用金鑰衍生函式(KDF,key derivation function),把使用者提供的密碼轉換成金鑰。
  • 透過金鑰協商協定:兩方或多方之間的一系列訊息交換,最終建立一把共享金鑰。

以下說明最簡單的方法:隨機產生。

產生對稱金鑰#

對稱金鑰是兩方共享的秘密金鑰,也是最容易產生的。

它們的長度通常等於其所提供的安全等級:一把 128 位元金鑰提供 128 位元安全性,而 2^128 把可能金鑰中的任何一把都是有效的,效果與其他任何一把相同。

要用密碼學 PRNG 產生一把 n 位元的對稱金鑰,你只要向它索取 n 個偽隨機位元,拿來當金鑰就好。就這樣。

例如用 OpenSSL 工具傾印偽隨機位元組來產生一把隨機對稱金鑰:

$ openssl rand 16 -hex
65a4400ea649d282b855bd2e246812c6

產生非對稱金鑰#

與對稱金鑰不同,非對稱金鑰通常比它們提供的安全等級更長。但這還不是主要問題。

非對稱金鑰比對稱金鑰更難產生,因為你不能只是從 PRNG 倒出 n 個位元就交差。

非對稱金鑰不只是原始位元序列——它們代表某種特定型態的物件,例如一個具有特定性質的大數(在 RSA 中是兩個質數的乘積)。一個隨機位元字串(因而是一個隨機數)不太可能具備所需的特定性質,因此不會是一把有效的金鑰。

流程#

要產生非對稱金鑰,你把偽隨機位元當成種子送進一個金鑰產生演算法。這個演算法接收一個至少與目標安全等級等長的種子值,並從中建構出一把私鑰及其對應的公鑰,同時確保兩者都滿足所有必要的條件。

例如一個天真的 RSA 金鑰產生演算法,會用一個「產生兩個長度相近之隨機質數」的演算法來產生 n = pq。而那個演算法會不斷挑隨機數,直到其中一個恰好是質數——所以你還需要一個測試某數是否為質數的演算法。

用 OpenSSL 代勞#

省下手動實作金鑰產生演算法的負擔,直接用 OpenSSL 產生一把 4096 位元的 RSA 私鑰:

$ openssl genrsa 4096
Generating RSA private key, 4096 bit long modulus
..............................................................................
...............................++
...............................................++
e is 65537 (0x10001)
-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEA3Qgm6OjMy61YVstaGawk22A9LyMXhiQUU4N8F5QZXEef2Pjq
vTtAIA1hzpK2AJsv16INpNkYcTjNmechAJ0xHraftO6cp2pZFP85dvknsMfUoe8u
btKXZiYvJwpS0fQQ4tzlDtH45Gj8sMHcwFxTO3HSIx0XV0owfJTLMzZbSE3TDlN+
JdW8d9Xd5UVB+o9gUCI8tSfnOjF2dHlLNiOhlfT4w0Rf+G35USIyUJZtOQ0Dh8M+
--snip--
-----END RSA PRIVATE KEY-----

輸出的解讀:

  • 金鑰以特定格式呈現——BEGIN RSA PRIVATE KEYEND RSA PRIVATE KEY 標記之間的 base64 編碼資料。這是多數系統都支援的標準編碼格式,系統會把這個表示法轉回原始位元組資料。
  • 開頭的點序列是一種進度條。
  • e is 65537 (0x10001) 指出加密時要用的參數(記得 RSA 加密是計算 C = P^e mod n)。

保護金鑰#

有了秘密金鑰之後,你得讓它保持秘密,卻又要在需要時能取用。有三種方式可以處理這個問題。

金鑰包裝#

金鑰包裝(key wrapping)就是用第二把金鑰加密這把金鑰。

問題在於:第二把金鑰必須在你要解開受保護金鑰時是可用的。實務上,這第二把金鑰通常是在使用者需要用到受保護金鑰時,從他所提供的密碼產生出來的。SSH 協定的私鑰通常就是這樣保護的。

從密碼即時產生#

這種做法不需要儲存任何加密檔案,因為金鑰直接由密碼算出。miniLock 這類現代系統採用此法。

這個方法雖然比金鑰包裝更直接,但普及度較低,部分原因是它對弱密碼更脆弱

假設攻擊者擷取到一段加密訊息:若用的是金鑰包裝,攻擊者得先取得受保護的金鑰檔,而該檔通常存在使用者本機檔案系統中,因此不易取得。但若用的是即時產生,攻擊者可以直接用候選密碼嘗試解密該訊息來搜尋正確密碼——密碼一弱,金鑰就淪陷。

存放在硬體權杖#

把金鑰存在智慧卡或 USB dongle 上。金鑰存放在安全記憶體中,即使電腦被攻破也依然安全。

這是最安全的金鑰儲存方式,但也最昂貴、最不方便,因為你得隨身攜帶硬體權杖,還有遺失的風險。

智慧卡與 USB dongle 通常要求你輸入密碼,才能從安全記憶體中解鎖金鑰。

無論你用哪種方法,交換金鑰時務必別把私鑰誤當公鑰,也別不小心透過電子郵件或原始碼把私鑰公開出去。

(作者實際在 GitHub 上找到過私鑰。)

測試金鑰包裝#

執行下列 OpenSSL 指令,加上 -aes128 參數,告訴 OpenSSL 用 AES-128(128 位元金鑰的 AES)加密這把金鑰:

$ openssl genrsa -aes128 4096
Generating RSA private key, 4096 bit long modulus
..........++
..............................................................................
...............................................++
e is 65537 (0x10001)
Enter pass phrase:

它所要求的通行短語,將被用來加密這把新建立的金鑰。