# 使用 GnuPG 確保傳輸重要檔案或文件的過程能確保機密性與完整性

當我們要進行機密文件傳輸時，可能會面臨許多不同的傳輸方式，但是過程中必須確保資訊安全，因此你就會需要考量到許多因素。這篇文章我打算分享如何利用 GPG 工具對檔案進行**簽章**與**加密**，檔案無論透過任何方式傳送給對方，都只有明確指定的對象可以開啟檔案，藉此達到**資訊安全**的中的**機密性**(Confidentiality)與**完整性**(Integrity)要求。

資訊安全 ([Information security](https://en.wikipedia.org/wiki/Information_security#cite_note-78)) 有三個重要的核心概念，分別是**機密性**(Confidentiality)、**完整性**(Integrity) 與**可用性**(Availability)，簡稱 [CIA](https://www.techtarget.com/whatis/definition/Confidentiality-integrity-and-availability-CIA)！當我們在考量「檔案傳輸」的安全性時，有好幾種不同的安全層級可以考量。若是透過網路進行檔案傳輸，你肯定會想到使用 TLS 傳輸層安全性協定，透過 TLS 可以確保傳輸過程的**機密性**與**完整性**，但若透過 USB 離線傳輸呢？此時你就無法確保這兩點了！因此我們會需要更好的工具來達成資訊安全的目的，這裡我將介紹如何用 GPG 這套工具。

### 簡介 GPG (GnuPG)

基本上 GPG ([GNU Privacy Guard](https://en.wikipedia.org/wiki/GNU_Privacy_Guard)) 源自於 PGP ([Pretty Good Privacy](https://en.wikipedia.org/wiki/Pretty_Good_Privacy))，當初是為了能夠提供更安全的電子郵件傳輸所設計的一套**加解密**工具。但是 PGP 是一套商用軟體，為了能夠提供大眾另一套開源且自由的選擇，因此先發展出了 [OpenPGP](https://www.openpgp.org/) 標準，並制訂 [RFC4880](https://www.ietf.org/rfc/rfc4880.txt) 標準規格，接著就實作出了 [GPG](https://gnupg.org/) 這套工具，它不但能支援對 E-mail 進行加密，也能對任何數位檔案進行加密(encrypt)、解密(decrypt)、簽章(sign)、驗章(verify)等處理。GPG 是一組 CLI 命令列工具，可以很容易的跟其他應用程式整合，例如 Git 或 Outlook 就經常使用，也支援 S/MIME 與 Secure Shell (ssh) 等應用。

我們從維基百科的 [GNU Privacy Guard](https://en.wikipedia.org/wiki/GNU_Privacy_Guard) 文章，可以看到關於 GPG 的完整歷史沿革，建議大家可以先抽空看過。在我先前的 [如何使用 GPG (GnuPG) 對 Git Commit 與 Tag 進行簽章](/post/2020/05/04/How-to-use-GPG-sign-git-commit-and-tag-object) 文章中，也曾經介紹過 GPG 這套工具。

在 Linux 或 macOS 作業系統底下，幾乎都已經內建 GPG 工具。但是很有可能是 `gpg1` 的舊版，你可以用 `gpg --version` 查詢目前的 `gpg` (GnuPG) 版本是否在 2.x 以上，以及工具支援的**產生金鑰的演算法**(Pubkey)、**加密演算法**(Cipher)、**雜湊演算法**(Hash)與**壓縮演算法**(Compression)等等。

```powershell
# gpg --version
gpg (GnuPG) 2.2.29-unknown
libgcrypt 1.9.3-unknown
Copyright (C) 2021 Free Software Foundation, Inc.
License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /c/Users/user/.gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2
```

如果你是 Windows 使用者的話，你只要安裝 [Git for Windows](https://www.git-scm.com/download/win) 就會內含 `gpg` 命令列工具，不過你還要特別設定 `PATH` 環境變數才能方便的使用 `C:\Program Files\Git\usr\bin\gpg.exe` 工具。以下是我認為修改 `PATH` 使用者環境變數**最安全**的方法，直接使用 [SetEnv](https://github.com/doggy8088/SetEnv/releases/download/1.0/SetEnv.exe) 命令列工具來修改比較不會弄亂現有環境變數。

```powershell
# 先用 PowerShell 下載設定環境變數的利器 SetEnv（你也可以手動下載）
Invoke-WebRequest -Uri "https://github.com/doggy8088/SetEnv/releases/download/1.0/SetEnv.exe" -OutFile "SetEnv.exe"

# 增加 PATH 使用者環境變數（這段命令支援 PowerShell 也支援 Command Prompt 環境）
SetEnv.exe -ua PATH %"C:\Program Files\Git\usr\bin"
```

> [Gpg4win](https://www.gpg4win.org/) 還附有一個 GUI 介面的 **金鑰管理員**(**Kleopatra**) 與 **Outlook 外掛程式**，你可以用來收發 **PGP/MIME** 郵件，需要的人還是可以自行安裝。

### GPG 的運作原理

GPG 為了要能做到**機密性**(Confidentiality)的要求，我們要確保**發送端**(Sender)到**接收端**(Receiver)之間的關係不能有其他**第三人**會看到「明文」內容，因此 PGP 使用到一系列**雜湊演算法**、**資料壓縮演算法**、**對稱金鑰演算法**與**公鑰加密演算法**來完成加密任務。

要瞭解 GPG 的運作原理，就要先瞭解他的祖先 [Pretty Good Privacy](https://en.wikipedia.org/wiki/Pretty_Good_Privacy) 的設計結構，我們可以從下圖來剖析 PGP 的加解密過程：

![PGP Diagram](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/7aac632aabf6adb9015b-167760614-bd2aa838-664a-479b-9646-463e40eb735d.png)

-   加密的過程
    
    首先，你是「發送端」的角色。
    
    你需要準備一份「明文」資料(Data)，也就是未加密的、要傳輸給對方的文件檔案。此外，你還要準備「接收端」的「公開金鑰」才能完成整個加密過程！
    
    -   動態產生一把**亂數金鑰**(Random Key)，這是一把**對稱式金鑰**(symmetric-key)
        
        > 注意：有時候這把 Random Key 會稱為 **Session Key**
        
    -   將你的「明文」資料，用這把**亂數金鑰**加密這份資料，變成「密文」資料
        
    -   使用「接收端」的「公開金鑰」將這把「亂數金鑰」加密處理，變成「加密過的金鑰」資料
        
    -   接著你就可以將這份「密文」與「加密過的金鑰」，我們簡稱為「加密訊息」(Encrypted Message)，你需要將這份「加密訊息」透過任何方式傳送給「接收端」
        
-   解密的過程
    
    此時，你是「接收端」的角色。
    
    你會收到一份「加密訊息」(Encrypted Message)，也就一份「密文」與一把「加密過的金鑰」！
    
    -   此時你是無法將「密文」解開的，因為你要先拿到**未加密的**「亂數金鑰」才能擁有一把可以解密的金鑰。
        
    -   由於這把「亂數金鑰」是用「接收端」的「公開金鑰」加密的，你只要拿你的「私密金鑰」就可以解開這份「亂數金鑰」。
        
        > 注意：這件事確保只有你可以解開這把「亂數金鑰」！
        
    -   拿著你解開的「亂數金鑰」將這份「密文」解開，你就可以拿到最當初的「明文」資料！
        

這個過程非常巧妙的完成訊息的安全傳輸，當你需要將文件發送給 100 個人的時候，雖然你需要取得這 100 人的公開金鑰，但加密的時候，也只需要將你的「明文」資料加密一次而已，大幅的提高加解密的效率！ 👍

使用 GPG 加解密檔案，並不需要依賴複雜的 PKI 基礎建設，但雙方依然都要先各自產生 GPG 金鑰組(key-pair)，並且「接收端」要先匯入「發送端」的「公開金鑰」，並先設定「信任」對方的身份，這是建立兩方「信任傳輸」的基礎，非常重要！

每台電腦的 GPG 工具都會維護著一份 GPG Keychain (鑰匙圈)，在匯入金鑰時，這些金鑰都會匯入到 GPG Keychain 之中，每一把金鑰會需要設定[信任等級](https://gpgtools.tenderapp.com/kb/faq/what-is-ownertrust-trust-levels-explained)，用以識別這些金鑰的信任程度。

> 無論匯入的金鑰是否受信任，都不影響**金鑰的效力**(validity of the key)，只要金鑰匯入到你的電腦，金鑰的「有效性」是一定會檢查的。

想像一下，你如果是「接收端」，預計接收一個「發送端」傳來的檔案，你如何「相信」這個檔案沒有被竄改？從 GPG 的角度來看，你要先「匯入對方的公開金鑰」到你的電腦，然後用這把金鑰解密檔案。但是你取得「發送端」的「公開金鑰」過程安全嗎？沒人知道！只有你能確認這件事，對吧？！所以從工具的角度來看，他是無法識別金鑰的「可信程度」，只有「你」可以「標註」這個金鑰的「信任程度」或「信任等級」！

GPG 在設定金鑰信任時會區分以下幾種**信任等級**：

1.  絕對信任 (Ultimate)
    
    這個只會用在自己擁有的金鑰，任何訊息用這把金鑰簽章過的，都會被視為**可靠的**。
    
    想像一下，你從朋友那邊拿到一把金鑰，你對這把金鑰進行簽章處理，這個簽章的結果對你來說依然是信任的，因為哪是你自己用 **絕對信任**(Ulitmate) 的金鑰簽署的，即便這把金鑰並沒有設定信任等級。
    
2.  完整信任 (Full)
    
    這個等級只會用來設定信任簽署其他人的金鑰。
    
    舉個例子，如果**張三**的金鑰被你的好友**李四**簽署過，而**李四**的金鑰已經匯入到你的 GPG Keychain 中，並且設定為 **完整信任** (Full)，那麼這份被**李四**簽署過的金鑰，也就是**張三**的金鑰)，就是受信任的。這就是一個信任鍊的關係！
    
    > 你應該在確實**驗證**與**簽署**過**李四**的金鑰之後，才能將**李四**的金鑰設定為**完整信任清單**中 (Full ownertrust)！
    
3.  未知對象 (Unknown)
    
    任何金鑰在匯入 GPG Keychain 之後，預設就是 `Unknown` 信任等級。
    
    這意味著這把金鑰並不在預設的**信任清單**(ownertrust)中，也就是說，這把金鑰是**不受信任的**！
    
4.  未定義 (Undefined)
    
    這個等級與 `Unknown` 幾乎一樣，但又有點不太一樣，因為這個 `Undefined` 等級是一個被你明確宣告這是「未知對象」的狀態。
    
    通常這個狀態代表你想要在**日後才要找時間來確認**這把金鑰的**信任等級**。
    
5.  從未信任 (Never)
    
    這個信任等級幾乎等同於 `Unknown` 與 `Undefined`，這個層級的金鑰一樣是不可信任的。
    
    這個 `Never` 等級是一個被你明確宣告這是「確定不信任」的狀態。
    
6.  無名小卒 (Marginal)
    
    這個 `Marginal` 有**微不足道**的意思，簡單來說就是「不重要」的金鑰。你要用 3 把以上 `Marginal` 等級以上的金鑰簽署過的金鑰，才能視為是一把「有效的」金鑰。
    
    舉個例子，如果你把**張三**、**李四**、**王五**的金鑰設定為 `Marginal` 信任等級，而且這三位都願意簽署**老六**的金鑰，那麼**老六**的金鑰就是有效的。因為這種用法過於複雜，基本上不建議這樣使用！
    

有了上述完整的知識基礎，接下來就可以好好的應用 GPG 在實務的情境中了。

### 建立 GPG 金鑰組(key-pair)

請使用 `gpg --full-generate-key` 建立全新的**金鑰組**，先擁有一個**金鑰組**才能對訊息進行**數位簽章**(Digital Signature)。建立的過程中，若遇到需要選擇的地方而又不知道該如何選擇，採用**預設值**通常就是比較安全的設定：

1.  你要選擇金鑰的用途，預設是**加密**與**簽章**都會使用。
2.  金鑰的長度決定了加密的強度，建議選預設值即可。
3.  金鑰的有效期限，可以有效避免金鑰外洩的風險，但預設是不會過期。
4.  GnuPG 需要替金鑰給予一個「身份」識別的 User ID，你必須輸入 **Real name** 與 **Email address** 才能建立金鑰，而 **Comment** 欄位可以自己選擇要不要輸入。
5.  最後要替你的金鑰設定一組 **Passphase** (密碼)

```
$ 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 金鑰組之後，就可以開始對訊息進行**簽章**，以下我示範幾個比較長見的 `gpg` 命令：

1.  建立明文簽章
    
    這種類型的簽章，主要針對「文字訊息」進行簽章，而且輸出的內容也是「純文字」格式。
    
    以下是將 `test` 這四個字進行明文簽章的結果：
    
    ```bash
    echo "test" | gpg --clearsign
    ```
    
    第一次簽章會需要輸入你的**私密金鑰**的 **Passphase** (密碼)：
    
    ![2022-05-11\_13-10-57](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/1042e1b023643885f667-167773803-077a3c26-8743-4c54-b9b1-c6763a70bbff.jpg)
    
    > 注意：輸入完正確的密碼後，GPG 會自動啟動一個 `gpg-agent` 背景程序，暫時記憶你剛剛輸入的密碼，所以在短時間內不需要再次輸入密碼。
    
    密碼輸入正確就會將訊息(`test`)進行簽章，並輸出類似以下的內容：
    
    ```log
    ❯ echo "test" | gpg --clearsign
    -----BEGIN PGP SIGNED MESSAGE-----
    Hash: SHA256
    
    test
    -----BEGIN PGP SIGNATURE-----
    
    iQGzBAEBCAAdFiEEPa6gkcpWHS5IKpVBcsfm1hMB+TMFAmJ7RWwACgkQcsfm1hMB
    +TPPCwv7B7zxRgzz/jisqGXF7VlWk0BZ3g4W3OR564Wslt58n0EQN4uCqCB2gRtZ
    DEgRXYH1pYcEFde8/8xmiWKBgyONmFgeNDgyRhWMfsSsm5pFmFwBwWgQO3mDGIpG
    HRAtvDWZESt44We1ffZsTTlzjouzjlOx+o3qFLT6+wHKUASGRCJtajOfGmYQbx/X
    0DlsLV5L6pOuNrfZyvtkrstanPWGX6EHPvvS4kddSVaNuiIqiXDLNPnlZWQ5jjae
    oxwaYEazcu/rvc1LIA83lVMElUC0AFAwOUbfPm+sZQ/xNcqhyvx65uJbzZo/FoWf
    GuF32ejbyccGyAbZ5X9rMpo76ers5RvfAeJreHTgxz8oOeAIjlxthfW9DBTTFjm+
    6qe/Fb00s7kcVP7jg7weFQUXMF8lq7io4XFc6PvV8tVVC3gNbcWSlgzB6qgQcahF
    P+DuBEku8glCY3tewwK4cwsrtdPr+si7WHsgm4ETl+SLLbkpTOn5LO6CDNsRGpzb
    8MhUfos9
    =KbWy
    -----END PGP SIGNATURE-----
    ```
    
    這裡的 `--clearsign` 代表你想建立一個「明文」的簽章，其結果會包含原本的訊息內容，並附上簽章資料！
    
    如果我們想把簽章的結果輸出到一個檔案，可以這樣執行：
    
    ```bash
    echo "test" | gpg --output test.txt --clearsign
    ```
    
    > 注意：簽章後的結果，是跟原始訊息內容放在一起的，但是這種類型的！
    
    不過，如果是在 Linux (包含 WSL2 環境) 或 macOS 的 Shell 環境下，就可能會出現以下錯誤：
    
    ```
    will@DESKTOP-K3PI2H2:~$ echo "test" | gpg --clearsign
    -----BEGIN PGP SIGNED MESSAGE-----
    Hash: SHA512
    
    test
    gpg: signing failed: Inappropriate ioctl for device
    gpg: [stdin]: clear-sign failed: Inappropriate ioctl for device
    ```
    
    或是這個錯誤：
    
    ```
    error: gpg failed to sign the data
    fatal: failed to write commit object
    ```
    
    要能成功對訊息進行簽章，你必須事先設定好 `GPG_TTY` 環境變數，才能正常使用。由於每次登入都需要這個環境變數，所以建議將這段命令加入到你 Shell 的啟動檔之中。
    
    -   使用 Bash 的人，請將以下命令加入到你的 `~/.profile` 檔案中
        
        ```bash
        export GPG_TTY=$(tty)
        ```
        
    -   使用 Zsh 的人，請將以下命令加入到你的 `~/.zprofile` 檔案中
        
        ```bash
        export GPG_TTY=$(tty)
        ```
        
    -   使用 Fish 的人，請將以下命令加入到你的 `~/.config/fish/config.fish` 檔案中
        
        ```bash
        set -x GPG_TTY (tty)
        ```
        
    
    🔥 這個步驟非常重要，而且只需要設定一次，你可以透過 `man gpg-agent` 得知這個注意事項，初學者蠻容易忽略的。我今天在設定 WSL 的時候，就是遺漏了這個步驟，導致鬼打牆好久！
    
    > 少數 macOS 用戶可能還是會遇到上述設定還是無法成功對訊息簽章的狀況，那麼建議你加裝 [pinentry-mac](https://formulae.brew.sh/formula/pinentry-mac) 工具，其設定步驟如下：
    > 
    > ```bash
    > brew upgrade gnupg  # 這個步驟可能會花些時間
    > brew link --overwrite gnupg
    > brew install pinentry-mac
    > echo "pinentry-program /usr/local/bin/pinentry-mac" >> ~/.gnupg/gpg-agent.conf
    > killall gpg-agent
    > ```
    
2.  建立壓縮的簽章
    
    雖然說簽章的內容是固定大小，但是連同內容本身可能還是會占空間，因此 GPG 有提供「壓縮簽章」的版本。
    
    我們想對這個檔案進行簽章處理，其命令如下：
    
    ```bash
    gpg -o test.sig --sign test.txt
    ```
    
    > 注意：`gpg` 的用法是 `gpg [options] [command] [files]`，所以 `-o test.sig` 要放在命令的前方！
    
    我以一個大小 `1MB` 的文字檔 `test.txt` 進行簽章處理，最後產生的 `test.sig` 簽章檔大小約略只有 `2121 bytes` 而已，由於這是一份壓縮過的檔案，所以你是看不出原始內容的。但其實這份**簽章檔**內容有包含**未加密過**的原始訊息內容喔，這點必須特別注意！ 🔥
    
3.  建立獨立的簽章檔 (Detached signatures)
    
    有時候將「訊息內容」與「簽章內容」放在同一個檔案裡面並不方便，因為我們可能還是希望能夠開啟該檔案，所以 `gpg` 提供另一個 `--detach-sig` 命令，可以將「簽章內容」放在另一個獨立檔案中，如此一來就相當實用了！ 👍
    
    ```bash
    gpg -o test.sig --detach-sig test.txt
    ```
    
    這個命令會產生一個只有簽章的 `test.sig` 檔案，這個檔案一樣是壓縮過的內容，但沒有包含原始訊息內容。我以一個 `1MB` 的文字檔 `test.txt` 來簽章，這一個 `test.sig` 簽章檔只有 `438 bytes` 而已，非常的小！(壓縮過)
    
4.  建立**文字格式的**獨立簽章檔 (Detached signatures)
    
    這裡我要先介紹 ASCII-Armor 這個專有名詞。
    
    早期 PGP ([Pretty Good Privacy](https://en.wikipedia.org/wiki/Pretty_Good_Privacy)) 使用來當作安全的 E-mail 傳輸之用，而傳送 E-mail 的時候**不允許**使用**二進位格式**的內容來傳輸，所有**二進位的檔案內容**最終都要編碼成**文字型態**才能使用。然而，我們要將郵件內容加密，勢必會需要將加密過的郵件內容轉成**文字格式**，而當時這個轉換功能就被稱為 ASCII-Armor！
    
    因此，GPG 也提供了一個 `--armor` 或 `-a` 選項，可以讓你輸出一個「文字格式」的簽章檔內容，命令如下：
    
    ```bash
    gpg -o test.sig --armor --detach-sig test.txt
    ```
    
    其輸出的 `test.sig` 檔案內容如下：
    
    ```
    -----BEGIN PGP SIGNATURE-----
    
    iQGzBAABCAAdFiEEPa6gkcpWHS5IKpVBcsfm1hMB+TMFAmJ7atgACgkQcsfm1hMB
    +TOGHAv+N8b8YVzhDZqkB7UGcDehNdv+aaGaoCe2N04u69O1RL/nVRyv3yxcvA/F
    zoVoNP83oTw8kAtjabD5E5ngWWalcQsZSAYUnCmkLjWy5cSUSZPn8Bbd/AKVzhnh
    siRm4Xrh8uc6HH5bIX6bQ67pJ/iBhKzT7xaR2rxHggFLs9wDAGNWt0rmjDo7Xat6
    RlrEVqOSan46SnYHE9YO534BzZYFNqFpbYxcyzRnL7jV6jG7hZYtc8l8hjKwt2fM
    AG2x45JeY/KY9rOzX8NL8Dr1/f66M0NIzjG0wUeGCTwAsMYDCRbX1Yzw8IrT92U/
    4hwvNHFj7EuwmZ3VNRg5cjir4RrZW6JJquIcbu8YplHVVD7C3+93iZRezddL73Mc
    99DYI2DPmIwcjUrHBQmyhyI3umHNCbk6Bq5rxYLXLtvGcDpDbFTB6KnlHAziZkvc
    NgRlu8/v40PSZGNbcPT9Ow7PtWROBYaWXOfo4K14wIBvYm5Fba9wm1OJIM09uyNm
    KwXADh3q
    =jKMy
    -----END PGP SIGNATURE-----
    ```
    

### 對訊息進行驗章

簽章完成後，當然就要進行「驗章」功能。

我們以上述的 `test.sig` 檔案，搭配 `test.txt` 原始內容為例進行驗證，其使用範例如下：

```bash
gpg --verify test.sig test.txt
```

> 如果**簽章檔**本身就包含了訊息內容本身，那麼命令就會長這樣：

```bash
gpg --verify test.sig
```

驗章的時候會輸出以下資訊：

```
gpg: Signature made Wed May 11 16:05:06 2022 TST
gpg:                using RSA key 138711BA41E360645F37E23CC22DCF85C7B99245
gpg: Good signature from "Will Huang <will@example.com>" [ultimate]
```

你只要看到 `Good signature` 就代表這是一個**受信任**的簽章！

當然，如果你不單單只想驗章而已，而是要取出簽章中的原始內容，你可以這樣執行：

```bash
gpg -o test.txt --decrypt test.sig
```

> 注意：若你將命令寫成 `gpg --decrypt test.sig --output test.txt` 是無法執行的喔，這裡的 `--output` 參數一定要寫在 `--decrypt` 命令的前面。

這裡分享一個可以顯示簽章更完整資訊的命令：

```bash
gpg --list-packets -vv --show-session-key test.sig
```

結果如下：

```log
gpg: armor: BEGIN PGP SIGNATURE
# off=0 ctb=89 tag=2 hlen=3 plen=435
:signature packet: algo 1, keyid 72C7E6D61301F933
        version 4, created 1652255448, md5len 0, sigclass 0x00
        digest algo 8, begin of digest 86 1c
        hashed subpkt 33 len 21 (issuer fpr v4 3DAEA091CA561D2E482A954172C7E6D61301F933)
        hashed subpkt 2 len 4 (sig created 2022-05-11)
        subpkt 16 len 8 (issuer key ID 72C7E6D61301F933)
        data: [3070 bits]
```

### 對訊息進行加密

若是牽涉到「加密」與「解密」，就絕對不能忽略「發送端」與「接收端」的身份。你想使用 GPG 對訊息進行加密，就一定要先取得「對方」的 GPG 公開金鑰，並且匯入到你的 GPG Keychain 之中，然後你才能指定將訊息「加密」給「特定人」。

假設你想要傳送給我一個**加密檔案**，就可以先到 [https://github.com/doggy8088.gpg](https://github.com/doggy8088.gpg) 取得我的公開金鑰 (我發佈到這裡)，下載之後，就可以用以下命令匯入：

```bash
gpg --import doggy8088.gpg
```

匯入成功後，你將會看到以下內容：

```bash
gpg: key 1F1BD86D70187EC1: "Will Huang <xxxx@xxxx.com>" not changed
gpg: Total number processed: 1
gpg:              unchanged: 1
```

> 你也可以利用 `gpg --list-keys` 列出目前匯入的所有 Keys

接著，你就可以開始**加密**檔案給**某人**了：

```bash
gpg --recipient xxxx@xxxx.com --encrypt test.txt

# or

gpg -r xxxx@xxxx.com --encrypt test.txt
```

這個命令會產生 `test.txt.gpg` 檔案，該檔案一樣是壓縮過的，不過整個檔案都有**加密處理**，非常安全。

### 對訊息進行解密

解密的過程非常簡單，不過你可以試試解開「加密給我」的檔案：

```bash
gpg --decrypt test.txt.gpg
```

你會看到 `gpg: decryption failed: No secret key` 訊息，因為你並沒有我的「私密金鑰」，即便你是加密的那個人，一樣打不開你指名要給我的檔案！

```
gpg: encrypted with 2048-bit RSA key, ID 144D30F4B2A611D3, created 2018-04-29
      "Will Huang <xxxx@xxxx.com>"
gpg: decryption failed: No secret key
```

如果在我的電腦，你就會看到以下訊息，而且會產生 `test.txt` 原始內容：

```
gpg: encrypted with 2048-bit RSA key, ID 144D30F4B2A611D3, created 2018-04-29
      "Will Huang <xxxx@xxxx.com>"
```

講到這裡，你應該已經學會了「簽章」、「驗章」、「加密」、「解密」等主要功能了！ 👍

### 管理 GPG Keychain (鑰匙圈)

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

-   列出**公開金鑰**清單
    
    ```bash
    gpg --list-keys
    ```
    
    ```bash
    gpg --list-keys --keyid-format SHORT
    ```
    
    ```bash
    gpg --list-keys --keyid-format LONG
    ```
    
-   列出**私密金鑰**清單
    
    ```bash
    gpg --list-secret-keys
    ```
    
    ```bash
    gpg --list-secret-keys --keyid-format SHORT
    ```
    
    輸出範例如下：
    
    ```
    sec   rsa3072/6CD3257D 2022-05-11 [SC]
          469B8A7CD0BF30AD3F58AEEB9901E05A6CD3257D
    uid         [ultimate] Will Huang <will@example.com>
    ssb   rsa3072/25580607 2022-05-11 [E]
    ```
    
    ```bash
    gpg --list-secret-keys --keyid-format LONG
    ```
    
    輸出範例如下：
    
    ```
    sec   rsa3072 2022-05-11 [SC]
          469B8A7CD0BF30AD3F58AEEB9901E05A6CD3257D
    uid           [ultimate] Will Huang <will@example.com>
    ssb   rsa3072 2022-05-11 [E]
    ```
    
    > 上述輸出 `sec` 就是 `secret` (私密金鑰) 的意思，而 `469B8A7CD0BF30AD3F58AEEB9901E05A6CD3257D` 是完整的**金鑰ID**，你可以用簡短的金鑰ID替代，也就是 `6CD3257D` 這段。
    

### 匯出 GPG 私鑰

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

```bash
gpg --export-secret-keys 6CD3257D > willexample.key
```

匯出**純文字版本**的私密金鑰，只要加上 `--armor` 參數：

```bash
gpg --armor --export-secret-keys 6CD3257D > willexample.key
```

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

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

```bash
gpg --import willexample.key
```

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

### 匯出 GPG 公鑰

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

匯出公鑰的方式如下：

```bash
gpg --export 6CD3257D > willexample.gpg
```

匯出純文字版本的金鑰，只要加上 `--armor` 參數：

```bash
gpg --armor --export 6CD3257D > willexample.gpg
```

其實你可以將 GPG 公開金鑰發佈到 GitHub 帳號下，你可以在使用者設定的 [SSH and GPG keys](https://github.com/settings/keys) 頁面進行新增。以我個人的 GitHub 帳號為例 `doggy8088`，其公鑰的所在網址就是 [https://github.com/doggy8088.gpg](https://github.com/doggy8088.gpg)，非常好記！ 👍

### 匯入其他人的 GPG 公鑰

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

```bash
gpg --import willexample.gpg
```

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

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

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

```powershell
# gpg --list-keys --keyid-format SHORT
/c/Users/WDAGUtilityAccount/.gnupg/pubring.kbx
----------------------------------------------
pub   rsa2048/70187EC1 2018-04-29 [SC]
      3B024BEF47B2DED3D163B7521F1BD86D70187EC1
uid         [ unknown] Will Huang <xxxx@xxxx.com>
sub   rsa2048/B2A611D3 2018-04-29 [E]

pub   rsa3072/6CD3257D 2022-05-11 [SC]
      469B8A7CD0BF30AD3F58AEEB9901E05A6CD3257D
uid         [ultimate] Will Huang <will@example.com>
sub   rsa3072/25580607 2022-05-11 [E]
```

你可以使用自己的金鑰**簽署**這份**公開金鑰**，由你來確認這份公鑰是「可信任的」：

```bash
gpg --sign-key 70187EC1
```

你要手動按個 `y` 來確認簽署：

```
pub  rsa2048/1F1BD86D70187EC1
     created: 2018-04-29  expires: never       usage: SC
     trust: unknown       validity: unknown
sub  rsa2048/144D30F4B2A611D3
     created: 2018-04-29  expires: never       usage: E
[ unknown] (1). Will Huang <xxxx@xxxx.com>

pub  rsa2048/1F1BD86D70187EC1
     created: 2018-04-29  expires: never       usage: SC
     trust: unknown       validity: unknown
 Primary key fingerprint: 3B02 4BEF 47B2 DED3 D163  B752 1F1B D86D 7018 7EC1

     Will Huang <xxxx@xxxx.com>

Are you sure that you want to sign this key with your
key "Will Huang <will@example.com>" (9901E05A6CD3257D)

Really sign? (y/N) y
```

也可以透過 `--command-fd 0` 來自動化這個過程：

-   Linux/macOS/WSL
    
    ```bash
    echo -e "y\n" |  gpg --command-fd 0 --expert --sign-key 70187EC1
    ```
    
-   Windows 命令提示字元
    
    ```batch
    (echo y) | gpg --command-fd 0 --sign-key 70187EC1
    ```
    
-   Windows PowerShell
    
    ```powershell
    $(echo y) | gpg --command-fd 0 --sign-key 70187EC1
    ```
    

若在用一次 `gpg --list-keys` 命令列出所有金鑰，此時你就會看到這把公開金鑰已經是 `full` 信任等級了！

```bash
pub   rsa3072 2022-05-11 [SC]
      469B8A7CD0BF30AD3F58AEEB9901E05A6CD3257D
uid           [ultimate] Will Huang <will@example.com>
sub   rsa3072 2022-05-11 [E]

pub   rsa2048 2018-04-29 [SC]
      3B024BEF47B2DED3D163B7521F1BD86D70187EC1
uid           [  full  ] Will Huang <xxxx@xxxx.com>
sub   rsa2048 2018-04-29 [E]
```

若用 `gpg --check-signatures` 可以列出所有驗證過簽名有效的公開金鑰清單：

```bash
pub   rsa3072 2022-05-11 [SC]
      469B8A7CD0BF30AD3F58AEEB9901E05A6CD3257D
uid           [ultimate] Will Huang <will@example.com>
sig!3        9901E05A6CD3257D 2022-05-11  Will Huang <will@example.com>
sub   rsa3072 2022-05-11 [E]
sig!         9901E05A6CD3257D 2022-05-11  Will Huang <will@example.com>

pub   rsa2048 2018-04-29 [SC]
      3B024BEF47B2DED3D163B7521F1BD86D70187EC1
uid           [  full  ] Will Huang <xxxx@xxxx.com>
sig!3        1F1BD86D70187EC1 2018-04-29  Will Huang <xxxx@xxxx.com>
sig!         9901E05A6CD3257D 2022-05-11  Will Huang <will@example.com>
sub   rsa2048 2018-04-29 [E]
sig!         1F1BD86D70187EC1 2018-04-29  Will Huang <xxxx@xxxx.com>

gpg: 5 good signatures
```

### 透過金鑰伺服器傳遞公鑰

透過 `--send-keys` 命令，可以將你的公鑰自動送到 GPG 預設的 **金鑰伺服器** (`keyserver`)。像我的 Ubuntu 18.04.2 LTS 作業系統，預設就設定了 `hkps.pool.sks-keyservers.net` 這台當成我的預設金鑰伺服器。而我的 Git for Windows 預設的 `keyserver` 則是 `hkps://keyserver.ubuntu.com`。執行時可以看到以下訊息：

```bash
$ gpg --send-keys 6CD3257D
gpg: sending key 9901E05A6CD3257D to hkps://keyserver.ubuntu.com
```

除此之外，你也可以明確指定**金鑰伺服器**來上傳，例如 `keyserver.ubuntu.com` 就相當穩定，速度也較快：

```bash
gpg --keyserver keyserver.ubuntu.com --send-keys 6CD3257D
```

> 上傳**公開金鑰**到**金鑰伺服器**之後，你就可以到 [https://keyserver.ubuntu.com/](https://keyserver.ubuntu.com/) 直接透過你的 E-mail 信箱即可搜尋到公鑰！

接著你找到另外一台電腦，就可以透過 `--recv-key` 命令直接匯入你的公鑰，這樣就方便多了：

```bash
gpg --keyserver keyserver.ubuntu.com --recv-key 6CD3257D
```

> 如果要匯入我的公開金鑰，可以輸入 `gpg --keyserver keyserver.ubuntu.com --recv-key 70187EC1` 命令！

### 刪除用不到的金鑰組

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

-   刪除私鑰 (**記得備份**)
    
    ```bash
    gpg --delete-secret-key 6CD3257D
    ```
    
-   刪除公鑰
    
    ```bash
    gpg --delete-key 6CD3257D
    ```
    

### 相關連結

-   [What is the CIA Triad? Definition, Explanation and Examples](https://www.techtarget.com/whatis/definition/Confidentiality-integrity-and-availability-CIA)
-   [What is Ownertrust? Trust-levels explained / FAQ / Knowledge Base - GPGTools Support](https://gpgtools.tenderapp.com/kb/faq/what-is-ownertrust-trust-levels-explained)
-   [OpenPGP](https://www.openpgp.org/)
-   [The GNU Privacy Guard](https://gnupg.org/)
    -   [**The GNU Privacy Handbook**](https://www.gnupg.org/gph/en/manual.html)
    -   [Making and verifying signatures](https://www.gnupg.org/gph/en/manual.html#AEN136)
    -   [Exchanging keys](https://www.gnupg.org/gph/en/manual.html#AEN57)
-   [Gpg4win - Secure email and file encryption with GnuPG for Windows](https://www.gpg4win.org/)
-   [Hockeypuck OpenPGP keyserver](https://keyserver.ubuntu.com/)
-   [Introduction to GnuPG | Ian Atkinson](https://web.archive.org/web/20240907231134/https://www.ianatkinson.net/computing/gnupg.htm)
-   [GPG Cheat Sheet](https://gock.net/blog/2020/gpg-cheat-sheet/)
-   [PGP、OpenPGP 和 GnuPG 加密之間的區別](https://www.ecnetworker.com/2019/08/26/pgp%EF%BC%8Copenpgp%E5%92%8Cgnupg%E5%8A%A0%E5%AF%86%E4%B9%8B%E9%96%93%E7%9A%84%E5%8D%80%E5%88%A5/)
