【本文尚未完工,将持续更新】

我是Oct,希望这篇文章能让你在折腾GPG的过程中少走点弯路(●’◡’●)

GNU Privacy GuardGnuPGGPG)是一个密码学软件,用于加密、签名通信内容及管理非对称密码学的密钥。GnuPG 是自由软件,遵循 IETF 订定的 OpenPGP 技术标准设计,并与 PGP 保持兼容1

其实折腾GPG对一般人来说并没有什么特别的好处,无非就是让你的互联网生活更“安全”了一点和更“繁琐”了一些。但对我来说,Github上绿色的Verified和公钥服务器上一连串的sig认证实在是太有吸引力了(太酷啦.jpg)。因此,为了让更多像我一样对“安全”、“繁琐”和“Cool”感兴趣的人在查找教程时省点力气,我决定开一篇GPG折腾指南,提供一点力所能及的帮助。

1. GPG密钥可以干什么

为什么要用GPG?GPG密钥的功能可以说十分繁多,数据加解密、数字签名、身份认证,甚至作为认识新好友之后的“信物”交换!

下面我将通过几个具体的例子向你介绍GPG密钥的用途:

  • 数据加解密:当你想要向A发送一条只想让ta知道的消息时,你可以使用A的公钥加密你的消息。因为GPG密钥是非对称加密密钥,因此其它持有公钥的人无法解密你的这条信息,只有持有对应私钥的A才能解密、读取你的消息。
  • 数字签名:A有一条数据想要发布,且ta想让人们能验证该数据的发布人是自己。此时A可以使用自己的GPG私钥对该数据“签名”,其它持有A的公钥的人经过一系列操作就可以验证该数据的发布人是A自己。该功能的典型应用就是Github的Commit后的绿色Verified。
  • 身份认证:和数字签名类似,身份认证也是利用了GPG密钥的非对称加密特性,即私钥是能得到保密的唯一身份令牌,且公钥与私钥是一一对应的。也就是说,某一方只要持有公钥,就可以验证对方是否持有公钥对应的私钥,又因为私钥应该是能得到保密的唯一身份令牌,进而可以认证对方的身份。这个用途看似简单,但现代Web安全的基石SSL正是建立在这之上。

2. 安装GPG

讲了这么多,终于要进入正题了。要使用GPG,就要先安装GPG(废话)。

要检查系统内是否安装了GPG,请在命令行中执行指令gpg --version,若出现GPG的版本信息,则说明GPG已经安装好了)

如果你的系统和我一样是Ubuntu22.04的话,恭喜你,Ubuntu已经贴心的预装了GPG。如果不一样,或者你的系统里没有gpg,那么这里提供三种GPG安装下载方式:

  1. GPG官网(提供可执行文件的下载):https://gnupg.org
  2. GPG的github(仅提供源代码,需要自行编译):https://github.com/gpg/gnupg
  3. Gpg4win(仅Windows,同时具有GPG命令行和图形化界面):https://www.gpg4win.org/

还有一种没提到的,就是从Linux的软件库中下载,因为各系统的包管理工具不尽相同,这里仅提供Debian系的apt安装方法:

sudo apt-get update
sudo apt install gpg

3. GPG常用基础操作

Gpg4win的图形化界面减小了很多操作难度,故本文将重点介绍GPG的命令行操作。

Oct希望你注意的事情-其一: 本文强烈推荐使用主密钥+子密钥的形式使用GPG密钥,接下来的介绍也会以该方案为例。在该方案中,主密钥应当被保存在一个绝对安全的脱机存储介质中,除非需要修改子密钥或认证其它密钥,否则不联机。日常使用中仅使用子密钥进行加密、签名和身份认证。
这样做的好处是——因为日常使用的是子密钥,当某个子密钥不慎泄露时(有被其它人冒用的风险),仅需将对应的子密钥吊销即可,不会影响到其它密钥的正常使用。关于这一点,我们将在后面聊到。

3.1. 生成GPG密钥

