在这里记录一下SSH和GPG的简单介绍和基础配置,主要针对的应用是git,不过并不局限于git,其他不少应用也是一样的。

基础介绍

SSH

SSH(Secure Shell) 是一个基于TCP的加密网络协议,相较于普通的TCP协议,可以保证通信的安全性,比较常用的两个应用是登录远程服务器和推送本地git仓库到远程。SSH可以使用密码或者密钥登录,但是由于密码被破解的风险是不确定的,如果设置了像123456这种弱口令,那么SSH的安全性就形同虚设了,所以通常推荐设置SSH密钥来代替密码,比如Debian系统默认情况下是不允许root用户SSH登录用密码的;还有GitHub也已经禁止了使用密码来推送代码。另外,SSH密钥本身也可以再加一层密码。

GPG

首先简单解释一下几个容易混淆的名词,PGP OpenPGP GPG:

  1. PGP(Pretty Good Privacy)是一个闭源软件的名字
  2. OpenPGP是一个标准,这个标准是基于PGP这个软件的一些格式签名等衍生而来的
  3. GPG(GNU Privacy Guard)是一个实现了OpenGPG标准的开源软件,大多数人用到的就是它

GPG有四个主要功能,加密(encryption)、签名(signing)、证书(certification)、认证(authentication),比较常用的是加密和签名。本文用到的是签名这个功能,以给git提交记录签名为例,由于git的用户名和邮箱是用户可以随便定义的,那么在开源项目中,就有可能会出现冒充他人提交代码的恶意行为,通过gpg为git commit签名就可以解决这个问题,这种签名的记录在GitHub上会显示一个绿色的Verfied的tag。

SSH 配置

生成 SSH 密钥对

这里用到的命令是ssh-keygen,如果你的系统没有这个命令,需要安装openssh,可以在command-not-found 来查看自己系统安装对应的软件包安装命令。我目前使用的版本是9.6p1-1,是当下的最新版本。

打开终端,在命令行输入ssh-keygen -t ed25519,其中ed25519是生成密钥对的类型,除了ed25519还有dsa ecdsa ecdsa-sk ed25519-sk rsa,具体可以通过man ssh-keygen查看,上述命令输出如下:

1
2
Generating public/private ed25519 key pair.
Enter file in which to save the key (/root/.ssh/id_ed25519):

如果是第一次生成,那么直接回车即可,接着会出现以下内容:

1
Enter passphrase (empty for no passphrase):

这里的密码可以为空,如果不为空,那么在使用这个密钥的时候会提示验证密码。

