The Will Will Web

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

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

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

資訊安全 (Information security) 有三個重要的核心概念,分別是機密性(Confidentiality)、完整性(Integrity) 與可用性(Availability),簡稱 CIA!當我們在考量「檔案傳輸」的安全性時,有好幾種不同的安全層級可以考量。若是透過網路進行檔案傳輸,你肯定會想到使用 TLS 傳輸層安全性協定,透過 TLS 可以確保傳輸過程的機密性完整性,但若透過 USB 離線傳輸呢?此時你就無法確保這兩點了!因此我們會需要更好的工具來達成資訊安全的目的,這裡我將介紹如何用 GPG 這套工具。

簡介 GPG (GnuPG)

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

我們從維基百科的 GNU Privacy Guard 文章,可以看到關於 GPG 的完整歷史沿革,建議大家可以先抽空看過。在我先前的 如何使用 GPG (GnuPG) 對 Git Commit 與 Tag 進行簽章 文章中,也曾經介紹過 GPG 這套工具。

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

# 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 就會內含 gpg 命令列工具,不過你還要特別設定 PATH 環境變數才能方便的使用 C:\Program Files\Git\usr\bin\gpg.exe 工具。以下是我認為修改 PATH 使用者環境變數最安全的方法,直接使用 SetEnv 命令列工具來修改比較不會弄亂現有環境變數。

# 先用 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 還附有一個 GUI 介面的 金鑰管理員(Kleopatra) 與 Outlook 外掛程式,你可以用來收發 PGP/MIME 郵件,需要的人還是可以自行安裝。

GPG 的運作原理

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

要瞭解 GPG 的運作原理,就要先瞭解他的祖先 Pretty Good Privacy 的設計結構,我們可以從下圖來剖析 PGP 的加解密過程:

PGP Diagram

  • 加密的過程

    首先,你是「發送端」的角色。

    你需要準備一份「明文」資料(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 之中,每一把金鑰會需要設定信任等級,用以識別這些金鑰的信任程度。

無論匯入的金鑰是否受信任,都不影響金鑰的效力(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)

    這個信任等級幾乎等同於 UnknownUndefined,這個層級的金鑰一樣是不可信任的。

    這個 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 nameEmail 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 這四個字進行明文簽章的結果:

    echo "test" | gpg --clearsign
    

    第一次簽章會需要輸入你的私密金鑰Passphase (密碼):

    2022-05-11_13-10-57

    注意:輸入完正確的密碼後,GPG 會自動啟動一個 gpg-agent 背景程序,暫時記憶你剛剛輸入的密碼,所以在短時間內不需要再次輸入密碼。

    密碼輸入正確就會將訊息(test)進行簽章,並輸出類似以下的內容:

    ❯ 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 代表你想建立一個「明文」的簽章,其結果會包含原本的訊息內容,並附上簽章資料!

    如果我們想把簽章的結果輸出到一個檔案,可以這樣執行:

    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 檔案中

      export GPG_TTY=$(tty)
      
    • 使用 Zsh 的人,請將以下命令加入到你的 ~/.zprofile 檔案中

      export GPG_TTY=$(tty)
      
    • 使用 Fish 的人,請將以下命令加入到你的 ~/.config/fish/config.fish 檔案中

      set -x GPG_TTY (tty)
      

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

    少數 macOS 用戶可能還是會遇到上述設定還是無法成功對訊息簽章的狀況,那麼建議你加裝 pinentry-mac 工具,其設定步驟如下:

    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 有提供「壓縮簽章」的版本。

    我們想對這個檔案進行簽章處理,其命令如下:

    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 命令,可以將「簽章內容」放在另一個獨立檔案中,如此一來就相當實用了! 👍

    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) 使用來當作安全的 E-mail 傳輸之用,而傳送 E-mail 的時候不允許使用二進位格式的內容來傳輸,所有二進位的檔案內容最終都要編碼成文字型態才能使用。然而,我們要將郵件內容加密,勢必會需要將加密過的郵件內容轉成文字格式,而當時這個轉換功能就被稱為 ASCII-Armor

    因此,GPG 也提供了一個 --armor-a 選項,可以讓你輸出一個「文字格式」的簽章檔內容,命令如下:

    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 原始內容為例進行驗證,其使用範例如下:

gpg --verify test.sig test.txt

如果簽章檔本身就包含了訊息內容本身,那麼命令就會長這樣:

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 就代表這是一個受信任的簽章!

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

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

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

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

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

結果如下:

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 取得我的公開金鑰 (我發佈到這裡),下載之後,就可以用以下命令匯入:

gpg --import doggy8088.gpg

匯入成功後,你將會看到以下內容:

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

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

接著,你就可以開始加密檔案給某人了:

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

# or

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

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

對訊息進行解密

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

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 (鑰匙圈)

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

  • 列出公開金鑰清單

    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
    

    輸出範例如下:

    sec   rsa3072/6CD3257D 2022-05-11 [SC]
          469B8A7CD0BF30AD3F58AEEB9901E05A6CD3257D
    uid         [ultimate] Will Huang <will@example.com>
    ssb   rsa3072/25580607 2022-05-11 [E]
    
    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 私鑰

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

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

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

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

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

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

gpg --import willexample.key

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

匯出 GPG 公鑰

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

匯出公鑰的方式如下:

gpg --export 6CD3257D > willexample.gpg

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

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

其實你可以將 GPG 公開金鑰發佈到 GitHub 帳號下,你可以在使用者設定的 SSH and GPG keys 頁面進行新增。以我個人的 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
/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]

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

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

    echo -e "y\n" |  gpg --command-fd 0 --expert --sign-key 70187EC1
    
  • Windows 命令提示字元

    (echo y) | gpg --command-fd 0 --sign-key 70187EC1
    
  • Windows PowerShell

    $(echo y) | gpg --command-fd 0 --sign-key 70187EC1
    

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

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 可以列出所有驗證過簽名有效的公開金鑰清單:

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。執行時可以看到以下訊息:

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

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

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

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

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

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

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

刪除用不到的金鑰組

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

  • 刪除私鑰 (記得備份)

    gpg --delete-secret-key 6CD3257D
    
  • 刪除公鑰

    gpg --delete-key 6CD3257D
    

相關連結