不久前我在機緣巧合之下發現了 pass 這個用 GPG 進行加密的密碼管理系統, 進而研究起 GPG 配置和子密鑰,結果學到一個保護 GPG 密鑰的妙招。

TL; DR: GPG 生成的密鑰默認都是將主密鑰(master key)和簽名/加密用的子密 鑰放在一起的,我們日常使用中主要用到的是子密鑰,所以可以將最重要的主密 鑰分開保存,防止所有密鑰同時泄漏。

默認配置

如果之前使用過 GPG, 你很可能只生成了兩對密鑰:

  1. 主密鑰,用於簽名(sign)和認證(certify)操作;
  2. 子密鑰,用於加密(encrypt)操作。

這裡面每對密鑰都包含公鑰和私鑰兩部分。

我們想要保護主密鑰的私鑰部分(公鑰顧名思義是公開的,沒有保護的必要), 最好的方法是將主密鑰的私鑰分開保存,但這樣我們要做簽名操作的時候就麻煩了, 因爲簽名用的私鑰不在。

所以,我們需要更改一下 GPG 默認生成密鑰的方式,至少生成這樣三對密鑰:

  1. 主密鑰,只用於認證(certify)操作;
  2. 子密鑰,用於簽名(sign)操作;
  3. 子密鑰,用於加密(encrypt)操作。

這樣將主密鑰分離後就不影響簽名操作了。

生成主密鑰

爲了自定義我們的密鑰生成過程,需要啓用 gpg2 命令的「專家」模式:

1
gpg2 --expert --full-gen-key

程序首先會詢問生成的主密鑰的類型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
   (9) ECC and ECC
  (10) ECC (sign only)
  (11) ECC (set your own capabilities)
Your selection? 8

如上所示,我們選擇了選項「8」,因爲 RSA 是目前公認具有足夠強度並且 兼容性不錯的算法。當然我們也可以選擇更「新潮」的 ECC 橢圓曲線算法, 不過以後遇上不支持 ECC 的老系統時會比較麻煩。請注意「set your own capabilities」,這意味這我們要自行選擇此密鑰能進行的操作:

1
2
3
4
5
6
7
8
9
Possible actions for a RSA key: Sign Certify Encrypt Authenticate
Current allowed actions: Sign Certify Encrypt

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection?

這裡要依次選擇「S」、「E」和「Q」選項,將簽名(Sign)和加密(Encrypt) 操作去除,最後就可以得到一對只能進行認證(Certify)操作的密鑰。

接下來程序會針對 RSA 算法詢問密鑰的長度:

1
2
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096

網上很多觀點都認爲 2048 位的 RSA 密鑰已經不夠安全了,而 4096 位的密鑰 在日常使用中並不會顯得很慢,所以我們輸入 4096 作爲密鑰長度。

這時我們要確定密鑰的有效期限:

1
2
3
4
5
6
7
8
Requested keysize is 4096 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0)

這裡的選項就見仁見智了。有效期限主要是防止我們自己把私鑰弄丟,如果認 爲自己的私鑰保管得很好,完全可以選擇「不會過期」(key does not expire).

然後程序會詢問我們的身份信息——包括姓名(real name)和 email 地址等——以及 私鑰的訪問密碼。這與一般的密鑰創建過程類似,這裡不再贅述。

回答完這一系列的問題之後,我們就得到一對 RSA 主密鑰。

生成子密鑰

前面我們生成的主密鑰只能用來做認證(certify),那簽名(sign)和加密 (encrypt)怎麼辦?我們還需要添加相應的子密鑰。

1
gpg2 --expert --edit-key '[email protected]'

這個命令會帶我們進入 GPG 命令行,在命令行中輸入 addkey ,程序會 再次詢問密鑰類型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
gpg> addkey
Please select what kind of key you want:
   (3) DSA (sign only)
   (4) RSA (sign only)
   (5) Elgamal (encrypt only)
   (6) RSA (encrypt only)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
  (10) ECC (sign only)
  (11) ECC (set your own capabilities)
  (12) ECC (encrypt only)
  (13) Existing key
Your selection? 8

再次選擇類型「8」,但是選擇密鑰用途時與生成主密鑰有些區別:

1
2
3
4
5
6
7
8
9
Possible actions for a RSA key: Sign Encrypt Authenticate
Current allowed actions: Sign Encrypt

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection?

這裡的選擇取決於此密鑰的用途,如果是簽名用,就選擇「E」選項將加密 功能關閉,反之選擇「S」選項將簽名功能關閉。這裡我們輸入「E」,生成 一對簽名用的密鑰。