一个完整的输出如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
➜  ~ ssh-keygen -t ed25519
Generating public/private ed25519 key pair.
Enter file in which to save the key (/root/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_ed25519
Your public key has been saved in /root/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:9T+8qnJrytq3BntQ/Ad9ufyGimAuYNUqux3SceXkP3Q root@VM-16-7-debian
The key's randomart image is:
+--[ED25519 256]--+
|                 |
|                 |
|       . .+  .  .|
|      . .*o.. ...|
|     ...S.o.o.E..|
|    +..oo  o.+.o |
|   ..+o o+  o.+..|
|    .o.*o.*. ..oo|
|    ..oo=O=+oo.. |
+----[SHA256]-----+

在这次操作中,我们得到了两个文件,id_ed25519.pub是公钥,id_ed25519是私钥,其中公钥稍后会上传到 GitHub 上,私钥则保存在本地,注意不能泄露。

这一对密钥也可以用于SSH登录远程服务器,通常情况下,没有必要生成很多对密钥。我个人的习惯是一台设备一对密钥,增加设备就新生成密钥,更换设备就继承旧设备的密钥

添加 SSH 公钥至 GitHub

登录 GitHub 之后访问 Add new SSH keys 页面,填写表单即可完成添加,公钥的内容类似下面的代码:

1
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH6/vsvmVi2MHq7duEZJ+Fv6FAZGG6dTZkPQWXKcRwId root@VM-16-7-debian

接下来还需要在本地配置ssh的config配置文件,对域名和密钥进行关联,补充下述内容到~/.ssh/config文件中,其中#后的为注释,可删除:

1
2
3
4
5
6
Host github.com # 可以为自定义的名称,不过对于git,建议为远程仓库的域名
  Hostname github.com # 远程仓库/服务器的域名
  User eyebrowkang # 自己的github用户名
  Port 22 # 端口,默认即为22,可省略
  PreferredAuthentications publickey # 偏好的认证方式,可省略
  IdentityFile ~/.ssh/id_ed25519 # 私钥文件的路径,建议用绝对路径

在命令行输入ssh -T [email protected]来验证是否添加成功,首次输入会得到这样的一个输出:

1
2
3
4
5
➜  ~ ssh -T [email protected]
The authenticity of host 'github.com (20.205.243.160)' can't be established.
ED25519 key fingerprint is SHA256:+DiF5lqwHfHwaSabpZisF/zLDA0zPMSvHdkr4UvCOqU.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

输入yes并回车,如果添加成功,会出现类似的结果:

1
2
Warning: Permanently added 'github.com' (ED25519) to the list of known hosts.
Hi eyebrowkang! You've successfully authenticated, but GitHub does not provide shell access.

SSH 密钥的备份

SSH 公钥刚才已经上传到了github,但是密钥只在本地保存,建议对两个文件进行备份。如果保存到U盘、移动硬盘等可以直接复制过去;如果要上传要云盘,建议对文件进行加密。

GPG 配置

生成 GPG 密钥对

如果没有gpg命令,需要先安装,方法和上面的ssh一样,不再赘述。

打开终端,输入gpg --full-generate-key命令,输出如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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.

gpg: directory '/root/.gnupg' created
gpg: keybox '/root/.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?

建议保持默认,直接回车,接着需要选择密钥长度:

1
2
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072)

这里我直接输入4096,然后需要设置过期时间:

1
2
3
4
5
6
7
8
Requested keysize is 4096 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)

过期时间后续是可以改的,如果不想过一阵子就重新生成一次,那么可以直接输入 0,永不过期。

接着需要填写真实名字,邮箱地址,和评论三个字段,分别来说明下:

  1. 真实名字(Real name):我个人不太倾向于填写真名,可以用自己的常用网名代替,或者github用户名,我这两个是一样的
  2. 邮箱地址(Email address): 可以选择公布或不公布自己的邮箱,公布邮箱的好处是这个GPG Key可以用于很多个场合,比如GitHub、博客、邮件等等,坏处就是暴露了自己的邮箱;而不公布的好处就是保持匿名,坏处则是这一对密钥只能用于特定的服务,比如GitHub有自己的noreply 邮箱,GitLab也有一个,同时使用这两个服务的话就比较麻烦。我这里用的是 GitHub的 noreply 邮箱 。
  3. 评论(Comment): 这个就随意了,我直接留空
1
2
3
4
5
GnuPG needs to construct a user ID to identify your key.

Real name: eyebrowkang
Email address: 48169104+eyebrowkang@users.noreply.github.com
Comment:

接着,和SSH类似,为密钥对设置密码,这个密码会在使用密钥对的时候验证,然后一路确认,最终得到的输出如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
gpg: /root/.gnupg/trustdb.gpg: trustdb created
gpg: key DCA603FF70CDCDD0 marked as ultimately trusted
gpg: directory '/root/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/root/.gnupg/openpgp-revocs.d/B1D32AD243FEFF42CCEBF9A9DCA603FF70CDCDD0.rev'
public and secret key created and signed.

pub   rsa4096 2023-06-03 [SC]
      B1D32AD243FEFF42CCEBF9A9DCA603FF70CDCDD0
uid                      eyebrowkang <[email protected]>
sub   rsa4096 2023-06-03 [E]

