如何使用 GPG (GnuPG) 對 Git Commit 與 Tag 進行簽章 | The Will Will Web

The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

如何使用 GPG (GnuPG) 對 Git Commit 與 Tag 進行簽章

最近將 Microsoft Store 上的 Ubuntu 20.04 LTS 安裝起來,這是 WSL (Windows Subsystem for Linux) 的執行環境,我在設定的過程中,原本想把我常用來對 Git 簽章的 GPG Key 匯入,但卻怎樣也無法 commit 新版本,花了我好些時間才理解背後的原理,索性就把我所知道的 GPG 知識與經驗分享出來吧!

簡介 GPG

GPG 全名為 The GNU Privacy Guard,又稱 GnuPG,是一套 OpenPGP 標準規格 RFC4880 的開源實作。你可以用 GnuPG 來對訊息或檔案進行加密(encrypt)或簽章(sign)。GPG 是一組 CLI 命令列工具,可以很容易的跟其他應用程式整合,例如 Git 或 Outlook 就經常使用,也支援 S/MIME 與 Secure Shell (ssh) 等應用。

在 Linux 或 macOS 作業系統底下,幾乎都已經內建 GPG 工具。如果你要在 Windows 使用的話,安裝 Gpg4win 即可,這套 Windows 實作不僅僅只有 CLI 工具,還有一個 GUI 介面的金鑰管理員(Kleopatra),以及 Outlook 外掛程式,你可以用來收發 PGP/MIME 郵件。

Windows 注意事項

當你 Gpg4win 安裝完成之後,必須開啟新的「命令提示字元」視窗,並且執行以下兩個步驟才算完成:

  1. 執行 where.exe gpg 命令,找出 gpg.exe 執行檔所在路徑。

    C:\Program Files (x86)\GnuPG\bin\gpg.exe

  2. 設定 Git 的 gpg.program 選項設定,指定 gpg.exe 所在路徑:

    git config --global gpg.program "C:\Program Files (x86)\GnuPG\bin\gpg.exe"
    

Linux/macOS/WSL 注意事項

使用 Bash 的人,請務必將以下設定加入到你的 ~/.profile~/.bashrc~/.bash_profile 檔案中:

export GPG_TTY=$(tty)

否則你就會在執行 gpg 簽章時發生以下錯誤:

error: gpg failed to sign the data
fatal: failed to write commit object

使用 fish 的人,請將以下設定加入到 ~/.config/fish/config.fish 檔案中:

set -x GPG_TTY (tty)

🔥 這個步驟非常重要,而且只需要設定一次,你可以透過 man gpg-agent 得知這個注意事項,初學者蠻容易忽略的。我今天在設定 WSL 的時候,就是遺漏了這個步驟,導致鬼打牆好久!

建立你的第一個 GPG 金鑰組(key-pair)

請使用 gpg --full-generate-key 建立全新的金鑰組:

$ gpg --full-generate-key
gpg (GnuPG) 2.2.4; Copyright (C) 2017 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

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)
Your selection?
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072)
Requested keysize is 3072 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 at all
Is this correct? (y/N)
Key is valid for? (0)
Key does not expire at all
Is this correct? (y/N) y

GnuPG needs to construct a user ID to identify your key.

Real name: Will Huang
Email address: will@example.com
Comment:
You selected this USER-ID:
    "Will Huang <will@example.com>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: key C22DCF85C7B99245 marked as ultimately trusted
gpg: directory '/home/will/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/home/will/.gnupg/openpgp-revocs.d/138711BA41E360645F37E23CC22DCF85C7B99245.rev'
public and secret key created and signed.

pub   rsa3072 2020-05-04 [SC]
      138711BA41E360645F37E23CC22DCF85C7B99245
uid                      Will Huang <will@example.com>
sub   rsa3072 2020-05-04 [E]

從產生金鑰的訊息中你會發現有以下這段:

gpg: key C22DCF85C7B99245 marked as ultimately trusted