如果你还没有GPG密钥,此时你的选择只有从零生成一个新的密钥。Let‘s do it~

要生成密钥,请在命令行中执行以下命令

gpg --full-generate-key

回车以后,会跳出一大段文字:

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

  请选择您要使用的密钥种类:  
   (1) RSA and RSA (default)  
   (2) DSA and Elgamal  
   (3) DSA (仅用于签名)   
   (4) RSA (仅用于签名)  
   (14) Existing key from card
  您的选择?

一般来说,主密钥会选择第一种,即RSA and RSA类型。我们输入1,然后回车确定。这时,终端会询问你密钥的长度:

RSA 密钥长度应在 1024 位与 4096 位之间。  
  您想要用多大的密钥尺寸?(3072)

自然的,密钥长度越长,其安全性越高,主密钥一般设置为4096位,我们输入4096,然后回车确定。之后,终端会询问你密钥的有效期:

请设定这把密钥的有效期限。  
   0 = 密钥永不过期  
   <n> = 密钥在 n 天后过期  
   <n>w = 密钥在 n 周后过期  
   <n>m = 密钥在 n 月后过期  
   <n>y = 密钥在 n 年后过期  
  密钥的有效期限是?(0)

一般来说,主密钥是能够确保一定不会泄露的绝对安全的密钥,可以不为其设置有效期(即有效期为无限长),这里我们不为主密钥设置有效期,直接回车(默认为不设置有效期)。GPG会提醒你:

密钥将永不过期
这是正确的吗 (y/N)

输入y后回车确认该提醒。
接下来,GPG会让你输入你的一些个人信息,用于标识该密钥:

GnuPG需要一个用户标识来辨识您的密钥:

  真实姓名:
  电子邮件地址:
  注释:

一般来说,真实姓名并不是必须的,可以使用网络ID代替,但如果你有特殊需求(如一些开源社区鉴别GPG密钥时需要核对ID和本人护照/身份证上的姓名是否一致)也可以使用真实姓名。电子邮件地址填入你的邮件地址。最后的注释可以留空。
填写完后,GPG会让你再确认一次:

你选择了这个 USER-ID:
 "[ 真实姓名 ] ([注释]) <[电子邮件地址]>"

更改姓名(N)、注释(C)、电子邮件地址(E)或确定(O)/退出(Q)?

如果没有问题的话,你就可以输入O确认了。这时GPG会让你设定你的GPG密钥的保护密码:

您需要一个密码来保护您的私钥:

这是为了防止其它人盗用你的私钥或者误操作而作的保护措施。请输入一个安全的密码并回车确认。
这时,屏幕会提示你:

我们需要生成大量的随机字节。这个时候您可以多做些琐事(像是敲打键盘、移动鼠标、读写硬盘之类的),这会让随机数字发生器有更好的机会获得足够的熵。

此时按照提示做一些琐事,几分钟后,密钥就生成完毕了:

gpg: 密钥 [Key_Fingerprint] 被标记为绝对信任  #注:[Key_Fingerprint]为你的GPG密钥的“指纹”
  公钥和私钥已经生成并经签名。

至此,你就生成了你的第一个GPG密钥!恭喜!(おめでとう.jpg)

3.2. 导入GPG密钥

如果你之前已经生成过GPG密钥,现在想将密钥导入到本地,可以遵循以下流程:

3.2.1. 导入密钥

要导入密钥,请在命令行中执行以下命令

gpg --import [sub_key_file_path] # sub_key_file_path为要导入的子密钥的文件路径

注:此处可能会要求你输入主密钥的保护密码

导入完成后验证是否成功导入

gpg --list-keys

终端应该会返回以下内容:

/home/[user_name]/.gnupg/pubring.kbx
----------------------------------
pub   [main_key_type] [gen_date] [SC]
      [Key_Fingerprint]
