將 OpenPGP 主私鑰保存在日常電腦之外。使用不同的子鑰執行簽署、解密與驗證身分。這可限制電腦遭入侵後攻擊者能做的事:認證新的子鑰與身分資訊需要主鑰。
此程序使用 Linux 上的 GnuPG、加密卸除式儲存裝置,以及位於 /dev/shm 的暫存鑰匙圈。你必須能辨識正確的鑰匙與儲存裝置。修改現有身分前,先用測試鑰匙練習。
範例沿用原始操作指南的 RSA-8192 主鑰與 RSA-4096 子鑰。RSA-8192 是範例設定,並非離線保存主鑰的必要條件。請換成你自己的完整指紋。透過 pinentry 輸入通行片語,不要寫進指令碼或 shell 歷史。
基礎 · 主鑰與子鑰的用途
發布公鑰,讓他人驗證簽章或加密寄給你的訊息。保護用來授權操作的私鑰,並分開各種能力:
| 能力 | 用途 | 此處使用的鑰匙 |
|---|---|---|
認證 [C] |
認證使用者 ID、子鑰及其他公鑰 | 主鑰 |
簽署 [S] |
簽署訊息、檔案或 Git 提交 | 簽署子鑰 |
加密 [E] |
接收加密資料,並用私密子鑰解密 | 加密子鑰 |
驗證身分 [A] |
向服務驗證身分,例如透過 gpg-agent 使用 SSH |
身分驗證子鑰 |
將認證與日常操作分開
- 主鑰只啟用認證能力。
- 分別建立簽署、加密與身分驗證子鑰。
- 將所有私鑰的已測試備份保存在加密卸除式儲存裝置。
- 測試還原後,從日常鑰匙圈移除主私鑰。
- 可選擇將日常子鑰移至 OpenPGP 智慧卡。
使用完整指紋辨識鑰匙。keygrip 是另一種識別碼,供 gpg-agent 辨識私鑰元件。刪除前先檢查兩者:
gpg --list-secret-keys --fingerprint --with-keygrip
進階 · 建立主鑰與三把子鑰
將此範本存為 primary_gpg_key_constructor.tpl,並替換姓名與電子郵件地址。此範例的 Expire-Date: 0 表示主鑰不過期;若你的鑰匙政策要求期限,請設定到期日。GnuPG 會透過 pinentry 要求通行片語。
%echo Generating a RSA key 8192
Key-Type: RSA
Key-Length: 8192
Key-Usage: cert
Name-Real: My Name
Name-Email: something@something
Expire-Date: 0
%commit
%echo done
gpg --batch --generate-key --enable-large-rsa primary_gpg_key_constructor.tpl
gpg --list-secret-keys
此範例用 --enable-large-rsa 建立 8192 位元鑰匙。確認主鑰僅顯示 [C]。以下為示意輸出:
sec rsa8192 2023-01-14 [C]
E6B0933DD47E3B2F823334F0A567B3B17C27BF6D
uid [ultimate] My Name <something@something>
gpg --expert --edit-key E6B0933DD47E3B2F823334F0A567B3B17C27BF6D
在 gpg> 提示字元加入子鑰:
- 簽署:
addkey,選 RSA(僅簽署)、4096 位元,並設定到期日。 - 加密:
addkey,選 RSA(僅加密)、4096 位元,並設定到期日。 - 身分驗證:
addkey,選 RSA(自訂能力)。停用S與E,啟用A,再選大小與到期日。
選單編號可能隨版本不同。請依能力選擇並確認結果。用 save 儲存變更,再列出所有指紋:
gpg --fingerprint --fingerprint E6B0933DD47E3B2F823334F0A567B3B17C27BF6D
pub rsa8192 2023-01-14 [C]
E6B0 933D D47E 3B2F 8233 34F0 A567 B3B1 7C27 BF6D
uid [ultimate] My Name <something@something>
sub rsa4096 2023-01-14 [S] [expires: 2030-01-12]
61DF 146E F7F6 0971 5E60 46B8 8C75 87C8 6EAC 2F2F
sub rsa4096 2023-01-14 [E] [expires: 2030-01-12]
DBBC AE3F 8166 9FF4 75B7 5F35 51D8 8833 7727 23AD
sub rsa4096 2023-01-14 [A] [expires: 2030-01-12]
F4CF F234 E969 2304 CC58 D55B D13A E6B6 5E8B E723
替換現有主鑰時,可在停用舊鑰前用它認證新鑰。請明確指定舊簽署鑰匙,不要依賴 GnuPG 的預設選擇:
gpg --local-user "$OLD_FINGERPRINT" --sign-key "$NEW_FINGERPRINT"
中階 · 選擇能持續維護的設定
若不需要 OpenPGP 認證,可考慮用 age 加密檔案,以及專用 SSH 鑰匙登入。若主鑰留在日常電腦上,請用通行片語保護、保存撤銷憑證,並測試加密備份。此設定仍讓主鑰暴露於日常電腦,應記錄這項限制。
移除主鑰前先備份與測試
在移除主鑰或將子鑰移至卡片前匯出私鑰。之後的匯出可能只有不可用鑰匙的參照,而非可還原的私密資料。匯出指令成功並不代表能成功還原。
以下 RAM 鑰匙圈可減少持久儲存寫入。/dev/shm 可能使用 swap,刪除其中檔案也不保證安全抹除。請使用可信任的環境,停用或加密 swap,且不使用休眠。連網或遭入侵電腦上的 RAM 鑰匙圈並非離線環境。
進階 · 匯出至加密裝置並測試還原
請在 Bash 中執行以下範例。先確認目前的 GnuPG 目錄:
gpgconf --list-dirs homedir
設定目的地與指紋,並確認預期的加密裝置已掛載。mountpoint 只檢查是否存在掛載點,不會驗證加密狀態或裝置身分。
set -euo pipefail
umask 077
ENCRYPTED_MOUNT="/run/media/user/Encrypted_Device"
EXPORT_DIR="$ENCRYPTED_MOUNT/secrets/gpg"
EXPORT_FILENAME="MySelf_Master_MyComment_private_rsa8192"
EXPORT_FINGERPRINT="E6B0933DD47E3B2F823334F0A567B3B17C27BF6D"
if ! mountpoint -q -- "$ENCRYPTED_MOUNT"; then
echo "Encrypted device is not mounted at $ENCRYPTED_MOUNT" >&2
exit 1
fi
install -d -m 0700 -- "$EXPORT_DIR"
每版備份使用新的檔名。保留上一份已測試的備份,直到新備份通過還原檢查。匯出完整私鑰、日常子鑰、公鑰及 ownertrust:
gpg --armor --output "$EXPORT_DIR/$EXPORT_FILENAME.private-key.sec" \
--export-secret-keys "$EXPORT_FINGERPRINT"
gpg --armor --output "$EXPORT_DIR/$EXPORT_FILENAME.subkeys.sec" \
--export-secret-subkeys "$EXPORT_FINGERPRINT"
gpg --armor --output "$EXPORT_DIR/$EXPORT_FILENAME.pubkey.asc" \
--export "$EXPORT_FINGERPRINT"
gpg --list-secret-keys --fingerprint --with-keygrip "$EXPORT_FINGERPRINT" \
> "$EXPORT_DIR/$EXPORT_FILENAME.keygrip.txt"
gpg --export-ownertrust > "$EXPORT_DIR/gnupg-ownertrust.txt"
趁主私鑰仍可用時產生撤銷憑證。妥善保護它們:取得憑證的人都能發布撤銷。以下範例涵蓋四種撤銷原因:
printf '%s\n' y 0 "" y | gpg --no-tty --command-fd 0 --armor \
--output "$EXPORT_DIR/revoke.no-reason.asc" --gen-revoke "$EXPORT_FINGERPRINT"
printf '%s\n' y 1 "" y | gpg --no-tty --command-fd 0 --armor \
--output "$EXPORT_DIR/revoke.compromised.asc" --gen-revoke "$EXPORT_FINGERPRINT"
printf '%s\n' y 2 "" y | gpg --no-tty --command-fd 0 --armor \
--output "$EXPORT_DIR/revoke.superseded.asc" --gen-revoke "$EXPORT_FINGERPRINT"
printf '%s\n' y 3 "" y | gpg --no-tty --command-fd 0 --armor \
--output "$EXPORT_DIR/revoke.no-longer-used.asc" --gen-revoke "$EXPORT_FINGERPRINT"
chmod 0600 -- "$EXPORT_DIR"/*
private-key.sec:主鑰與子鑰的私密資料,用於完整復原。subkeys.sec:日常私密子鑰,不含主私鑰。pubkey.asc:供發布的公開憑證。keygrip.txt:用於檢查鑰匙元件的識別碼。gnupg-ownertrust.txt:本機信任設定,匯入其他鑰匙圈前先檢查。revoke.*.asc:撤銷主鑰的憑證。
將完整私鑰備份匯入另一個暫存鑰匙圈:
umask 077
TEST_GNUPGHOME="$(mktemp -d /dev/shm/gnupg-test.XXXXXX)"
chmod 0700 "$TEST_GNUPGHOME"
gpg --homedir "$TEST_GNUPGHOME" \
--import "$EXPORT_DIR/$EXPORT_FILENAME.private-key.sec"
gpg --homedir "$TEST_GNUPGHOME" \
--list-secret-keys --fingerprint --with-keygrip "$EXPORT_FINGERPRINT"
確認完整指紋、可用的 sec 及三個 ssb 項目。測試簽署、解密先前加密的測試檔案,以及已設定的身分驗證。也要測試還原的主鑰能認證一把測試鑰匙。僅列出鑰匙並不能證明私密資料可用。
停止暫存代理程式,並且只移除上方建立的測試目錄:
case "$TEST_GNUPGHOME" in
/dev/shm/gnupg-test.*)
gpgconf --homedir "$TEST_GNUPGHOME" --kill all
rm -rf -- "$TEST_GNUPGHOME"
;;
*) echo "Unexpected test keyring path" >&2; exit 1 ;;
esac
確認復原成功後,只從日常鑰匙圈移除主私鑰。引號中的 ! 指定該元件;省略它也會刪除私密子鑰。繼續前先確認目前目錄與指紋。GnuPG 指令參考。
gpg --delete-secret-keys "${EXPORT_FINGERPRINT}!"
gpg --list-secret-keys "$EXPORT_FINGERPRINT"
sec# rsa8192 2023-01-14 [C]
E6B0933DD47E3B2F823334F0A567B3B17C27BF6D
uid [ultimate] My Name <something@something>
ssb rsa4096 2023-01-14 [S] [expires: 2030-01-12]
ssb rsa4096 2023-01-14 [E] [expires: 2030-01-12]
ssb rsa4096 2023-01-14 [A] [expires: 2030-01-12]
sec# 表示此鑰匙圈無法使用主私鑰,不能證明其他鑰匙圈、備份、檔案系統快照或可復原的磁碟資料中沒有副本。建立新身分時,請在離線環境產生主鑰,只將日常子鑰匯入日常電腦。
在獨立環境使用主鑰
在可信任的離線環境執行認證、身分變更與子鑰管理。只有執行必要操作時才連接加密備份。將它與日常電腦分開存放,並在不同地點保留另一份已測試副本。
sec actual primary secret available
sec# primary secret unavailable in this keyring
ssb secret subkey locally available
ssb# secret subkey unavailable
ssb> secret subkey on a smartcard
進階 · 還原、修改並測試新備份
從一般 GnuPG 目錄開始,不要預先覆寫 GNUPGHOME。在可信任的離線環境確認加密掛載點,並將備份匯入暫存目錄:
umask 077
ENCRYPTED_MOUNT="/run/media/user/Encrypted_Device"
EXPORT_DIR="$ENCRYPTED_MOUNT/secrets/gpg"
EXPORT_FILENAME="MySelf_Master_MyComment_private_rsa8192"
EXPORT_FINGERPRINT="E6B0933DD47E3B2F823334F0A567B3B17C27BF6D"
mountpoint -q -- "$ENCRYPTED_MOUNT" || exit 1
NORMAL_GNUPGHOME="$(gpgconf --list-dirs homedir)"
GNUPGHOME="$(mktemp -d /dev/shm/gnupg.XXXXXX)"
export GNUPGHOME
chmod 0700 "$GNUPGHOME"
gpg --import "$EXPORT_DIR/$EXPORT_FILENAME.private-key.sec"
gpg --import-ownertrust "$EXPORT_DIR/gnupg-ownertrust.txt"
gpg --list-secret-keys --fingerprint --with-keygrip "$EXPORT_FINGERPRINT"
編輯前確認主鑰指紋及可用的私密元件:
gpg --edit-key "$EXPORT_FINGERPRINT"
修改此鑰匙圈不會更新備份檔案。變更通行片語、到期日、使用者 ID、認證或子鑰後,請匯出新的候選備份:
NEW_BACKUP="$EXPORT_DIR/$EXPORT_FILENAME.private-key.NEW.sec"
gpg --armor --output "$NEW_BACKUP" --export-secret-keys "$EXPORT_FINGERPRINT"
chmod 0600 "$NEW_BACKUP"
將候選備份還原至第二個暫存鑰匙圈並重做檢查。同時更新並測試子鑰與公鑰匯出。通過後才封存前一版並替換整組備份。在他人取得公鑰的位置發布更新後的公開憑證。
case "$GNUPGHOME" in
/dev/shm/gnupg.*)
gpgconf --homedir "$GNUPGHOME" --kill all
rm -rf -- "$GNUPGHOME"
;;
*) echo "Unexpected working keyring path" >&2; exit 1 ;;
esac
unset GNUPGHOME
gpgconf --list-dirs homedir
確認最後的路徑與 NORMAL_GNUPGHOME 相同。在日常電腦上只匯入更新後的公開憑證與日常子鑰:
gpg --import "$EXPORT_DIR/$EXPORT_FILENAME.pubkey.asc"
gpg --import "$EXPORT_DIR/$EXPORT_FILENAME.subkeys.sec"
gpg --import-ownertrust "$EXPORT_DIR/gnupg-ownertrust.txt"
在日常電腦確認 sec#,並測試日常子鑰。在該電腦匯入 private-key.sec 會讓主私鑰再次可用。
撤銷受影響的鑰匙
進階 · 撤銷前先選取子鑰
若子鑰遭洩漏,在離線環境載入主鑰並編輯憑證:
gpg --expert --edit-key E6B0933DD47E3B2F823334F0A567B3B17C27BF6D
用 key N 選取受影響的子鑰。確認選取標記與指紋,執行 revkey,再執行 save。匯出並發布更新後的公開憑證。用 delkey 刪除本機鑰匙不會通知他人該鑰匙已撤銷。
主鑰遭洩漏或停用時,使用適當的主鑰撤銷憑證,並發布已撤銷的公開憑證。若仍需解密歷史資料,依你的資料保留與洩漏處理政策保存舊加密私鑰。
驗證結果
完成設定的條件是:完整備份已測試、撤銷憑證可取得、日常子鑰可正常使用,以及日常鑰匙圈無法使用主私鑰。記錄備份位置與還原程序。修改鑰匙後重新執行復原檢查。