預設從你的電腦建立的金鑰組,都會是「絕對信任」的金鑰。因為 GPG 對於本機的金鑰有區分不同的信任等級,當你使用 git log --show-signature 列出所有 Git 紀錄時,就會明確的寫出哪些簽章的紀錄是不可信任的!

列出目前安裝於本機的金鑰組

你可以從本機列出目前已有的金鑰資訊,金鑰區分「公開金鑰」與「私密金鑰」兩種。公開金鑰是可以在網路上流傳的版本,用來驗證特定訊息是否由同一個人所簽章過。私密金鑰是你必須安全保存的版本,用來對訊息簽章加密之用。

  • 列出公開金鑰資訊

    gpg --list-keys
    
    gpg --list-keys --keyid-format SHORT
    
    gpg --list-keys --keyid-format LONG
    
  • 列出私密金鑰資訊

    gpg --list-secret-keys
    
    gpg --list-secret-keys --keyid-format SHORT
    
    gpg --list-secret-keys --keyid-format LONG
    

如果我們要在 git commit 的時候,對 log 訊息進行 GPG 簽章,那麼你必須先列出私密金鑰資訊,並取得金鑰ID:

$ gpg --list-secret-keys --keyid-format SHORT
gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
/home/will/.gnupg/pubring.kbx
-----------------------------
sec   rsa3072/C7B99245 2020-05-04 [SC]
      138711BA41E360645F37E23CC22DCF85C7B99245
uid         [ultimate] Will Huang <will@example.com>
ssb   rsa3072/FFF9AFD5 2020-05-04 [E]

從上述訊息可發現 sec 就是 secret (私密金鑰) 的意思,而 138711BA41E360645F37E23CC22DCF85C7B99245 是完整的金鑰ID,你可以用簡短的金鑰ID替代,也就是 C7B99245 這段。

設定 Git 使用 GPG 金鑰進行簽章

執行 git commit 時使用 GPG 簽章 (-S)

git commit -m "OK" -SC7B99245

指定預設 GPG 金鑰

git config --global user.signingkey C7B99245
git commit -m "OK" -S

強制使用 GPG 簽章

git config --global commit.gpgsign true
git commit -m "OK"

請注意:當你第一次使用 GPG 簽章時,會自動跳出一個視窗讓你輸入私密金鑰的密碼,輸入完成才能完成 commit 動作。如果是使用 Gpg4win 的話,則會出現一個視窗讓你輸入密碼。這個過程,gpg 工具會自動啟動一個 gpg-agent 程式在背景執行,下次再進行簽章時,就不用再次輸入密碼。你可以透過 gpgconf --kill gpg-agent 命令,強制將該程式停用。

顯示 Git 簽章資訊

你可以用 git log --show-signature 命令顯示每個 commit 物件的簽章資訊,以下是「受信任」的 Log 訊息:

$ git log --show-signature
commit 7fbc730a8ca7d2b7f05873b4f79930d0cfc460b4 (HEAD -> master)
gpg: Signature made Mon May  4 16:10:51 2020 CST
gpg:                using RSA key 138711BA41E360645F37E23CC22DCF85C7B99245
gpg: Good signature from "Will Huang <will@example.com>" [ultimate]
Author: Will <will@example.com>
Date:   Mon May 4 16:10:51 2020 +0800

    OK

以下則是一個「不受信任」的 Log 訊息,但這依然是一個「好的簽章」(Good signature):

$ git log --show-signature
commit a006114a49be7bce37f00b09b9dcee2d4947c9de (HEAD -> master)
gpg: Signature made Mon May  4 17:59:50 2020 CST
gpg:                using RSA key 138711BA41E360645F37E23CC22DCF85C7B99245
gpg: Good signature from "Will Huang <will@example.com>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 1387 11BA 41E3 6064 5F37  E23C C22D CF85 C7B9 9245
Author: Will <doggy.huang@gmail.com>
Date:   Mon May 4 15:09:25 2020 +0800

    OK

你只要透過 --edit-key 參數,即可修改「信任層級」,本文稍後就會提到如何修改。

匯出 GPG 私鑰

當你想重灌電腦的時候,就需要將你的私鑰備份起來,以下是備份步驟:

gpg --export-secret-keys C7B99245 > willexample.key

匯出純文字版本的私密金鑰,只要加上 --armor 參數:

gpg --armor --export-secret-keys C7B99245 > willexample.key

匯出金鑰時會需要你輸入金鑰的密碼。

匯入 GPG 私鑰 (公鑰也會一併匯入)

gpg --import willexample.key

匯入金鑰時也會需要你輸入金鑰的密碼。

匯出 GPG 公鑰

由於 GPG 公鑰本來就是應該要公開的,許多人會將公鑰放在自己的個人網站上,或是直接放在 GitHub 給其他人下載。

匯出公鑰的方式如下:

gpg --export C7B99245 > willexample.gpg

匯出純文字版本的金鑰,只要加上 --armor 參數:

gpg --armor --export C7B99245 > willexample.gpg

其實你在註冊 GitHub 的時候,他們預設就已經幫你建立了一組 GPG 金鑰,其公鑰也會直接公布在網路上。以我的 GitHub 帳號為例 doggy8088,其公鑰的所在網址就是 https://github.com/doggy8088.gpg

匯入其他人的 GPG 公鑰

匯入別人的公鑰,可以直接透過以下命令匯入:

gpg --import willexample.gpg

如果要匯入我在 GitHub 的公鑰,則只要輸入以下命令:

curl -s -o doggy8088.gpg https://github.com/doggy8088.gpg
gpg --import doggy8088.gpg

匯入金鑰之後,執行 gpg --list-keys 命令會看到一個 [ unknown] 字樣,這意味著這個金鑰的信任層級未知

$ gpg --list-keys --keyid-format SHORT
/home/will/.gnupg/pubring.kbx
-----------------------------
pub   rsa3072/C7B99245 2020-05-04 [SC]
      138711BA41E360645F37E23CC22DCF85C7B99245
uid         [ultimate] Will Huang <will@example.com>
sub   rsa3072/FFF9AFD5 2020-05-04 [E]

你可以透過以下命令,將該金鑰設定為「絕對信任」層級:

  • Linux/macOS/WSL

    echo -e "trust\n5\ny\n" |  gpg --command-fd 0 --expert --edit-key C7B99245
    
  • Windows 命令提示字元

    (echo trust &echo 5 &echo y &echo quit) | gpg --command-fd 0 --edit-key C7B99245
    
  • Windows PowerShell

    $(echo trust; echo 5; echo y; echo quit) | gpg --command-fd 0 --edit-key C7B99245
    

透過金鑰伺服器傳遞公鑰

透過 gpg --send-keys C7B99245 命令,可以將你的公鑰自動送到系統預設內建的 金鑰伺服器 (keyserver)。像我的 Ubuntu 18.04.2 LTS 作業系統,預設就設定了 hkps.pool.sks-keyservers.net 這台當成我的預設金鑰伺服器,執行時可以看到以下訊息:

$ gpg --send-keys C7B99245
gpg: sending key C22DCF85C7B99245 to hkps://hkps.pool.sks-keyservers.net

接著你找到另外一台電腦,就可以透過以下命令直接匯入你的公鑰:

gpg --keyserver hkps.pool.sks-keyservers.net --recv-key C7B99245

除此之外,你也可以找一台更為穩定的金鑰伺服器來上傳,例如 keyserver.ubuntu.com 就比預設的穩定很多,速度也較快:

gpg --keyserver keyserver.ubuntu.com --send-keys C7B99245

上傳到金鑰伺服器之後,你就可以到 https://keyserver.ubuntu.com/ 直接透過你的 E-mail 信箱即可搜尋到公鑰!

也可以透過命令快速匯入:

gpg --keyserver keyserver.ubuntu.com --recv-key C7B99245

刪除用不到的金鑰組

如果公鑰私鑰都在同一台電腦,你必須先刪除私鑰,才能刪除相對應的公鑰。也可以直接刪除私鑰就好,公鑰保留。

  • 刪除私鑰

    gpg --delete-secret-key C7B99245
    
  • 刪除公鑰

    gpg --delete-key C7B99245
    

相關連結