uid             [ 未知 ] [UserName] ([KeyName]) <[e-mail]>
sub   [sub_key_type] [gen_date] [E] [有效至:[exp_date]]
sub   [sub_key_type] [gen_date] [S] [有效至:[exp_date]]

3.2.2. 更改密钥的信任级

刚导入的GPG密钥还处于未知信任级,下面要提升其信任级别。在命令行中执行以下命令

gpg --edit-key [Key_Fingerprint] # 此处Key_Fingerprint为之前导入密钥的密钥指纹

终端应该会返回以下内容:

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

私密子密钥可用。

pub  [main_key_type]/[main_key_fingerprint]
     创建于:[gen_date]  有效至:永不       可用于:SC
     信任度:未知        有效性:未知
ssb  [sub_key_type]/[sub_key_fingerprint]
     创建于:[gen_date]  有效至:[exp_date]  可用于:E
ssb  [sub_key_type]/[sub_key_fingerprint]
     创建于:[gen_date]  有效至:[exp_date]  可用于:S
[ 未知 ] (1). [UserName] ([KeyName]) <[e-mail]>

gpg>

此时输入 trust 来更改信任级,终端会提示以下信息:

请决定您对这名用户能否正确地验证其他用户密钥
(通过查看护照,检查不同来源的的指纹等等)的相信程度

  1 = 我不知道或不作答
  2 = 我不相信
  3 = 我勉强相信
  4 = 我完全相信
  5 = 我绝对相信
  m = 回到主菜单

您的决定是什么?

如果你刚刚导入的是自己之前生成的子密钥,则可以选择 5 = 绝对相信。这时GPG会向你再次确认指令,确认后你的密钥就是绝对信任级了,像这样:

pub  [main_key_type]/[main_key_fingerprint]
     创建于:[gen_date]  有效至:永不       可用于:SC
     信任度:绝对        有效性:绝对
ssb  [sub_key_type]/[sub_key_fingerprint]
     创建于:[gen_date]  有效至:[exp_date]  可用于:E
ssb  [sub_key_type]/[sub_key_fingerprint]
     创建于:[gen_date]  有效至:[exp_date]  可用于:S
[ 绝对 ] (1). [UserName] ([KeyName]) <[e-mail]>

⚠️ 注意:绝对相信信任级应该且只应该应用于自己的密钥!!! ⚠️

3.3 生成子密钥

要创建一个子密钥,首先你要拥有一个主密钥,且将主密钥的私钥需导入到本地2

要进行子密钥生成,执行命令gpg --quick-add-key FINGERPRINT [ALGO [USAGE [EXPIRE]]]。其中,FINGERPRINT为你的主密钥的密钥指纹,ALGO为你生成的子密钥的类型,可选的如rsa2048rsa4096等,USAGE为你生成的子密钥的用途,可选的有encr(加密)、sign(签名)、auth(认证),EXPIRE为子密钥的有效时长(从创建时刻开始计算)。
例如,我们想要为密钥指纹为705A5236的主密钥生成一个使用2048位RSA签名子密钥,并设置其有效期为一年,只需要输入gpg --quick-add-key 705A5236 rsa2048 sign 1y回车执行就可以了。

3.4. 修改密钥的过期时间

要修改密钥的过期时间,首先你需要拥有主密钥,且主密钥需保存在本地2

要修改密钥的过期时间,你可以执行命令gpg --quick-set-expire FINGERPRINT EXPIRE [SUBKEY-FPRS]。其中,FINGERPRINT为你的主密钥的密钥指纹,EXPIRE为要设置的有效时长(从当前时间开始计算),[SUBKEY-FPRS]可选的子密钥的密钥指纹(如果没有,则会更改主密钥的过期时间;如果设为'*'则会更改所有未过期的子密钥的过期时间)
例如,我们想要为指纹为705A5236的主密钥的所有未过期子密钥从今天起延长一年有效期,可以执行命令gpg --quick-set-expire 705A5236 1y '*'

4. 公钥服务器