後面的步驟與生成主密鑰類似。完成所有步驟後,程序會顯示當前的密鑰列表:

1
2
3
4
5
6
sec  rsa4096/XXXXXXXXXXXXXXXX
     created: 2016-10-08  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  rsa4096/AAAAAAAAAAAAAAAA
     created: 2016-10-08  expires: never       usage: S
[ultimate] (1). Your Name <[email protected]>

列表中有兩對密鑰, sec 行是主密鑰, ssb 行是子密鑰。 Usage 後面的字母則標明了密鑰的用途:「C」==「Certify」,「S」==「Sign」, 「E」==「Encrypt」.

在 GPG 命令行中重複上面的 addkey 命令,再生成一個加密(Encrypt) 用的密鑰,我們的密鑰列表看起來就是這樣的:

1
2
3
4
5
6
7
8
sec  rsa4096/XXXXXXXXXXXXXXXX
     created: 2016-10-08  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  rsa4096/AAAAAAAAAAAAAAAA
     created: 2016-10-08  expires: never       usage: S
ssb  rsa4096/BBBBBBBBBBBBBBBB
     created: 2016-10-08  expires: never       usage: E
[ultimate] (1). Your Name <[email protected]>

XXXXXXXXXXXXXXXX, AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB 分 別是相應密鑰的 keyid,當我們想要引用特定的主密鑰或子密鑰時,可以指定 這個十六進制串。

最後輸入 save 保存並退出,我們的密鑰就基本上就位了。

分離主密鑰

先將所有密鑰導出,用作主密鑰的備份:

1
gpg2 --export-secret-keys '[email protected]' > exported_keys

再導出所有子密鑰:

1
gpg2 --export-secret-subkeys AAAAAAAAAAAAAAAA! BBBBBBBBBBBBBBBB! > exported_subkeys

這裡的感嘆號「!」告訴 GPG 程序只導出具有相應 keyid 的子密鑰,否則 GPG 會將主密鑰一併導出。

然後我們就可以將 keyring 中的所有密鑰刪除了:

1
2
gpg2 --delete-secret-keys '[email protected]'
gpg2 --delete-keys '[email protected]'

這時再導入前面導出的子密鑰:

1
gpg2 --import exported_subkeys

列一下 keyring 中的私鑰:

1
gpg2 --list-secret-keys --keyid-format long

此時的輸出應該是類似這樣的:

1
2
3
4
5
------------------------------------
sec#  rsa4096/XXXXXXXXXXXXXXXX 2016-10-08 [C]
uid                 [ unknown] Your Name <[email protected]>
ssb   rsa4096/AAAAAAAAAAAAAAAA 2016-10-08 [S]
ssb   rsa4096/BBBBBBBBBBBBBBBB 2016-10-08 [E]

Sec 後面出現一個井字符號「#」,說明主密鑰的私鑰不存在,至此主密鑰 已經分離成功了。

由於這些密鑰是我們自己的,所以還應該把信任等級從「unknown」改爲 「ultimate」:

1
gpg2 --edit-key '[email protected]'

在 GPG 命令行中輸入 trust 命令,程序會詢問信任等級:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I don't know or won't say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu

Your decision? 5

選擇「I trust ultimately」並退出。

現在我們把 exported_subkeys 文件刪除,然後將 exported_keys 文件 轉移到一個「乾淨」的U盤或存儲卡中,再鎖進保險箱,就大功告成了。

現在我們的主密鑰和日常使用的 keyring 已經通過物理手段隔離了,即使 keyring 所在的電腦被偷或者被破壞,主密鑰也不會泄漏,因此也沒有人能冒用我們的身份。

日常使用

分離主密鑰之後的 GPG 密鑰在進行簽名/加密/解密操作時與普通密鑰並沒有區 別,因爲簽名/加密等操作使用的是子密鑰。

但是在編輯密鑰和給別人的公鑰簽名時必須用到主密鑰(的私鑰部分),爲了方 便地完成這些操作,我們可以用 gpg2 命令的 --homedir 選項生成一個 獨立的 keyring,往其中導入我們的主密鑰,再將這個 keyring 與上面生成的 exported_keys 文件保存在一起。

更安全的做法

在常用電腦上生成密鑰可能不夠安全,更好的做法是,在「乾淨」並且沒有網絡 連接的電腦上——或者啓動到 live 系統——生成密鑰,再在常用電腦上導入生成的 子密鑰。這樣可以保證主密鑰在分離前不會泄漏。