这个输出的内容不包含key的ID等,这里我对前4行解释一下:

  1. 我这台机器没有任何的gpg key,所以第一次会创建这么一个trustdb,用来存放本机信任的gpg key
  2. ultimate是最高的信任级别,一共有五个等级,ultimate是5,none是1,因为这是当前机器生成的,所以级别默认是最高,如果是从其他地方导入的key,那么默认的信任等级会是1,这个信任级别是可以修改的,下文会提到
  3. 创建了一个openpgp-revocs.d的目录,这个目录用于存放撤销证书,这个证书一定要保存好,因为它可以用来撤销整个GPG Key
  4. 撤销证书保存到了上面的目录

通过命令gpg --list-keys --keyid-format LONG可以获取本机的GPG Key的详细信息:

1
2
3
4
5
6
/root/.gnupg/pubring.kbx
------------------------
pub   rsa4096/DCA603FF70CDCDD0 2023-06-03 [SC]
      B1D32AD243FEFF42CCEBF9A9DCA603FF70CDCDD0
uid                 [ultimate] eyebrowkang <[email protected]>
sub   rsa4096/57D615B2EC5D45C1 2023-06-03 [E]

这里末四行的信息比上面的更全一些,我逐行解释一下:

  1. pub是public公钥,如果命令是list-secret-keys那么会显示sec,实际上后面的内容是一样的,代表这行是主密钥的信息,GPG是有主密钥和子密钥的,一个主密钥可以有多个子密钥,接着rsa4096就是前面选过的算法,DCA603FF70CDCDD0则是key ID,末尾的[SC]代表这个主密钥有两个功能signing和certification
  2. 这个是主密钥的指纹,或者说是这个GPG Key的指纹
  3. uid就是user ID,[ultimate]是前面提到过的信任等级,后面的信息对应真实姓名、邮箱和评论
  4. sub是subkey,57D615B2EC5D45C1是子密钥的key ID,末尾的[E]代表这个子密钥的功能是encryption。注意,这个子密钥不能用于git签名,因为它没有S的标志

添加 GPG 公钥至 GitHub

首先要得到GPG Key的公钥,输入gpg --armor --export DCA603FF70CDCDD0命令,整个key的公钥部分,如下所示(有截断):

1
2
3
4
5
6
7
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQINBGR6/6IBEADatsJbClB2bzL/egH3MBnFHriC1Pht2oextY5ccRnt2ILoQ03w
... ... ...
EpY+XBZcPygWCl4gU2BS/PuDUYidFDo=
=N2E3
-----END PGP PUBLIC KEY BLOCK-----

将这部分内容完整的复制下来,然后访问GPG Key 的添加页面,填写表单即可。

设置 git 提交签名

通过运行命令git config --global user.signingkey DCA603FF70CDCDD0命令,可以为 git 设置全局的签名密钥,去掉--global就仅会为当前目录下的 git 仓库设置。然后输入git config --global commit.gpgsign true,设置所有的 git 提交都需要签名。

然后一般还需要在.bashrc或者其他 shell 配置文件中添加如下环境变量:

1
export GPG_TTY=$(tty)

接着运行killall gpg-agent杀掉 gpg 进程,然后可以随便提交一次,可以通过git log --show-signature命令查看到Good signature的字样就是可以了,最后 push 到 GitHub 就可以看到绿色的认证了。

我的最佳实践

实际上,上述的操作是最为简单的一种操作方式,直接使用主密钥进行签名。但是我个人不太推荐,因为如果碰到泄露什么的就要撤销整个密钥才行,而且由于我有多台电脑,每台电脑都是一样的签名会让我分不清楚,所以我会为每一台设备生成一个用于签名的子密钥,由于子密钥是可以单独导出和导入的,这样的话,我家里台式机存放着所有信息,我外出携带的笔记本可以只放一个subkey,假设笔记本丢失了,撤销掉对应的subkey就可以了。

输入gpg --edit-key DCA603FF70CDCDD0命令来生成一个subkey,该命令的输出如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
➜  ~ gpg --edit-key DCA603FF70CDCDD0
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.

Secret key is available.

sec  rsa4096/DCA603FF70CDCDD0
     created: 2023-06-03  expires: never       usage: SC
     trust: ultimate      validity: ultimate