公钥服务器(GPG KeyServer)是互联网上一些公开的提供GPG公钥存储服务的服务器。用户可将自己的公钥上传到这些服务器,使其他人能更容易找到密钥。大多数密钥服务器彼此进行通信,因此你的密钥信息最终将与所有其他密钥信息同步。

由于大多数公钥服务器没有身份检查机制(有例外,如 keys.openpgp.org 有对E-mail所有权的验证机制),任何人都可以用你的名义上传公钥,所以并没有办法保证服务器上的公钥的可靠性。为应对这个问题,你可以将自己主密钥的密钥指纹公布在自己的个人网站上,这样其它人便可以通过该指纹核对自己获取的“你的公钥”是否遭到了篡改。

Oct希望你注意的事情-其二: 关于密钥指纹,以往大多使用最后8位/16位等短指纹来进行辨认。但后来的研究发现使用短指纹存在 哈希碰撞3 的可能性,因此现在更推荐使用全指纹进行辨认。(不过,这不意味着你在本地执行各类指令时需要输入40位全指纹来指定一个密钥。大多数情形下的8位/16位指纹仍然是足够的。)

Oct希望你注意的事情-其三: 将密钥发布到公钥服务器,意味着你的用户ID、注释信息、E-mail地址都将被公开给全世界,并且无法被删除。因为公钥服务器在最初设计时为了避免外力篡改服务器内的数据而设计为只增不减,所有被上传到服务器的密钥信息均只能被标记为吊销,而无法被彻底删除4。如果你的用户ID是你的真名(身份证/护照/户口本上的名字),或者你的E-mail地址是私密地址,或者在你的注释中存在任何敏感信息,请在发布之前一定要慎重、慎重再慎重!

4.1. 生成吊销证书

在将密钥上传到KeyServer之前,最好先生成一份密钥的吊销证书。这是因为,一旦你的密钥上传到了公钥服务器上,它就不能再被删除了。此时,若你不小心弄丢了主密钥,唯一能吊销服务器上的密钥的办法,就只有上传吊销证书这一条路可走了。从这个意义上来说,吊销证书是你粗心大意的最后保险丝

吊销证书是一次生成,始终有效的。要生成吊销证书,首先你需要拥有主密钥,且主密钥需保存在本地2。(我这是第几次说这句话了?)

然后,执行命令:gpg [options] --gen-revoke user-id。其中,user-id可以用密钥指纹代替。options部分可以指定吊销证书生成到哪里。比如,我们要为密钥指纹为705A5236的主密钥生成一份吊销证书到用户目录下,命令为gpg -o ~\revoke-cert --gen-revoke 705A5236

4.2. 将你的密钥发布到公钥服务器

再次强调:如果你的密钥中存在任何不想被公开的敏感信息,请不要上传到服务器!

上传密钥(实际上是上传公钥)很简单,只需要一条命令:gpg --send-keys FINGERPRINT...FINGERPRINT即为你要上传的密钥的密钥指纹,可以有多条,每条之间用空格分隔。

4.3. 吊销密钥

⚠️危险操作⚠️ 你最好知道自己在做什么!密钥的吊销是不可逆的!密钥一旦被吊销将无法恢复!

吊销密钥的操作一般只对丢失/泄露了主密钥的密钥使用。要吊销密钥,只需要导入吊销证书,然后上传至服务器即可。

  • 导入吊销证书:gpg --import [<input-file>]
  • 上传至服务器:gpg --send-keys FINGERPRINT
    • 如果你之前没有将密钥上传到服务器,那么可以将导入吊销证书后的密钥导出公钥,发给你的朋友让他们导入。

4.4. 和其它人交换GPG公钥并进行签名

作为一名Geek(?),我们交到新朋友时,交换的不是手机号/邮箱/QQ/微信,而是GPG公钥!(huh?.jpg)

要与其它人交换公钥并签名,需要三个步骤:

4.4.1. 导入其它人的公钥

这个简单,参考前面3.2.1.节导入密钥部分即可。

4.4.2. 进入密钥编辑模式,对公钥签名

