使用 GPG 對 git commit 簽章

GPG 是用在加密與簽章的軟體,也可以拿來對 git commit 做簽章。因簽章具備了不可否認性,因此對 commit 做簽章也算是表現出對作者 commit 負責的態度。

安裝

Mac 系統可以使用 Homebrew 安裝

brew install gnupg

產生 GPG key

執行 gpg 指令產生 GPG key,它有幾種方法可以產生,以下使用 --full-generate-key 來產 key:

gpg --full-generate-key

實際在 MacOS Catalina 10.15.3 系統試過,中文會出現亂碼,這裡可以改使用英文:

env LC_ALL="en_US.UTF-8" gpg --full-generate-key

如果在中文亂碼的狀況下建出 ~/.gnupg,之後即使使用英文環境參數,一樣還是會出現亂碼,這狀況只要把 ~/.gnupg 目錄移除即可。

首先第一個問題是問要產哪一種類型的 key,因需求只有簽章,就選 4 來用看看:

gpg (GnuPG) 2.2.19; Copyright (C) 2019 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.

gpg: directory '/Users/miles/.gnupg' created
gpg: keybox '/Users/miles/.gnupg/pubring.kbx' created
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)
  (14) Existing key from card
Your selection? 4
RSA keys may be between 1024 and 4096 bits long.

長度 GitHub 說至少要 4096 以上

What keysize do you want? (2048) 4096
Requested keysize is 4096 bits

再來會問說,這個 key 的有效期限,預設是 0。

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 上要建立 ID 與 email 等資訊,下面的範例是 comment 沒打:

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

Real name: yourname
Email address: [email protected]
Comment:
You selected this USER-ID:
    "yourname <[email protected]>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?

最後是為 key 打上密碼,這是 key 遺失的最後一道防線,跟產生 SSH Key 一樣,也可以選擇不打:

┌──────────────────────────────────────────────────────┐
│ Please enter the passphrase to                       │
│ protect your new key                                 │
│                                                      │
│ Passphrase: ________________________________________ │
│                                                      │
│       <OK>                              <Cancel>     │
└──────────────────────────────────────────────────────┘

產生 key 後的訊息如下(以下用我自己產生的 GPG key 為例):

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: /Users/yourname/.gnupg/trustdb.gpg: trustdb created
gpg: key C2E48B0C8A230FDA marked as ultimately trusted
gpg: directory '/Users/yourname/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/Users/yourname/.gnupg/openpgp-revocs.d/0E61EBDE7C19062A6AE93318C2E48B0C8A230FDA.rev'
public and secret key created and signed.

Note that this key cannot be used for encryption.  You may want to use
the command "--edit-key" to generate a subkey for this purpose.
pub   rsa4096 2020-02-11 [SC]
      0E61EBDE7C19062A6AE93318C2E48B0C8A230FDA
uid                      yourname <[email protected]>

上面有個 0E61EBDE7C19062A6AE93318C2E48B0C8A230FDA,為 GPG key 的 fingerprint。

查詢 GPG key

使用指令可以查訊 GPG key,會看到上述的 fingerprint

gpg --list-secret-keys

它可以加上參數 --keyid-format long--keyid-format short 來改變顯示的格式,差異如下:

Fingerprint:   0E61EBDE7C19062A6AE93318C2E48B0C8A230FDA
Long:                                  C2E48B0C8A230FDA
Short:                                         8A230FDA

使用 --armor--export 參數可以把 key 匯出來給 GitHub 用。照 GitHub 文件的範例,使用 Long 格式即可:

gpg --armor --export C2E48B0C8A230FDA

GPG public key 跟 SSH 的 public key 很像:

-----BEGIN PGP PUBLIC KEY BLOCK-----
...
...
...
-----END PGP PUBLIC KEY BLOCK-----

到 GitHub Add new GPG Key 頁面(需登入)把上面的內容貼進去,即可完成公鑰部署。

對自己的 commit 負責

最後就是對 commit 簽章。首先要先設定 git config:

git config --global user.signingkey C2E48B0C8A230FDA

接著就是 commit 的時候加上 -S 即可:

git commit -S -m "Signed commit"

如果懶得加 -S 也可以設定 git,讓它成為預設行為:

git config --global commit.gpgsign true

2024/5/1 補充

如果用新版的 gpg 產生金鑰的話,它會提示必須要輸入口令(Passphrase)才能順利產生金鑰,但如果直接 commit 的話會出現下面這個錯誤:

error: gpg failed to sign the data:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
[GNUPG:] KEY_CONSIDERED 4CE45AD8AC74A271CD11D8CEB4E0365D5DF0D7FA 2
[GNUPG:] BEGIN_SIGNING H10
[GNUPG:] PINENTRY_LAUNCHED 3801 curses 1.3.0 - xterm-256color - - 501/20 0
gpg: signing failed: Inappropriate ioctl for device
[GNUPG:] FAILURE sign 83918950
gpg: signing failed: Inappropriate ioctl for device

fatal: failed to write commit object

有查過很多方法,但其實原因就是它無法正常叫出介面來使用者輸入密碼而已(目前看起來是 Mac 特有的問題)。接著有查到替代方法,是使用 pinentry-mac

brew install gnupg pinentry-mac

接著編輯 ~/.gnupg/gpg-agent.conf 檔,加入下面這一行字,但要注意右邊執行檔的位置,可能還是要看上面安裝指令實際裝到哪個地方:

pinentry-program /opt/homebrew/bin/pinentry-mac

接著 kill GPG agent,讓它能夠重新啟動:

gpgconf --kill gpg-agent

接著 commit 就會出現 UI 讓使用者可以輸入密碼了。

查詢簽章

GitHub 的 commit 列表會有 verified 的字樣,而 commit 只要下 git show --show-signature 也能查得到,以本文章建立的 commit 範例如下:

commit 82ff8b2524480678e4702b45b0e970fb49ea686f (HEAD -> master, origin/master)
gpg: 由 二  2/11 16:01:05 2020 CST 建立的簽章
gpg:                使用 RSA 金鑰 0E61EBDE7C19062A6AE93318C2E48B0C8A230FDA
gpg: 完好的簽章來自於 "Miles <[email protected]>" [ultimate]
Author: MilesChou <[email protected]>
Date:   Tue Feb 11 16:01:05 2020 +0800

參考文章