ssb  rsa4096/57D615B2EC5D45C1
     created: 2023-06-03  expires: never       usage: E
[ultimate] (1). eyebrowkang <[email protected]>

gpg>

输入 help 查看帮助,前面提到的改过期时间的命令是expire,改信任等级的命令是trust,退出是quit,我们输入增加子密钥对的命令addkey,和生成主密钥类似:

1
2
3
4
5
6
7
8
gpg> addkey
Please select what kind of key you want:
   (3) DSA (sign only)
   (4) RSA (sign only)
   (5) Elgamal (encrypt only)
   (6) RSA (encrypt only)
  (14) Existing key from card
Your selection?

因为要用于签名,所以只能选3或4,确认之后最终输出如下:

1
2
3
4
5
6
7
8
sec  rsa4096/DCA603FF70CDCDD0
     created: 2023-06-03  expires: never       usage: SC
     trust: ultimate      validity: ultimate
ssb  rsa4096/57D615B2EC5D45C1
     created: 2023-06-03  expires: never       usage: E
ssb  rsa4096/6D27CF5A5037DE52
     created: 2023-06-03  expires: never       usage: S
[ultimate] (1). eyebrowkang <[email protected]>

6D27CF5A5037DE52就是新生成的子密钥ID,然后还要输入save保存并退出。运行git config --global user.signingkey 6D27CF5A5037DE52,可以把子密钥作为全局的签名密钥。

然后还要用同样的方法再生成一个,给我的笔记本用,那么就涉及到导出和导入了,这部分内容合并到备份一起讲。

GPG 密钥的备份

无论是否和我一样有多设备的需求,生成了 subkey 用于专门的用途,都建议进行备份。

首先说需要备份那些内容:

  1. 撤销证书,也就是前面存放到 openpgp-revocs.d 文件夹的那个文件,这个证书无论生成多少个 subkey 都不会改变,而且一旦使用是不可逆的,因此这个文件建议多备份到几个地方,同时本机的建议删除,避免不小心撤销掉。
  2. GPG Key 的公钥和私钥,这个就和 SSH 类似

撤销证书直接复制对应文件就可以了,公钥和私钥需要手动导出,输入gpg --armor --export DCA603FF70CDCDD0 --output public.asc命令导出公钥,输入gpg --armor --export-secret-keys DCA603FF70CDCDD0 --output private.key命令导出私钥。后缀是无所谓的,asc就是ascii的意思,改成pub也可以。

前面提到的仅导出subkey的命令是gpg --armor --export-secret-keys 6D27CF5A5037DE52! --output laptop-private.key,注意结尾的!符号,如果不加的话,导出的还是整个的密钥。另外,公钥其实无所谓主密钥子密钥的,单独导出一个子密钥是没有意义的,一般公钥只有一份就可以,并且这个公钥可以上传到公钥服务器,这里不展开了,可以自行查询。

然后,复制到新设备之后,导入的命令如下

1
2
gpg --import public.asc
gpg --import private.key

其他配置

如果是 MacOS,并且想要让设备记住 gpg 密码,那么需要安装 pinentry-mac 这个包,然后运行 echo "pinentry-program $(which pinentry-mac)" >> ~/.gnupg/gpg-agent.conf 命令,并 killall gpg-agent

Command Cheat Sheet

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# ssh
ssh-keygen -t ed25519 # 生成SSH密钥对
ssh -T git@github.com # 验证GitHub是否配置成功

gpg --full-generate-key # 生成GPG密钥对
gpg --list-keys --keyid-format LONG # 查看GPG密钥详细信息
gpg --edit-key <key-id> # 编辑对应密钥
gpg --armor --export <key-id> --output public.asc # 导出公钥
gpg --armor --export-secret-keys <key-id> --output private.key # 导出私钥
gpg --import <filename> # 导入公钥/私钥

总结

以上是基础的一些操作,无论是SSH还是GPG,都还有很多用法或玩法,比如SSH在服务器上的配置,GPG撤销子密钥等等,后续有时间再写好了。