注意: 签名密钥,意味着你将为该密钥的身份真实性提供保证(俗称“背书”),请慎重,不要随意为来历不明的公钥签名。

要进入密钥编辑模式,请执行命令:gpg --edit-key FRINGERPRINT
比如,要编辑密钥指纹为CB980B52的密钥,可以输入gpg --edit-key CB980B52后回车执行,之后GPG会显示关于该密钥的信息:

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


pub  [main_key_type]/[main_key_fingerprint]
        创建于:[gen_date]  有效至:永不       可用于:SC
        信任度:未知        有效性:未知
ssb  [sub_key_type]/[sub_key_fingerprint]
        创建于:[gen_date]  有效至:[exp_date]  可用于:E
ssb  [sub_key_type]/[sub_key_fingerprint]
        创建于:[gen_date]  有效至:[exp_date]  可用于:S
[ 未知 ] (1). [UserName] ([KeyName]) <[e-mail]>
   
gpg>

要签名该密钥,需执行指令sign,之后GPG会提醒你再次确认:

pub  [main_key_type]/[main_key_fingerprint]
     创建于:[gen_date]  有效至:永不       可用于:SC
     信任度:未知        有效性:未知
 主密钥指纹: [main_key_fingerprint_full]

     [UserName] ([KeyName]) <[e-mail]>

你确定要签名该密钥,使用你的密钥"[Your UID]"(Your key fingerprint)?

确认签名? (y/N)

输入y确认,这时可能需要你输入你主密钥的保护密码。输入后回车确认即可完成签名。

默认情况下,签名会对密钥中所有的uid签名。如果你只想给某个uid签名,可以在sign之前使用uid uid_num选择某个uid(uid_num为uid前的数字编号,输入0可以取消选择)。

签名完成后,可以直接quit退出编辑模式

4.4.3. 将签名后的公钥上传至服务器(可选)

经密钥所有者认可后,你就可以将经过你签名的公钥上传至公钥服务器了。具体的上传操作请参照4.2节中的内容。

5. 将GPG认证子密钥用于SSH登录

5.1. 为什么要这样做

使用密钥认证登录远程服务器可以大大提高远程服务器的安全性。使用SSH登录远程服务器时,弱密码容易被爆破,但强密码又不容易记忆。公钥认证解决了这个问题,它使用GPG认证子密钥作为强密码。只要持有认证子密钥,即可登录对应的服务器。

5.2. 如何进行配置

5.2.1. 在本地进行配置

要配置本地SSH支持GPG密钥登录,需要对GPG-Agent进行配置

如其名称所示,GPG-Agent是一款为GnuPG提供代理的软件,它是GPG中的一个组件,用于管理与GPG相关的密钥和密码。可以理解为:为其它软件调用GPG服务提供了接口(API)。

  • 首先我们需要配置GPG-Agent以使其支持SSH:
    编辑文件~\.gnupg\gpg-agent.conf(没有就创建一个新的),将enable-ssh-support附加至文件尾新行。

  • 然后我们需要配置环境变量SSH_AUTH_SOCK,使用GPG-Agent代替SSH-Agent:
    这种设置环境变量的操作一般放在命令行启动脚本中执行(应该没人想每次启动命令行都手动输入吧),以我使用的zsh为例,需要将以下内容添加到~\.zshrc

    unset SSH_AGENT_PID
    if [ "${gnupg_SSH_AUTH_SOCK_by:-0}" -ne $$ ]; then
    export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
    fi
    export GPG_TTY=$(tty)
    gpg-connect-agent updatestartuptty /bye >/dev/null
    
  • 接下来我们需要设置用于SSH认证的密钥:

    • 在设置密钥之前我们要先知道用于认证的密钥的Keygrid,输入命令gpg --list-keys --with-keygrip <FINGERPRINT>(FINGERPRINT处输入你的主密钥指纹)并执行,即可获取你所有密钥的Keygrid,如下所示:
      pub   [main_key_type] [main_key_gen_time] [SC]
          [main_key_fingerprint]
          Keygrip = <MainKeygrid>
      uid           [UserName] ([KeyName]) <[e-mail]>
      sub   [sub_key_type] [sub_key_gen_time] [A] [expires: expire_time]
          Keygrip = <SubAuthKeygrid>
      ...
      
      注意第五行的[A],这意味着这个子密钥是可以用来Auth(即身份认证)的。我们就使用这个子密钥作为SSH认证密钥,SubAuthKeygrid就是我们需要的Keygrid。
    • 将刚才获得的认证子密钥的Keygrid添加到~/.gnupg/sshcontrol文件尾新行(同样的,没有就新建一个)
    • 重启你的终端,使之前的更改生效
  • 最后我们需要确认是否已设置完成:
    输入ssh-add -L并执行,应该会出现以下内容:

    ssh-rsa <一长串字符> (none)
    

    再输入gpg --export-ssh-key <FINGERPRINT>(FINGERPRINT处输入你的主密钥指纹)并执行,此时应该会出现以下内容:

    ssh-rsa <一长串字符> openpgp:0x<8位16进制数字>
    

    比较两次的一长串字符是否一致,如果是,则SSH-Key是否已成功添加(替换)。

至此,本地主机的配置就算完成了。

5.2.2. 在远程主机上进行配置

要在远程主机上实现SSH认证登录,需要在远程主机上对SSH进行配置。相较于在本地主机上的配置来说,远程主机的配置比较简单,只需要将公钥上传即可。

有两种上传方式:自动上传和手动上传。

  • 首先介绍手动上传的方法:
    OpenSSH 规定,用户公钥保存在服务器的~/.ssh/authorized_keys文件。你要以哪个用户的身份登录到服务器,密钥就必须保存在该用户主目录的~/.ssh/authorized_keys文件。
    因此,只要把公钥添加到这个文件中,就相当于公钥上传到服务器了:复制好公钥(输入ssh-add -L执行后获得的所有输出)后,登录目标远程服务器,然后将公钥粘贴入~/.ssh/authorized_keys(还是那句话,没有就新建一个)的文件尾新行。
  • 然后是自动上传:
    OpenSSH 自带一个ssh-copy-id命令,可以自动将本地的公钥文件拷贝到远程服务器的~/.ssh/authorized_keys文件。如果~/.ssh/authorized_keys文件不存在,ssh-copy-id命令会自动创建该文件。
    因此只需要ssh-copy-id -i <path> <user>@<host>(path处为公钥文件路径,user处为远程主机的用户名,host处为远程主机的地址)即可实现自动上传公钥至远程主机。
    但由于ssh-copy-id命令,拷贝的使公钥文件,所以这要求我们先将认证公钥导出到文件中:执行命令gpg --output <path> --export-ssh-key <FINGERPRINT>(path处输入目标文件路径,FINGERPRINT处输入你的主密钥指纹)即可实现将SSH公钥导出到指定文件中。

🚧🚧🚧前方正在施工,请注意安全🚧🚧🚧

5.2.3. 附:如何在Github中使用SSH密钥

6. 在Git中使用GPG密钥对Commit进行签名

6.1. 为什么要这样做

6.2. 如何进行配置

6.2.1. 在本地进行配置

6.2.2. 在Github上进行配置


  1. GnuPG - 维基百科,自由的百科全书 (wikipedia.org) ↩︎

  2. 要查看主密钥的私钥是否在本地,可输入gpg --list-secret-keys。看输出信息第三行是否为:sec# [密钥类型] [生效时间] [SC],如果有#则说明私钥不在本地,如果没有则说明私钥在本地。

    ↩︎ ↩︎ ↩︎
  3. 哈希算法 - 廖雪峰的官方网站 (liaoxuefeng.com) ↩︎

  4. 除非你能接触到实体服务器,并把记录有你的密钥的介质取出销毁,这显然不太现实 ↩︎