在知乎上看到了一篇文章,《GPG 与端到端加密:论什么才是可以信任的 - 知乎》,对非对称加密和软件gpg做了一些介绍,于是决定尝试学习一下gpg软件的使用。

gpg的简要介绍

gpg,GNUPG,是一款根据GNU协议开源的,openPGP标准的实现,其使用对称和/或非对称算法提供了加密,数字签名,身份验证和解密的功能。

使用非对称加密技术可以加密发送文件给某人而避免被第三方打开,同时通过数字签名技术能够检查文件是否遭到第三方篡改。

安装

很多linux操作系统都预装了gpg(命令行)软件,如果没有安装的话可以安装 gnupg 软件包。

或者,也可以参考这篇GPG有关安装的介绍,前往 https://www.gnupg.org/download/来获取和安装gnupg的源码或二进制包。

虽然gnupg有很多图形化后端,不过此处只介绍命令行方式

生成密钥对


一个密钥对由一个公钥和一个私钥组成,公钥可以公开发行,而不会影响密钥的保密性;私钥应当小心保管,避免丢失或泄露。

在shell下执行下面这条命令,创建一个新的密钥对

1
gpg --expert --full-generate-key

一切正常的话,将会输出一些选项来对密钥对进行配置:

选择加密算法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
gpg (GnuPG) 2.2.21; Copyright (C) 2020 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 和 RSA (默认)
   (2) DSA 和 Elgamal
   (3) DSA(仅用于签名)
   (4) RSA(仅用于签名)
   (7) DSA(自定义用途)
   (8) RSA(自定义用途)
   (9) ECC 和 ECC
  (10) ECC(仅用于签名)
  (11) ECC(自定义用途)
  (13) 现存的密钥
  (14) Existing key from card
您的选择是? 8

第一个问题是询问密钥对所使用的算法,默认是1(直接回车会默认选择1),GPG支持RSA、DSA、ECC等加密算法,其中RSA兼容性最好,而ECC被认为能够通过更短的位数提供更高的安全性。不过,目前而言,无论哪种算法在现在都可以基本上是不可能被破解的。

此处选择 (8) RSA (自定义用途)

更改密钥功能标记

1
2
3
4
5
6
7
8
9
RSA 密钥的可实现的功能: 签名(Sign) 认证(Certify) 加密(Encrypt) 身份验证(Authenticate) 
目前启用的功能: 签名(Sign) 认证(Certify) 加密(Encrypt) 

   (S) 签名功能开关
   (E) 加密功能开关
   (A) 身份验证功能开关
   (Q) 已完成

您的选择是? q

这个问题仅会在选择"自定义用途"的情况下才会显示,且不同的密钥算法所支持的功能也各不相同。比如说ECC算法就不支持加密功能。

此步骤能够允许使用者修改密钥使用范围(修改密钥使用范围标记)。

输入功能对应的字母,能够开启或者关闭相应的功能。不过一次只能输入一个字母。

功能介绍如下

  • 认证(Certify) : 认证功能允许私钥持有者使用该密钥对其他密钥签名,认证其他密钥。只允许主密钥持有此标记,且主密钥不允许移除此标记
  • 签名(Sign) : 签名功能允许私钥持有者对文件或消息签名,以允许公钥持有者验证文件完整性和文件来源
  • 加密(Encrypt) : 加密功能允许使用此密钥用于加密文件或消息。
  • 身份认证(Authenticate) : 身份认证功能允许使用此密钥用于验证身份,比如ssh登陆之类的

完成功能选择后,输入q结束此步骤

设置密钥长度

1
2
3
RSA 密钥的长度应在 1024 位与 4096 位之间。
您想要使用的密钥长度?(2048) 4096
请求的密钥长度是 4096 位

在此步骤可以设置生成的密钥长度。较短的密钥长度可以加快生成/签名/解密的速度,并减少生成结果的大小,但是也会同时缩短破解或攻击所需要的时间。

而较长的密钥长度会大幅增加破解或攻击所需要的时间,但是也会导致生成结果增大和解密等操作的处理速度下降,并且,如果设置的密钥长度过长,则亦可能出现兼容性问题。

GNUPG在官网的常见问题(en)中建议使用默认的2048位,而不建议使用4096,因为2048被认为在2030年之前足够安全,而使用更高的位数所带来的安全性不能够弥补所损失的兼容性(比如无法将密钥移动至智能卡等兼容性较差的其他软件或设备)。官网建议如果需要超过rsa2048位的安全性,则应该切换至椭圆曲线加密(ECC),而不是继续使用rsa。

不过,尽管如此,RSA的兼容性还是比ECC要好一些的……大概?虽然ECC使用更短的位数能够提供比RSA更高的安全性。

如果使用的gpg版本在编译时开启大型安全内存缓冲区(large secure memory buffer),则最大将能够支持8192位长度的RSA密钥构建,只需要在创建密钥时加上 --enable-large-rsa-–batch 这两个选项就能在创建密钥时允许最大8192位的密钥构建——当然,通过这样构建的密钥兼容性极差,甚至连常规版本的GPG都不一定支持使用。

需要注意的是, 密钥长度一旦确定,就不再允许修改和变更 ,如果需要更改密钥长度将只能重新生成一个新的密钥对。

设置密钥过期时间

1
2
3
4
5
6
7
8
9
请设定这个密钥的有效期限。
         0 = 密钥永不过期
      <n>  = 密钥在 n 天后过期
      <n>w = 密钥在 n 周后过期
      <n>m = 密钥在 n 月后过期
      <n>y = 密钥在 n 年后过期
密钥的有效期限是?(0) 12m
密钥于 xxxx年xx月xx日 星期四 xx时xx分xx秒 GMT 过期
这些内容正确吗? (y/N) 

在此步骤能够设置密钥的有效时间,过期的密钥将被忽略,和弃用。

不过,如果是私钥泄露的话,设置密钥有效期其实没有什么用——因为私钥持有者随时都可以延长密钥有效期。对于这种情况,应该使用吊销证书。

输入数字和时间单位以密钥的有效期,默认时间单位是天。此处示例为12个月(12m)。

设置时间后,输入y确认,进入下一步。直接回车则是N,重新填写证书有效期。

设置密钥的用户标识

1
2
3
4
5
6
7
8
9
GnuPG 需要构建用户标识以辨认您的密钥。

真实姓名: example
电子邮件地址: [email protected]
注释: 仅用于示例
您选定了此用户标识:
    “example (仅用于示例) <[email protected]>”

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

在此步骤,设置密钥的用户标识,用户标识由 [真实姓名](注释)<邮箱> 组成。

其中, 真实姓名 不允许使用少于5个字母,而 邮箱 必须包含"@“符号,并且”@“前后存在字符,并且不允许包含 < > ( ) [ ] 尖括号、圆括号、方括号。至于 注释 只要不包含”()“圆括号,基本上随便填,可以用于记录密钥的用途。当然,你也可以留空一部分标识,这样就会省略此部分标识。但是不允许全部为空(全部为空会直接报错退出)。

使用 --allow-freeform-uid 选项可以禁用对用户标识任何形式的格式检查。注意, 一般情况下不要使用该选项 ,使用它不能保证用户ID为标准格式,并可能导致错误。

设置完成后,可以输入 N/E/C 重新修改姓名/电子邮件/注释,输入 Q 则会直接退出密钥创建,输入 O 进入下一步。

设置密钥密码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
我们需要生成大量的随机字节。在质数生成期间做些其他操作(敲打键盘
、移动鼠标、读写硬盘之类的)将会是一个不错的主意;这会让随机数
发生器有更好的机会获得足够的熵。
gpg: 密钥 7F9199B8737F5089 被标记为绝对信任
gpg: 吊销证书已被存储为‘/home/tntwiki/.gnupg/openpgp-revocs.d/0339DCAA11EA0C1ACEB36A5C7F9199B8737F5089.rev’
公钥和私钥已经生成并被签名。

pub   rsa4096 2020-08-05 [SCEA] [有效至:2021-07-31]
      0339DCAA11EA0C1ACEB36A5C7F9199B8737F5089
uid                      example (仅用于示例) <[email protected]>

GPG生成密钥对时会要求一个密码,来为私钥对进行一个简单的加密。这样,恶意攻击者即使通过某种途径拿到私钥文件,也无法直接使用私钥文件解密被加密的文件,以及假冒身份签名文件。

GPG设置密钥密码时,会调用pinentry,如果在图形化界面终端使用,则会弹出一个输入窗口,如下图所示,要求输入密码。

密码输入完成后,如果gpg认为这是一个弱密码,则会再弹出一个窗口询问是否继续使用这一密码,如下图:

需要注意的是, 过短的密码口令无法阻止针对私钥文件的密码爆破攻击。市面上出售的的计算机一天可以尝试28亿个密码,这足以破解由10个小写字母组成的任何密码。 因此,应当尽量使用不易被猜测出来的强密码。

另外,密码应当被妥善记忆。如果密码被遗忘,将无法解密私钥文件进行签名等必须私钥才能进行的操作。

输入完成后,需要等待一段时间获取足够多的熵以生成新的密钥。在此期间可以依照建议,做些其他操作以加快熵的生成速度。

后续操作/检查以及关于密钥的注意事项

密钥生成后,可以使用 gpg -K 列出所有私钥。

在非对称加密体系中,公钥可以公开发放,但是私钥需要格外小心泄露风险。

私钥泄露也可能会导致别人发送给私钥的加密信息被第三方解密。而私钥恶意持有者也可以使用私钥对伪造文件或者恶意程序/密钥签名而损害私钥的信任

毕竟,对私钥建立信任需要漫长的时间,但是被恶意毁掉却很容易。

关于密钥生成的选项的差异

gpg在密钥创建时会提供很多选项,不过有些选项在有些设置不会提供,在此简单的列出各个选项的部分差异

  1. gpg --generate-key 命令,创建密钥对时提供的选项最少
    生成一个新的密钥对
  2. gpg --full-generate-key 命令,创建密钥时提供的功能比较全完整
    完整功能的密钥对生成
  3. gpg --expert --full-generate-key 命令,提供一些专业的功能选项。
    --expert 专家选项所提供的功能不建议使用,该选项允许用户执行某些荒谬或者愚蠢的事情,可能会导致程序出现某些奇怪的问题。
功能 简单的密钥生成 完整的密钥生成 专业的密钥生成 附注
选择加密算法 不提供,跳过 提供部分 提供完整
\_自定义密钥用途 - 不提供 提供完整
\_ECC椭圆算法密钥 - 不提供 提供完整 根据gpg官网,椭圆算法被认为比RSA更为优秀,未来创建密钥时可能会默认提供ECC,故此表于将来可能过时
\_RSA算法密钥 默认使用 提供部分 提供完整
\_DSA算法密钥 - 提供部分 提供完整
设置密钥长度 跳过 允许 允许
设置密钥过期时间 跳过 允许 允许
设置用户标识 不提供"注释” 提供完整 提供完整
设置密钥密码 允许 允许 允许

gpg密钥编辑模式

1
gpg --expert --edit-key example    

gpg提供了一个密钥编辑模式,提供了常见的密钥编辑操作。

使用 gpg --edit-key [user_id] 将够进入密钥编辑模式,允许用户对user_id所对应的密钥进行修改。(如下方示例块所显示)。关于此模式的具体功能介绍,可以访问gpg官网手册页面edit-key章节。

密钥编辑模式允许 --expert 专家选项。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
gpg (GnuPG) 2.2.21; Copyright (C) 2020 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.

私钥可用。

sec  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2021-07-31  可用于:SCEA
     信任度:绝对        有效性:绝对
[ 绝对 ] (1). example (仅用于示例) <[email protected]>

gpg> 

进入gpg密钥编辑模式后,将会自动列出该密钥所对应的全部密钥和用户标识。

并给出一个gpg命令输入界面接收命令。可以在此输入命令来编辑密钥。

  • 密钥编辑模式的帮助菜单(专家模式)
    (该模式存在部分未记录于帮助的命令)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    
    gpg> help
    quit        退出此菜单
    save        保存并退出
    help        显示此帮助
    fpr         显示密钥指纹
    grip        显示 keygrip
    list        列出密钥和用户标识
    uid         选择用户标识 N
    key         选择子密钥 N
    check       检查签名
    sign        为所选用户标识添加签名 [* 参见下面的相关命令]
    lsign       为所选用户标识添加本地签名
    tsign       为所选用户标识添加信任签名
    nrsign      为所选用户标识添加不可吊销签名
    adduid      增加一个用户标识
    addphoto    增加一个照片标识
    deluid      删除选定的用户标识
    addkey      增加一个子密钥
    addcardkey  增加一个密钥到智能卡
    keytocard   移动一个密钥到智能卡
    bkuptocard  移动一个备份密钥到智能卡上
    delkey      删除选定的子密钥
    addrevoker  增加一个吊销用密钥
    delsig      从所选用户标识上删除签名
    expire      变更密钥或所选子密钥的使用期限
    primary     标记所选的用户标识为主要
    pref        列出偏好设置(专家模式)
    showpref    列出偏好设置(详细模式)
    setpref     为所选用户标识设定偏好设置列表
    keyserver   为所选用户标识设定首选公钥服务器 URL
    notation    为所选用户标识的设定注记
    passwd      变更密码
    trust       变更信任度
    revsig      吊销所选用户标识上的签名
    revuid      吊销选定的用户标识
    revkey      吊销密钥或选定的子密钥
    enable      启用密钥
    disable     禁用密钥
    showphoto   显示选定的照片标识
    clean       压缩不可用的用户标识并从密钥上移除不可用的签名
    minimize    压缩不可用的用户标识并从密钥上移除所有签名
    * ‘sign’命令可以通过‘l’前缀(lsign)进行本地签名,‘t’前缀(tsign)进行
      信任签名,‘nr’前缀(nrsign)进行不可吊销签名,
      或者上述三种前缀的任意组合(ltsign、tnrsign 等)。
    

新建子密钥

子密钥是一种自动关联而相对独立于主密钥的密钥,其能够独立于主密钥单独使用,代替主密钥进行签名/加密操作。

并在仅子密钥私钥泄露的情况下,可以使用主密钥吊销子密钥,而减少私钥泄露对密钥的信任损害,当然,只是一般使用的话,此步骤可以跳过。默认生成的密钥够用了。

如果要创建子密钥的话,执行 gpg --expert --edit-key [密钥id] ,进入到想新建子密钥的密钥的编辑模式。

然后执行密钥编辑模式命令 addkey 就会进入子密钥生成的引导界面,其中大部分操作与gpg创建新密钥的步骤一致。

不过子密钥生成到最后,需要输入主密钥密码,才能生成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
gpg> addkey 
请选择您要使用的密钥类型:
   (3) DSA(仅用于签名)
   (4) RSA(仅用于签名)
   (5) ElGamal(仅用于加密)
   (6) RSA(仅用于加密)
   (7) DSA(自定义用途)
   (8) RSA(自定义用途)
  (10) ECC(仅用于签名)
  (11) ECC(自定义用途)
  (12) ECC(仅用于加密)
  (13) 现存的密钥
  (14) Existing key from card
您的选择是? 8

RSA 密钥的可实现的功能: 签名(Sign) 加密(Encrypt) 身份验证(Authenticate) 
目前启用的功能: 签名(Sign) 加密(Encrypt) 

   (S) 签名功能开关
   (E) 加密功能开关
   (A) 身份验证功能开关
   (Q) 已完成

您的选择是? a

RSA 密钥的可实现的功能: 签名(Sign) 加密(Encrypt) 身份验证(Authenticate) 
目前启用的功能: 签名(Sign) 加密(Encrypt) 身份验证(Authenticate) 

   (S) 签名功能开关
   (E) 加密功能开关
   (A) 身份验证功能开关
   (Q) 已完成

您的选择是? q
RSA 密钥的长度应在 1024 位与 4096 位之间。
您想要使用的密钥长度?(2048) 4096
请求的密钥长度是 4096 位
请设定这个密钥的有效期限。
         0 = 密钥永不过期
      <n>  = 密钥在 n 天后过期
      <n>w = 密钥在 n 周后过期
      <n>m = 密钥在 n 月后过期
      <n>y = 密钥在 n 年后过期
密钥的有效期限是?(0) 100
密钥于 2020年11月14日 星期六 10时14分55秒 CST 过期
这些内容正确吗? (y/N) y
真的要创建吗?(y/N) y
我们需要生成大量的随机字节。在质数生成期间做些其他操作(敲打键盘
、移动鼠标、读写硬盘之类的)将会是一个不错的主意;这会让随机数
发生器有更好的机会获得足够的熵。

sec  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2021-07-31  可用于:SCEA
     信任度:绝对        有效性:绝对
ssb  rsa4096/0EA18957BCB383D0
     创建于:2020-08-06  有效至:2020-11-14  可用于:SEA 
[ 绝对 ] (1). example (仅用于示例) <[email protected]>

创建完成后可以在编辑模式执行 list 查看已有密钥。其中,子密钥会以 ssb 作为标记。

1
2
3
4
5
6
7
8
9
gpg> list
sec  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2021-07-31  可用于:SCEA
     信任度:绝对        有效性:绝对
ssb  rsa4096/0EA18957BCB383D0
     创建于:2020-08-06  有效至:2020-11-14  可用于:SEA 
[ 绝对 ] (1). example (仅用于示例) <[email protected]>

gpg> 

不过,虽然看起来已经创建完成了,不过现在密钥只是临时存储,还需要执行 save 命令将刚刚创建的子密钥保存至密钥环。

1
gpg> save

保存后将会自动退出编辑模式。现在,可以使用子密钥进行签名等其他操作了。

关于gpg子密钥的具体用途,可以参阅下文。

并且也可以参考一下关于GnuPG的subkey(子密钥)的使用,以及debian对子密钥的介绍(英文)这两篇关于子密钥的介绍文章。

导入/导出密钥

在很多时候需要使用文件型的公钥或者私钥,比如说向其他人分发公钥,或者备份/迁移私钥到其他设备,以及导入其他人的公钥至密钥环。这种时候就需要使用导入/导出密钥功能了。

不过在说明导入/导出的相关选项之前,先介绍一下GPG常用的指定用户ID的方法

gpg指定用户id(user_id)


gnupg常用以下几种指定用户/密钥的方式,关于全部方式,可以前往相关的gpg官方文档(en)

密钥ID指定

gpg可以通过密钥ID指定密钥。密钥ID是从密钥指纹尾部截取一段得到的。

gpg会通过字符段长度和/或 0x 前缀来判断是否在用用户ID。在指定密钥ID时,如果最后加入了一个感叹号,可以强制使用密钥id指定的密钥,而不是尝试计算应当使用的主密钥或子密钥。

gpg密钥ID可以在gpg密钥编辑模式下通过list命令查看

1
2
3
4
5
6
gpg> list
sec  rsa4096/7F9199B8737F5089
     …………………………
ssb  rsa4096/0EA18957BCB383D0
     …………………………
gpg> 

此处的 7F9199B8737F50890EA18957BCB383D0 分别为主密钥ID和子密钥ID

如果使用脚本等进行批处理,应当使用 --with-colons 选项,这会打印便于计算机处理的由冒号 : 分割的列表。(不过–with-colons选项不允许单独使用,同时还需要与其他列出信息的选项连用,比如说"-k"、"-K"、"–list-keys"之类的)

1
2
3
4
5
6
7
8
$ gpg -K --with-colons example
sec:u:4096:1:7F9199B8737F5089:1596592245:1627696245::u:::escaESCA:::+:::23::0:
fpr:::::::::0339DCAA11EA0C1ACEB36A5C7F9199B8737F5089:
grp:::::::::3CC05090E4442BBC2A729C9874DEDDAAF25BCF82:
uid:u::::1596592245::AFF800466D7EDF8CAE427CCBB825B88376DF3B6F::example (仅用于示例) <[email protected]>::::::::::0:
ssb:u:4096:1:0EA18957BCB383D0:1596679712:1605319712:::::esa:::+:::23:
fpr:::::::::25EA761F08C41F7B22FA94220EA18957BCB383D0:
grp:::::::::CDE35DFDD016D3922708C6A48C853EA57C7245C0:

secssbpubsub 开头的行包含了gpg的密钥ID。

需要注意的是,使用密钥ID只是为了方便。在进行批处理操作的情况下,不应该使用密钥ID,而应该使用完整的密钥指纹指定密钥(这样能够降低密钥重复的情况)。

使用密钥ID指定密钥:

1
2
3
4
5
6
7
7F9199B8737F5089
0x7F9199B8737F5089
0x7F9199B8737F5089!

0EA18957BCB383D0
0x0EA18957BCB383D0
0x0EA18957BCB383D0!

注意,以"!“结尾指定密钥具有特殊含义

密钥指纹指定

gpg密钥指纹和密钥ID类似,是通过 0x 前缀和/或字符串长度判断。

密钥指纹字符串可以在 --with-colons 选项的结果中找到,以 fpr 开头的行就是。

1
2
3
4
5
$ gpg -K --with-colons example
…………
fpr:::::::::0339DCAA11EA0C1ACEB36A5C7F9199B8737F5089:
…………
fpr:::::::::25EA761F08C41F7B22FA94220EA18957BCB383D0:

另外,密钥指纹常常用于辅助验证密钥真实性,以降低收到假公钥而被中间人攻击的概率。

使用 --fingerprint 选项会打印使用空格分隔的主密钥指纹(便于逐字比对),但是默认不会打印子密钥指纹。

1
2
3
4
5
$ gpg --fingerprint example
pub   rsa4096 2020-08-05 [SCEA] [有效至:2021-07-31]
      0339 DCAA 11EA 0C1A CEB3  6A5C 7F91 99B8 737F 5089
uid           [ 绝对 ] example (仅用于示例) <[email protected]>
sub   rsa4096 2020-08-06 [SEA] [有效至:2020-11-14]

只有加上 --with-subkey-fingerprint 选项才会显示子密钥指纹

1
2
3
4
5
6
$ gpg --fingerprint  --with-subkey-fingerprint example
pub   rsa4096 2020-08-05 [SCEA] [有效至:2021-07-31]
      0339 DCAA 11EA 0C1A CEB3  6A5C 7F91 99B8 737F 5089
uid           [ 绝对 ] example (仅用于示例) <[email protected]>
sub   rsa4096 2020-08-06 [SEA] [有效至:2020-11-14]
      25EA 761F 08C4 1F7B 22FA  9422 0EA1 8957 BCB3 83D0

使用密钥指纹指定密钥:

1
2
3
4
5
6
7
0339DCAA11EA0C1ACEB36A5C7F9199B8737F5089
0x0339DCAA11EA0C1ACEB36A5C7F9199B8737F5089
0x0339DCAA11EA0C1ACEB36A5C7F9199B8737F5089!

25EA761F08C41F7B22FA94220EA18957BCB383D0
0x25EA761F08C41F7B22FA94220EA18957BCB383D0
0x25EA761F08C41F7B22FA94220EA18957BCB383D0!

注意,和密钥ID一样,以”!“结尾指定密钥具有特殊含义

用户标识完全匹配

gpg允许使用密钥声明的用户标识进行完全匹配,这使用前缀的“=”等号表示,如:

1
  "=example (仅用于示例) <[email protected]>"

电子邮件完全匹配

gpg允许使用电子邮件进行完全匹配,这使用包围住邮箱的尖括号表示,如:

电子邮件域名搜索匹配

gpg允许使用电子邮件进行部分匹配,这通过在搜索的字符前家上前缀”@“来表示,如:

1
2
3
@example.org
@example
@[email protected]

@后面的字符仅会搜索匹配尖括号的内容

使用字符串搜索匹配

如果没有使用任何标识,gpg将在密钥中搜索字符串以匹配密钥,

不过gpg软件会希望前戳 * 来表示这一点,如

1
2
3
4
example
*example
仅用于示例  #搜索注释
*仅用于示例

使用keygrip匹配

gpg也支持使用keygrip来匹配密钥,这通过 “&” 和紧随其后的四十个16进制数字表示

可以使用 --with-keygrip 选项来显示keygrip,以人类易读的方式

1
2
3
4
5
6
7
$ gpg --with-keygrip -k example
pub   rsa4096 2020-08-05 [SCEA] [有效至:2021-07-31]
      0339DCAA11EA0C1ACEB36A5C7F9199B8737F5089
      Keygrip = 3CC05090E4442BBC2A729C9874DEDDAAF25BCF82
uid           [ 绝对 ] example (仅用于示例) <[email protected]>
sub   rsa4096 2020-08-06 [SEA] [有效至:2020-11-14]
      Keygrip = CDE35DFDD016D3922708C6A48C853EA57C7245C0

gpg的 --with-colons 选项也能输出keygrip,以grp开头的就是。不过与 “-k” (小写k)连用似乎不会显示keygrip。

1
2
3
4
5
$ gpg -K --with-colons example
…………
grp:::::::::3CC05090E4442BBC2A729C9874DEDDAAF25BCF82:
…………
grp:::::::::CDE35DFDD016D3922708C6A48C853EA57C7245C0:

使用keygrip来标识密钥:

1
2
"&3CC05090E4442BBC2A729C9874DEDDAAF25BCF82"
"&CDE35DFDD016D3922708C6A48C853EA57C7245C0"

导出公钥

就目前所知的情况,无论使用哪种导出方式,主公钥 pub 都会包含在导出结果内。

使用 --export 选项可以导出公钥,没有其他选项的话,默认会以二进制格式导出当前密钥环的所有公钥,并在终端打印所导出的公钥。

可以使用重定向或者 --output (简称 -o)选项重定向至文件中,不过 -o 选项要在其他选项之前,否则可能导致选项被忽略。另外,如果将 -o 选项的输出设置为 - 则会将内容输出至标准输出。

1
2
gpg --export >all.pub
gpg --export -o all.pub

在导出命令后面加上user_ID可以筛选要导出的目标公钥。

1
gpg  --yes -o /tmp/example.pub.asc -a --export example 

像这样的话,gpg将只导出匹配"example"的密钥的公钥,并导出至/tmp/example.pub.asc文件中。

另外 -a 选项会将密钥导出为可打印格式(即生成结果仅允许ascii字符)。不过ascii版本的公钥在不导入的情况下无法进行部分操作,因此,如果可以则应当尽量使用二进制版本。

虽然说 --enarmor--dearmor 这两个选项能够转换二进制和ascii格式,不过处理后格式兼容性存在一定问题。

导出特定子公钥

虽然说,一般情况下没有单独导出特定子公钥的必要,不过姑且在这里记录一下导出方法

就目前已知的信息,任何情况下,使用gpg命令导出密钥,主公钥都会被包含在导出文件中。因此,此处的“导出子公钥”是指导出不包括其他子公钥的公钥文件

这一特殊导出类型,就目前所知,仅支持密钥ID指定。

首先。执行 gpg -K --with-colons [user_id] 获取想要导出的子公钥的密钥ID或密钥指纹信息

1
gpg -k --with-colons example
1
2
3
4
5
6
tru::1:1596619663:1627696245:3:1:5
pub:u:4096:1:7F9199B8737F5089:1596592245:1627696245::u:::escaESCA::::::23::0:
fpr:::::::::0339DCAA11EA0C1ACEB36A5C7F9199B8737F5089:
uid:u::::1596592245::AFF800466D7EDF8CAE427CCBB825B88376DF3B6F::example (仅用于示例) <[email protected]>::::::::::0:
sub:u:4096:1:0EA18957BCB383D0:1596679712:1605319712:::::esa::::::23:
fpr:::::::::25EA761F08C41F7B22FA94220EA18957BCB383D0:

以子密钥 0EA18957BCB383D0 为例

1
gpg -a --export "0x0EA18957BCB383D0!"

这样将会导出一个包含子公钥 0EA18957BCB383D0 和认证该子密钥的主密钥的公钥的文件。

注意,密钥ID最后的’!‘感叹号不能遗漏,否则将会导出该密钥所对应的全部公钥。

另外,如果指定的密钥ID是主密钥的ID(以sec开头的行所指定的密钥),那么将不会导出除主公钥外任何公钥。

导出私钥

注意,私钥文件,尤其是含有主私钥的私钥文件导出后必须小心传递存储,否则私钥泄露可能出现严重后果!

在绝大多数情况下,私钥文件都不应该被导出。不过,有时需要进行备份或者迁移密钥,这时可以通过导出私钥文件来进行备份或者迁移操作。

gpg提供了 --export-secret-key 选项用于导出私钥。其用法如下:

1
gpg -a --export-secret-key example

上述命令将会以ascii格式导出匹配"example"字符串的密钥的所有私钥(和公钥),并打印至终端,可以使用重定向或 -o 选项指定导出文件保存位置。导出时需要输入密钥密码验证权限。

导出子密钥

相对于导出所有私钥,导出子密钥相对用到的地方就比较多了。比如说导出一个签名用子密钥到服务器来自动化文件签名什么的?

gpg提供了一个选项来专门导出子密钥—— --export-secret-subkey ,这个选项会自动导出密钥环内符合条件的所有子密钥。例如:

1
gpg -a --export-secret-subkey example

这个命令,将会以ascii格式导出最近的,匹配"example"字符串的密钥的所有子私钥至标准输出。当然,需要输入密码。

--export-secret-key 选项的区别是, --export-secret-subkey 如果拒绝输入密码,将会导出公钥(而不是不导出任何信息)。

导出特定子密钥

类似于导出特定子公钥,导出特定子密钥也只能使用密钥ID或密钥指纹来指定。并需要在最后面加上”!“感叹号来避免解析其他相关密钥。

1
gpg -a --export-secret-subkey "0x0EA18957BCB383D0!"

将会导出密钥 0EA18957BCB383D0 的私钥,而主密钥 7F9199B8737F5089 的私钥,和其他子密钥在导出时就此被剥离。

另外,需要注意的是, --export-secret-key 不允许单独导出子密钥,即使指定密钥ID时使用”!" ,也会在导出结果中包含主私钥。

类似于导出特定子公钥,如果指定导出的密钥ID是主私钥的密钥ID,则除主密钥外的任何信息都将被剥离,比如子私钥以及子公钥。

注意, --export-secret-subkey 是GPG的拓展选项。通过该选项所导出的密钥文件不一定能被其他OpenPGP标准实现上导入。

导入密钥文件

使用 --import 将要求gpg导入密钥文件至密钥链。

可以使用该选项向其他设备迁移密钥,以及导入其他人的公钥。

1
gpg --import [密钥文件]

这一命令将会导入[密钥文件]至默认密钥环,如果导入的密钥文件包含私钥,则可能会要求输入密码解锁并导入私钥。

非对称加密体系简述


在介绍生成的密钥对的功能之前,可能需要简单了解一下非对称加密的原理。

关于 非对称加密 ,是指知道加密方法不能够通过逆操作(或者说逆操作成本过高)来对密文进行解密的一系列加密方式的总称。

这一加密体系中存在一组对应的密钥,称为 公钥私钥

公钥一般用于加密,私钥一般用于解密。不过一部分加密算法支持使用私钥加密,其作用就是告诉公钥持有者,这段加密信息确实是由私钥持有者发布而不是第三方伪造的。这种用途被称为 数字签名

公钥可以自由分发给其他人,任何通过某个途径获取公钥的人都可以将信息加密为只有持有私钥才能解密的内容。

通过公钥很难推导出对应的私钥,如果试图强行暴力破解的话,根据gpg官网的介绍,强行破解时所释放的热量足以煮沸海洋,以目前的技术水平几乎不可能实现。

只有私钥能够解密密文,因此,私钥必须小心谨慎的保存,私钥一旦泄露,任何第三方都可能解密密文,而泄露被加密的信息。

签名与验证[S]

签名、散列函数与数字签名

签名 其本意是指写下自己的名字,不过,现实中,在某些特殊情况下的签名往往还带有"认证,保证,负责"之类的的意义,担保被签名文件的真实与准确性。

比如说,质检员在质量检测报告上的签名有“以质检员身份保证这一质量检测报告真实和准确性”的意义。而在协议文本上的签名也带有“协议各方以各自身份确认协议有效性,并会遵守协议”的含义。

不过,在网络中,对文件内容进行删改编辑非常容易,不像物理介质那样,被修改了表面往往会留下些许痕迹。

这样,为了避免电子文件被篡改,或者因为传输问题损坏文件而没被检查出来,就需要其他技术手段来避免文件没有受到篡改和变动。比如说散列函数(哈希函数),以及数字签名。

散列函数


散列函数,一般用于校验文件完整性,以及服务器加密保存密码。

散列函数是一种多对一的函数,即多个不同的输入可能会有同一个输出。一个简单的散列函数,可以是计算输入除以某个值的余数,比如说45? \(f(x)=x\mod45\),当然,这个散列函数基本没有什么实用价值就是了。

一个优秀的散列函数需要有两种性质

  1. 很难找到两个输出哈希值相同但内容不同的输入文件。(避免哈希碰撞攻击)
  2. 给定哈希值很难乃至不可能恢复输入的内容。(避免以哈希值格式保存的密码泄露)

当前比较常用的校验和算法有 md5sha1sha256 等。(注意,md5目前已经被认为不安全,因为md5算法已经发现影响性质1的漏洞,很容易受到哈希碰撞攻击)
在linux下可以使用 md5sumsha1sumsha256sum 命令来获取文件校验码。例如:

1
2
3
4
echo "散列算法 文件名 = 校验值/校验和(以bsd风格输出校验和)"
md5sum --tag /bin/bash
sha1sum --tag /bin/bash
sha256sum --tag /bin/bash
散列算法 文件名 = 校验值/校验和(以bsd风格输出校验和)
MD5 (/bin/bash) = 34eb9bff3d8e6e55916ea900917bfefe
SHA1 (/bin/bash) = 845260cdda0f6e585e1c7719b3be7fba8739faa5
SHA256 (/bin/bash) = 901165bb063e6c33299ee6c5cfd44f4729d213c9d2ee96ff65a0973cd57db134

使用散列函数验证文件完整性的步骤一般是:

  • 文件发布者
    1. 发布文件时计算文件散列值(文件hash)
    2. 将文件和文件散列值(文件hash)同时发布到网站上
  • 文件接收者
    1. 从文件发布网站获取文件散列值和文件
    2. 本地计算文件散列值并与获取的散列值进行对比,如果不同则完整性校验失败

如下图所示。

如果文件的校验和是准确的话,依靠哈希算法的性质1,,比较校验和能够保证文件的完整性,

不过,这种验证方法存在缺点: 通过哈希值验证仅能证明下载到的确实是文件分发方提供的文件,但并不能保证这一分发版本就是由文件发布者确认的原始版本。仅凭借hash值并不能判断hash值是原始hash还是重新生成的hash。并且,如果网络劫持者能够在传输时劫持文件,那么,修改所看到的校验值也不会增加太多难度。

如上图,尽管文件通过了hash校验,但是由于是通过是恶意分发方获取校验值和文件,导致篡改没有被发现。因此,仅仅通过校验和对比是不够稳妥的。

为了避免出现这样的安全风险,可以使用数字签名技术进行验证。

数字签名

数字签名,是非对称加密的一种使用方法——使用私钥加密一段信息,并且任何公钥持有者都可以通过公钥检查这一加密文件是否为公钥所对应的私钥加密的。这依靠私钥加密后的信息难以在不持有私钥的情况下生成伪造的性质。(知道公钥的解密算法不能推导出对应的加密算法/推导出对应的加密算法的成本过高)

像这样的使用私钥加密过的信息可以告诉所有公钥持有者,这段加密的文件就是该公钥所对应的私钥所加密的。像这种,使用私钥加密过的信息,就是数字签名。

通过数字签名验证能够保证,数据已经通过签名者的确认(且知道签名者),并且数据没有被第三方篡改。

gpg支持使用私钥对数据签名。不过,根据GPG官网的介绍,数字签名也不是所有非对称加密算法都支持的,比如说ElGamal算法就不支持使用私钥加密。

理论上来说,可以使用私钥加密整个文件,然后发布这一加密后的文件。不过由于非对称加密为了其安全性,解密效率较差。因此,出于效率等因素考虑,基本不使用这种方法(gpg似乎也不支持这样子加密),而是生成并使用包含文件散列值的 摘要 来进行验证。并将摘要和原始文件同时发布。

而摘要实质上就是一段包含有文件散列值的文本,运用强散列函数所具备的"很难找到两个输出相同但内容不同的输入文件"这一性质,能够保证文件很难被篡改而不被发现。同时,使用私钥加密能够保证摘要本身很难被不持有私钥的第三方恶意篡改。

数字签名和验证流程大致如下:
签名流程

  1. 输入数据,计算数据的校验值(可以参考散列函数一节)
  2. 将校验值和一些签名时间还有签名者合并起来,生成摘要
  3. 将生成的摘要使用私钥加密,生成签名
  4. 将签名和数据公开发布

验证流程

  1. 获取签名文件和原始数据
  2. 使用对应的可信公钥解密签名,获取摘要
  3. 本地计算数据校验值
  4. 将计算得到的校验和与摘要文件中的校验和进行对比。如果相同,则基本上可以确认文件没有受到篡改。

也可以参考一下下面这张图

创建常规签名

gpg提供了很多选项用来创建不同的数字签名。最简单的,可以使用 --sign 1选项创建一个合并签名(可以简写为 -s)。签名步骤因为需要使用私钥,所以需要使用密码解密私钥。

不过,如果存在多个可用主私钥的话,没有其他选项,gpg将会默认使用其中最早创建的私钥。这时,可以使用 --local-user 选项来指定用于签名的密钥(可简写为 -u)。

gpg允许从管道接受要被签名的信息,下面这条命令将会使用密钥"example"进行签名,然后以ascii格式在终端输出经过压缩后的签名信息。

1
echo "测试签名消息"|gpg -s -a -u example
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
-----BEGIN PGP MESSAGE-----

owEBdQKK/ZANAwAIAQ6hiVe8s4PQAcsZYgBfLmsC5rWL6K+V562+5ZCN5paH5Lu2
CokCSAQAAQgAMhYhBCXqdh8IxB97IvqUIg6hiVe8s4PQBQJfLmsCFBxleGFtcGxl
QGV4YW1wbGUub3JnAAoJEA6hiVe8s4PQRlIQAImjFiB/3UwCzVlbux1KSxTB3Wi4
09jpK4L2pSYYIGTVUpHP1LR63fhH27xZTNHWX6642ES15n7ZH7ULR43pZpXDmqyZ
aNhu6WAGLKIZQMWb8mpv7fGTEO9HPNM+/MtVnZOmMxyZeQlAZcIoW34a5B1mbwHD
LqFpDorjEG7e8b5l8dqAqDMeXjFqJXu7hCbvmuehc4DTN4Vc+8E0rqy3CLTx/qdb
fnIBXBNDDCqwXo/bPNx3B4UYdjUJ6oQxAyNdleE5kfKXDbJmdhC/BnUz+GvTT8fC
WL9jS0cxbfZA7YB5yzZeibRCXeHh+dM309dzYxt1zJvXi9kZoUxbpEg8hCNwyAai
gW1V5WVoIjZAWs+ugYUup3R12eIv3RNDGsvr/d5JzgBG2aMW/zLFNy6aL2w3z+43
F8D2eTGeapxDQMuebR8KxwsKreKbdDRuHFHmndL6HPz/hvwI8tMGrKhgzaYruoux
qFeU8dNV6pOOY4JmXl+bY21pLxEus9W32uuojYwzr07xDPFOjopsNVIF4iNpETkk
7nEIaujGxS2elKCljPwrqrsCBZRBchywDaeB7iOTtuQq4Ks08gojo9AyXkU3vuF+
WYR9r64KIR9M7NgY1vNhXbc+6XhLZKKBSFpc0DEMjhQq9Apk5eNOlQz0MnRapXzZ
4Na6fdK6tk/EEd7R
=GWva
-----END PGP MESSAGE-----

当然,也可以对文件进行签名。

下面这条命令(严格来说是第二行),会对位于 /tmp/test_sign.txt 的文件签名。指定文件进行合并签名的内容,默认不会打印至终端,而是会保存到 [原始文件名].asc (ascii输出),或者 [原始文件名].gpg (二进制输出)。

1
2
echo "测试签名文件" > /tmp/test_sign.txt  #创建test_sign.txt 
gpg -o - -u example --sign -a /tmp/test_sign.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
-----BEGIN PGP MESSAGE-----

owEBggJ9/ZANAwAIAQ6hiVe8s4PQAawmYg10ZXN0X3NpZ24udHh0Xy602Oa1i+iv
leetvuWQjeaWh+S7tgqJAkgEAAEIADIWIQQl6nYfCMQfeyL6lCIOoYlXvLOD0AUC
Xy602BQcZXhhbXBsZUBleGFtcGxlLm9yZwAKCRAOoYlXvLOD0CFYEACD88CN53vG
ej467Eg3JDvWmpdWPtkJ+19VOXirvjRSDDGpDpgTuNTGYSQBmz+kWLdtnRw5GuV6
pSzFaBtth2H5SMCSdxKmHTsVJ/SlCxzvy9SDd2H8REYbublu8RsSn4L4I9Vqq9nO
cs1akzbKOdhks8N5qjM2jrUOkhxLLYpow74brMSjz1Qsqf0O7LNW4l9z2RGp/Sdm
LphgosHqXomiN+gq83J0who43K6jyMWDJRERwghfEyVcYqtFwJwATxCar3npMVTV
MayyJnUj+i0xjsmwIJkYPZwbQPkyV4wFYEOOuzwXZFB/V2rpgtXX0aptskJp/tRv
78QtC1vluMrPu85/mZiglOfeuEpW521zLU80GARWzjgXwhagifwIviGcYRQlDmaL
UoHpQgPcO+oiF/fALKoCqH8SG05Lafut6r1JIfrW6pRpvzwVrpSOZgKKWBsw0Q3a
/DcgvnPnJAa4djXvKeGkaDIgPQCMmk0CqQraWMn8x9ouNqChp0fXlinw3K/xzo6R
4jQbdPPtX1LU7tyFdQ3bb7UViLER1lp/fXUWEqS2y5kzt9R5oYMwG8eT0/mqibVy
QaZBXOAI4DvejuhYnV1Ljyg64s3WO2W3DgTYJo3/dqx0lK7hgWjM9QXxq6fd7NfA
1Euz4X5tFRUAIxf3uceutrnkrYp7RV1L1A==
=LRcn
-----END PGP MESSAGE-----

使用 --sign -s 选项进行签名,会输出一个包含经过压缩处理后的原始文件,需要从输出文件中提取原始文件。

创建明文签名

常规的签名文件,其原文是压缩过的,需要粘帖到签名验证的程序里解压缩后才能查看被签名的信息,这样在网络论坛以及电子之类的地方使用签名相对比较麻烦,这时可以使用明文签名的方式来生成可读的明文签名,只有在需要验证签名的时候需要使用OpenPGP。

创建明文签名使用 --clearsign 1(--clear-sign 也可以)选项生成,这将会生成一个以明文显示原始数据的ascii签名文本。

1
2
3
echo "这里,现在,
将要创建一个明文签名
而这里,就是明文部分"|gpg --clearsign -u example
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

这里,现在,
将要创建一个明文签名
而这里,就是明文部分
-----BEGIN PGP SIGNATURE-----

iQJIBAEBCAAyFiEEJep2HwjEH3si+pQiDqGJV7yzg9AFAl8utOoUHGV4YW1wbGVA
ZXhhbXBsZS5vcmcACgkQDqGJV7yzg9A4Eg//ZXHiDsAVyDrC3q37AeikVgU5GFLG
pNnSJb99pnB0fgLw/UQ7//h/IvaBiJlIUFDjDLSMwO3tj9KdfmQlQSBpYgpgvrQd
PQCuCAqg4Kh+yNr/G7jDfYHBn8qvap61YGEh1/Ivk1iVpD+z+mQBBKpPsCDHWKmt
iV9uQyzfzyLmBRw/OEGCD/fbTc6cJ/z15Fas3Vs2uV2xY2WXyN1+rurlBQpfhU4c
gWLveDtWZ0v2I2Sowi0h6VHIsYq6sMSsa1chCw1YjHZP1UN04OncJb6kDK1rgKlP
ZWS66bhIzV32qP+opF9dnl5DXdgdDVZFHyx8Ol2ADdlWXn+FcAN1xiovSr4mIQp1
juMMjbz+rcpxNklTvQppt06rc+vTpxHJ9DK4G8tj7urWJKiOG862F5rHiiN5WKgb
+SyMKsXgLsmk6qJV41NXRmjMDI3YmLCXzh91L3s69JJvONlJfIXzDvad+9qNaXbq
q1Z0iPXQBr9sKOrDLBbCjIIteeduJbngstyjRajSlSltWgxsQOEUIpvemXhCRnld
lGjon0CHuMHK2JvAHAORO1Q192MuMI7PiVYCyTzL/kXXHa0SHK/g3QC+ius15RXz
Z9RGXVFKXs5HJlMopoMng953IttJ+mmmYyAsiKjZdTMOHJqFuEUuRdBtAZTg8LEg
gfOcrWXh6/TLIsc=
=zdIw
-----END PGP SIGNATURE-----

如上所示,原始数据和签名文件共同被包含在输出的结果之中,并且原始数据不会被压缩,可以直接看到明文信息,而大体上持原样输出。

不过需要注意的是,为了实现平台独立性,明文签名可能会修改行尾空格,并且此这一操作 不可逆

而且使用明文签名处理二进制文件可能会产生原始文件损坏的严重错误,任何情况下都不要将明文签名用于二进制文件上。

创建分离签名

将原始文件和签名摘要混合打包为一个文件,用途有限。这样的签名在使用之前往往需要提取原始文件。即使是明文签名,想要获取原始文档也需要进行提取的步骤。

不过,gpg支持创建分离的签名,以让摘要信息单独保存在一个文件之中。像这样,分离签名可以避免提取文件的步骤。

使用 --detach-sig 1(可简写为 -b)选项将会创建一个分离签名

1
gpg -o - -u example -a -b /bin/bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
-----BEGIN PGP SIGNATURE-----

iQJIBAABCAAyFiEEJep2HwjEH3si+pQiDqGJV7yzg9AFAl8uwkoUHGV4YW1wbGVA
ZXhhbXBsZS5vcmcACgkQDqGJV7yzg9AEJg//V5T7eNZEE7ReJWQK9Z3vSY2wsd6d
N9a3LMBJasp+i0nN4/XrAo4JbDhs8un4Cci1l3FDHI7BA6vs6PDfzGHL4FwUlCVa
h8PbCSV0py92ExVccmWBLxhnHsZAcw1WMSgJR62iYAS4USmZbJdSBomKwe5jc1sP
o6KDVEerYQ/NpIu6Crjho2K1ioQ9GcyimLxK0tSmKXUWj3A16CKAFN7776HJ4Ch5
r3/FDYZE2XFlUyqamfWrlKXT1QJ1XIUgpkXyUyKllOakUYoz6n3psiGbNFUM8bYq
QQDyz1zE8k8LAwwsVNgKHvfRpUwyfpI2CAs45VeW0DbV49uPJRPeqOccquL6Yzf3
QXiOlc7OwrUBEEB67+M1KmspLYQsL4QAZrS5RxvAzaB8NLGNKra0fxsPKS1P3LR4
DG5TjiH6GL7Lpu7EZwu4KbC1t2JlgnIhvTTqcGxYS9d4TZLrJvsazQ8OGhyq1DFT
PZL7RjpZt9Ux7tEqVvZYEfe+frTg9b1dPbjxGTT8sCLPBO6+HG3BJ40nWuxufax5
I6lHwqwgIivZEKTyBwJq1XoHDW7FRPVS14g/Htnvt/UsKEpqiC0HI8bKlieXWcft
AjD0kuonJw5496g23tA+3WccmT95Mte6xJfmtLfi/DACnGf3Sz6ocOioQFHCynJD
YEcwT+Zagl2IlZc=
=1Ygx
-----END PGP SIGNATURE-----

上面那条命令,会使用密钥example来对文件/bin/bash生成一个分离签名,并将生成的签名摘要以ascii格式输出至标准输出,

如果没有指定-o选项,则分离的签名摘要则默认会被保存到 [原始文件名].asc (ascii格式输出),或 [原始文件名].sig (二进制格式输出)。

设置签名有效期与GPG时间相关选项


有时候,希望对创建的签名文件声明一个有效期,这时可以为签名设置一个有效期。

可以使用 --ask-sig-expire 2选项或者 --default-cert-expire 2选项可以在签名时指定有效期,不过这两种选项用法各有差异。

使用 --ask-sig-expire 选项会进入一个输入状态,会要求输入签名有效期。如下所示

1
echo "签名截止日期为创建之日3月后"|gpg -u example --ask-sig-expire -a --clearsign
1
2
3
4
5
6
7
8
9
请设定这份签名的有效期限。
         0 = 签名永不过期
      <n>  = 签名在 n 天后过期
      <n>w = 签名在 n 周后过期
      <n>m = 签名在 n 月后过期
      <n>y = 签名在 n 年后过期
签名的有效期限是?(0) 3m
签名于 2020年11月07日 星期六 13时03分16秒 CST 过期
这些内容正确吗? (y/N) y

类似于gpg设置密钥过期时间,可以输入(d)、w、m、y来设置密钥过期时间,将会把签名的过期时间设置为n个时间单位后。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

签名截止日期为创建之日3月后
-----BEGIN PGP SIGNATURE-----

iQJOBAEBCAA4FiEEJep2HwjEH3si+pQiDqGJV7yzg9AFAl8vg5YFgwB2pwAUHGV4
YW1wbGVAZXhhbXBsZS5vcmcACgkQDqGJV7yzg9DLixAAoe1REm8z88VMvhuUo0OC
LuCvD7dOSl79BtvooKUiSZ98XuA92xXk7dgUD0AFUPUaXiYbhAjHyCFFXoFPZBPp
tJQR3CONHqBuiHRPftmWIrkNIixO2nyowJ3Ez/3fMsyGHyzFcnQUPEaxAdB/IBft
6hEdeaGLR7j8vHjLYg/kUWdKuBFiQVt1UvhA2m+MNi/oYB/x48IehrDUPVfHQsIq
zbC4vJpBo3KAvbusO5R6cEwmwzWxMA4xbWfMwSXAYfCcXfMuSo/CgAp4MNlMOFNc
EFaCEsW/ws8MsmZp6X4qKt/2XFEUqES4mrYjkx1REj7Wc9r/JdRQC0duNkASOG/9
5W97yeFfBIJK9TZrJSyeFUpFjPxkooA9gAZn6529NI/0oF0umD69kisPd1EKW6OR
GUuwCxWCbzB4zpxDQsxY2mLG2GoFjsQLFFyNCrb+PiIlY9sqDsL1C9BcLuvrL3jK
lf8JXik/DMIGr/4MKNhLtq8tv94OS0EXNonlo40iQ7MoX7GP4L4KV5wqJDQqeRem
9nPAZoWYHx01219bC3ONbTJJz/M6bH29ed9x9HUVQs7+XiDswwMQcKAv3swpCYwO
/ymcii7ZyZO4h/T3Mewsn06v79D1s+DzgN2npqdvuwEKabjvQ4bz44ZIsLO2FBAp
ln+Kc1pm2Sq+hNaeK+RIceQ=
=Rl3r
-----END PGP SIGNATURE-----

--default-sig-expire 选项需要一个值,他可以接受上面所记述的 --ask-sig-expire 选项的时间单位输入,也支持以 “YYYY-MM-DD”(年-月-日)格式来输入绝对值截止日期。

1
echo "签名截止日期为2020年9月7日"|gpg -u example --default-sig-expire 2020-09-07 -a --clearsign 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

签名截止日期为2020年9月7日后
-----BEGIN PGP SIGNATURE-----

iQJOBAEBCAA4FiEEJep2HwjEH3si+pQiDqGJV7yzg9AFAl8vk3QFgwAmHMwUHGV4
YW1wbGVAZXhhbXBsZS5vcmcACgkQDqGJV7yzg9A7rhAAj1NFCVANirxqj2yB61yl
5hipraqOzVrIQzpxvej3aWn23U5eHPonp/F+7uN5whpqCGFSw+Dz/1eHkfsd18Wk
T4HSLfiyFSwN6P+UjM9t45pefr3v5QHhOc/7BZeVGEPsAMk0/cBjDfj2NR2oqdx+
qIzrPYAnQgp2mETyBaJTL0/CNY5SS0AQcFZJfeBIWcKe2jIH6IRkYVs72vhM4m45
qWXXlnyGhwHAw7BBzoQ2s6lOfe2NZnC8ecw7efq93HQ5jql3HoSoaXpkmUef38ZJ
jj7FkZdVlljHcXa3OnomTTSpyjpyZORO0Xpm8whQNoXbLhi5E1ggLSLWAwJti/Mh
NYN/SGiLa5oz0+y5KDrRvpNWF4ldeAuQDIgg6DNahK35SyVmyOm1X/17R8MjWj09
iZAGLl8befPVarkaajeovdFK2bTbb1bD2OKDc5xeawOzvJHwz5mwwXNwLlcie4bP
BA0sUrDDzJiIFooyLIRcozrLCooSYF0q1qc3LukZrXFfi3EI3CfNl+2XjXPuVnhM
47ERYa6vVh/Wd8dHunpnjKTxVhC9Lba5XSKtXUuLjFK5mlLw0Ejaq0Ux4q9vY46s
+/q1d5hPDlVNuqZawzXqWMegBi8To3K4CwOKZgLhskReexYCHDFm8e7IwD3K6QKc
i7OkMxZfu52Umi7J+7Mchro=
=cBQt
-----END PGP SIGNATURE-----

上面这条命令会生成一个签名有效期的截止日期为2020年9月7日的明文签名。

选项 --ask-sig-expire 所输入的值会覆盖 --default-sig-expire 选项指定的值,可以使用 --no-ask-sig-expire 选项禁用 --ask-sig-expire

在为密钥签名时,也有两个类似的选项 --ask-cert-expire--default-cert-expire 选项能用于修改密钥签名的有效期,关于这两个选项的用法,可以类似于上述修改签名有效期的选项的用法,也可以参考gpg文档,此处不做介绍。

指定[创建时间]

虽然说,正常情况下,指定创建时间没有什么用。不过可能有些时候会需要的(比如说调试的时候)。

虽然修改系统时间也是一种解决方法,不过太过麻烦。不过,gpg提供了一个选项来修改进程中所使用的时间。

选项 --faked-system-time 2能用来指定gpg所使用的系统(当前)时间。

该选项允许使用两种格式的参数。其一是 自1970年1月1日GMT至指定时间所经过的所有秒数 ,比如 1262275200 (2010年1月1日0时0分0秒)。而另外一种格式则为完整的ISO时间字符串 年月日T时分秒20151227T173148 (2015年12月27日17时31分48秒)。然后,如果在参数后面加上"!“感叹号的话,时间将会出现在指定定时间而被冻结。

1
2
3
4
date|gpg --faked-system-time 253402300799 -s -u example 2>&1
echo "-------万年虫-------"
date|gpg --faked-system-time 253402300800 -s -u example 2>&1
(echo -n >&2) 2>&1
1
2
3
4
5
6
7
gpg: 警告:正在以虚假的系统时间运行: 9999-12-31 23:59:59
gpg: 已跳过 “example”: 不可用密钥
gpg: signing failed: 不可用密钥
-------万年虫-------
gpg: 警告:正在以虚假的系统时间运行: 1000-00-10 T0:00:00
gpg: 已跳过 “example”: 不可用密钥
gpg: signing failed: 不可用密钥

如上,将会要求gpg把 253402300799 (从1970年开始的秒数)换算为 9999-12-31 23:59:59 ,并当作执行gpg时当前系统的时间。

(如果指定的秒数大于"253402300799"的话,嗯,会出现万年虫的,不过应该没人会活到9999年后吧,真到9999年后gpg这种签名技术估计已经极端不安全了)

而另外一种格式,可读性相对较好 20210101T120000 2021年1月1日12时整

1
2
date|gpg --faked-system-time 20210101T120000 -s -a -u example 2>&1
(echo -n >&2) 2>&1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
gpg: 警告:正在以虚假的系统时间运行: 2021-01-01 12:00:00
-----BEGIN PGP MESSAGE-----

owEBjQJy/ZANAwAIAX+Rmbhzf1CJAcsxYgBf7w7AMjAyMOW5tCAwOOaciCAwOeaX
pSDmmJ/mnJ/ml6UgMTU6MjU6MjYgQ1NUCokCSAQAAQgAMhYhBAM53KoR6gwazrNq
XH+Rmbhzf1CJBQJf7w7AFBxleGFtcGxlQGV4YW1wbGUub3JnAAoJEH+Rmbhzf1CJ
OjEQAL247jURTeMrUITr0jpaKwn/oaKQ4a5g3t2gyCpfjsTIKny69fXJGAwqF2hL
nL6ZbSvlAD4EuqCEjyA2Y+AUeKslJdUikqqbN2uDkKmZ1jWconJeNByZWgDCSdEC
tDNN+yU90BbZ9vwU3X8yrKU9vR6dRflL3JnO6+Kol4xZe6jvgufHEx9VqdJHTPik
HuYPVh+vV0+HCbEGglV2oJnyTXSvz5p7yWsD1NFaN8lXzBjpK+OXCDH5GXVMmxg8
43+QzH3JWmcgRqRTiI56HIFPn6ypJbqyo3ucCx5bTa/pvUmNwG/lWNnRzqNUv5tr
rPXN6ov4nb8aCsonLp5xmd3AJ+G5tqVpdcS3mtYwqZ23whOwu+7pq0F/+l387s4m
7g6x98WNo/XbCW6BX5GYkw5O/SPX8CljiEnBMXy51/s7sSD18Oz0MfVhnYxQ5mMB
HLC75is/JHezJEgbD5wG2vJPMA+RLOJ6kpEChYDs9NzsoSra1f2hf0n7xzd252pO
lQmTD2GzYrOjIjQn03Tt3e9j4RcVyU4HaPqSH1tsMDTcSg8+83JHNzPlZ8Pb34DX
GkLFFQN1K5esM1VzxEaaiEhjioIYFFkZzxRlzARpPH/LvAlPhlMj8lHAxoGr9ACe
7O6R9bVyqSlVbbFGS6zkjNGNvg7U6j7X8Q46RApxRVxzVyoP
=/WgQ
-----END PGP MESSAGE-----

如果实在不会转换时间格式的话,可以用date命令来转换

1
2
3
4
5
echo "date的-d选项用于指定其他格式的时间"
date -d "2050-12-10 17:31:59 GMT" +%s
echo ^换算为1970年至指定时间的秒数
date -d "2050-12-10 17:31:59 GMT" +%Y%m%dT%H%M%S
echo ^换算为完整的ISO时间字符串
1
2
3
4
5
date的-d选项用于指定其他格式的时间
2554306319
^换算为1970年至指定时间的秒数
20501211T013159
^换算为完整的ISO时间字符串

需要注意的是, 此功能请不要随便使用。一个创建时间很奇怪的签名或密钥往往会让人很难信任,而且,随便乱设定该时间可能会导致时间相关的验证无法正常通过

忽略时间戳错误


gpg一般会检查时间是否具有合理的值,如果不合理,默认会拒绝执行。

比如说,一个签名(所请求)的创建时间在密钥的创建日期之后,这种情况下会gpg会直接拒绝执行。

1
2
date|gpg --faked-system-time $(date -d "2020-1-1" +%Y%m%dT%H%M%S) -s -u example 2>&1
(echo -n >&2) 2>&1
1
2
3
4
5
gpg: 警告:正在以虚假的系统时间运行: 2020-01-01 00:00:00
gpg: 密钥 7F9199B8737F5089 生成于未来的 217 天后(可能是因为时空扭曲或系统时钟的问题)
gpg: 密钥 7F9199B8737F5089 生成于未来的 217 天后(可能是因为时空扭曲或系统时钟的问题)
gpg: 已跳过 “example”: 不可用密钥
gpg: signing failed: 不可用密钥

如上所示,上述命令要求gpg把2020年1月1日当作当前时间,在密钥创建时间(2020年8月3日)之前。导致gpg报错时间错误,并拒绝执行签名操作。

gpg提供了 --ignore-time-conflict 2选项。这个选项允许gpg忽略时间错误,将时间相关的"错误”(不允许执行)降级为"警告"(允许执行,但输出警告提示)。

1
2
date|gpg -a --faked-system-time $(date -d "2020-1-1" +%Y%m%dT%H%M%S) --ignore-time-conflict -s -u example 2>&1
(echo -n >&2) 2>&1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
gpg: 警告:正在以虚假的系统时间运行: 2020-01-01 00:00:00
gpg: 密钥 7F9199B8737F5089 生成于未来的 217 天后(可能是因为时空扭曲或系统时钟的问题)
gpg: 密钥 7F9199B8737F5089 生成于未来的 217 天后(可能是因为时空扭曲或系统时钟的问题)
gpg: 密钥 0EA18957BCB383D0 生成于未来的 218 天后(可能是因为时空扭曲或系时钟的问题)
gpg: 密钥 7F9199B8737F5089 生成于未来的 18755445 秒后(可能是因为时空扭曲或系统时钟的问题)
-----BEGIN PGP MESSAGE-----

owEBjQJy/ZANAwAIAX+Rmbhzf1CJAcsxYgBeC+EAMjAyMOW5tCAwOOaciCAwOeaX
pSDmmJ/mnJ/ml6UgMTY6MjA6NDcgQ1NUCokCSAQAAQgAMhYhBAM53KoR6gwazrNq
XH+Rmbhzf1CJBQJeC+EAFBxleGFtcGxlQGV4YW1wbGUub3JnAAoJEH+Rmbhzf1CJ
gJwP/RvJbQcJzjsIk5ZK9+qAyyurH2He1tsAv/1nM2ERO/6kPxsqMPlqU6U4edw3
X4p2Tf5eBNxgj+m1Zes76kFyrImNVwplBOTchmkxjBbbOpKBTIiMi8AhtT5HIS88
LZ7n8Fkyt4akoTqIKAQAsDUBN7wd6l9dzRWC5KU8BTdIoP3ttiyZUj2Jm8brEZ96
+UJrbkbzbVOE2QwfvQrkaV/qaNvCB2U2lXLnpZsa4J9WQ0Re0hJnM3qraSAyzRP1
O5IEOYUt2gUgwaSclDXlLtldAGplYLEKuIjK7x/qmIu6FBedvTrvslyrEW7h9Rvk
+mA1AoCxmqvrHmwk2if9zl5Dt6HSHcwgp38/7pvT8kJFoeHLaNKEhtEkPs8JWKVd
J/t6rttzAfEvBkIL8NwdVs3b50nSrP8SR8Mw7Kg0FZ2ZdVnvKzJ2LsJOIQZPa8II
uFLr96Yi64TMSnJn22vj104Smufl8WsstprxRjXC8i61eh1X/FfTLVcPRifHqZpW
3ioEMUGd4C3EuHWe65sjojR/AitpxdtqtIbt0XfDvvrDvTvAxHRq2lqNVi2bWhLb
En2YzXpuxBnnZzm8Cne9IkCWWX5zHGv4QUvlAV0V4QE4Oh1fhFouGI8JmTyHCdcX
llDLA96MfwNJUsrRFwOgMrF2KIIqHkQHiVqFxxBKiOPZsIMM
=QEQP
-----END PGP MESSAGE-----

就像这样,gpg忽略了时间错误,执行了签名操作,正常生成了签名。

不过, 在"时空扭曲"情况下生成的签名,gpg会拒绝验证该签名并报错“时间冲突”。除非签名验证时也使用了 --ignore-time-conflict 这一选项,否则签名不会被接受

另外, 使用此选项不会允许使用已过期密钥签名,密钥过期并不属于时间错误

更改摘要算法与散列(哈希)碰撞攻击

散列(哈希)碰撞攻击

正如上文所述,一个数字签名实际上并不是直接用公钥对整个文件加密,而是使用散列算法生成一个摘要。

而从一个签名所获取的信息也不是"签名者对原始文件aaaa签名,使用者能确认整个解密出来的文件就是原始文件",而是"签名者对具有sha1散列值为49559328482c1e9571b3a009172046bdd2fb6b7a的文件签名,签名者能够保证被签名文件的sha1就是这个"。而保证文件没有受到篡改的是通过

签名只保证了文件具有某一散列值,但是没有保证整个文件。将哈希值和具体文件联系在一起的,而让签名能够用于确认文件没有被篡改的就是 “很难找到两个输出哈希值相同但内容不同的输入文件” 这样一个优秀散列值所必备的性质。

但是,文件散列值终究不是文件本身,对散列值进行碰撞,仍然是一种可能的攻击方案。


如上图所示,散列值碰撞就是指,给定一个已有散列值,尝试生成一个文件散列值为指定散列值的文件内容(往往这个被篡改的内容还要有意义,毕竟一团乱码是人都能知道这文件有问题)。然后将篡改过的文件和

一个优秀的散列函数能让此步骤所需要消耗的时间不可接受。(比较差的sha1在目前都能做到需要100个显卡持续计算一年才能碰撞出1个3)

GPG默认使用的散列函数是sha256,这在目前足够安全。不过,如果对安全有更高要求的话,可以在签名时指定更安全的散列函数。

GPG指定签名时使用的摘要(散列)算法

如前节所述,gpg所默认使用的签名摘要算法 sha256 ,以现在的技术来说足够安全。

不过,有些时候可能为了更安全或者兼容性之类的原因,会有使用其他散列值的需求,这时可以要求gpg更改所使用的散列值。

OpenPGP标准提供了 --personal-digest-preferences 4选项可以用于更改默认签名时使用的散列算法。这一选项会将摘要首选算法设置为指定散列值算法(列表),此选项会在使用前检查所指定的散列值是否兼容OpenPGP标准,并会使用所有收件人都能使用的摘要算法,故而能获得较好的兼容性。可以设置该选项为 none 来不使用偏好设置。

可以使用 gpg --version 命令来查看gpg所支持的所有散列算法

1
gpg --version
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
gpg (GnuPG) 2.2.21
libgcrypt 1.8.6
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 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: /home/tntwiki/.gnupg
支持的算法:
公钥: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
密文: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
    CAMELLIA128, CAMELLIA192, CAMELLIA256
散列: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
压缩:  不压缩, ZIP, ZLIB, BZIP2

如上所示,当前所使用的gpg版本支持 SHA1、RIPEMD160、SHA256、SHA384、SHA512、SHA224 这六种散列算法(虽然md5实际上也是支持的,不过现在md5已经不能对抗散列碰撞攻击,不能保证签名有效性)。

1
2
echo xxx|gpg -u example --clear-sign --personal-digest-preferences sha512|grep "Hash" -C 1
echo "…………以下省略…………"
1
2
3
4
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

…………以下省略…………

如上所示,上述命令会使用 sha512 散列算法来生成散列摘要。

另外,gpg还支持使用 --digest-algo 2选项来强制指定使用的散列算法,不过不建议使用。因为这一选项允许使用者违反OpenPGP标准,并且不会检查指定的接收人是否支持使用的摘要算法,因而可能导致兼容性问题。

验证签名,并提取原始文件

当从种种途径获取到其他人的签名以后,就可以使用gpg软件验证这一签名的有效性了。

gpg使用 --verify 选项来验证签名。可以指定签名文件进行检查,也能从管道接收签名数据进行验证。

1
2
3
4
5
6
7
8
#生成测试用签名
echo 验证签名> /tmp/gpg_sign.txt
gpg -u example -s /tmp/gpg_sign.txt
echo "-----指定签名文件验证签名-----"
gpg --verify /tmp/gpg_sign.txt.gpg 2>&1
echo "-----gpg也可以从管道获取签名数据-----"
echo "2020年,新签名"|gpg -u example --sign -a|gpg --verify 2>&1
echo -n ""
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-----指定签名文件验证签名-----
gpg: 签名建立于 2020年08月10日 星期一 20时10分29秒 CST
gpg:               使用 RSA 密钥 25EA761F08C41F7B22FA94220EA18957BCB383D0
gpg:                签发者 "[email protected]"
gpg: 完好的签名,来自于 “example (仅用于示例) <[email protected]>” [绝对]
-----从管道获取签名数据-----
gpg: 签名建立于 2020年08月10日 星期一 20时14分46秒 CST
gpg:               使用 RSA 密钥 25EA761F08C41F7B22FA94220EA18957BCB383D0
gpg:                签发者 "[email protected]"
gpg: 完好的签名,来自于 “example (仅用于示例) <[email protected]>” [绝对]

验证分离签名则一般需要输入两个选项。其中第一个选项则是gpg生成的签名文件,第二个则是原始文件。一般情况下,只提供分离签名的签名文件,gpg能从文件名推测出原始文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
echo "这是分离签名原始文件" >/tmp/detach.txt
#创建一个测试用分离签名
gpg -u example --yes -b  /tmp/detach.txt 2>&1 
echo "-----检查分离签名-----"
gpg --verify /tmp/detach.txt.sig /tmp/detach.txt 2>&1
echo "-----只提供签名文件则会假定原始文件在去掉后戳名的位置-----"
gpg --verify /tmp/detach.txt.sig  2>&1
echo "-----不过,能省略,但是不能颠倒输入顺序,包含签名数据的文件必须在第一个-----"
gpg --verify /tmp/detach.txt  /tmp/detach.txt.sig 2>&1
echo -n ""
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
-----检查分离签名-----
gpg: 签名建立于 2020年08月10日 星期一 20时05分56秒 CST
gpg:               使用 RSA 密钥 25EA761F08C41F7B22FA94220EA18957BCB383D0
gpg:                签发者 "[email protected]"
gpg: 完好的签名,来自于 “example (仅用于示例) <[email protected]>” [绝对]
-----只提供签名文件则会假定原始文件在去掉后戳名的位置-----
gpg: 假定被签名的数据在‘/tmp/detach.txt’
gpg: 签名建立于 2020年08月10日 星期一 20时05分56秒 CST
gpg:               使用 RSA 密钥 25EA761F08C41F7B22FA94220EA18957BCB383D0
gpg:                签发者 "[email protected]"
gpg: 完好的签名,来自于 “example (仅用于示例) <[email protected]>” [绝对]
-----不过,能省略,但是不能颠倒输入顺序,签名文件必须在第一个-----
gpg: 找不到有效的 OpenPGP 数据。
gpg: 签名无法被验证。
请记住签名文件(.sig 或 .asc)应该是
在命令行中第一个给定的文件。

如果要从合并的签名文件中提取原始文件,可以使用 --decrypt 选项(可简写为 -d)。该选项也用于解密文件,详情可参阅下章。

1
2
3
4
#生成测试用签名
echo "从签名流中
提取原始数据"|gpg -u example --sign -a|gpg -d  2>&1
echo -n ""
1
2
3
4
5
6
从签名流中
提取原始数据
gpg: 签名建立于 2020年08月10日 星期一 21时41分16秒 CST
gpg:               使用 RSA 密钥 25EA761F08C41F7B22FA94220EA18957BCB383D0
gpg:                签发者 "[email protected]"
gpg: 完好的签名,来自于 “example (仅用于示例) <[email protected]>” [绝对]

就像这样,提取出签名文件所嵌入的原始数据,并同时验证了签名。

指定公钥文件验证签名

在验证签名时,gpg会从当前密钥环查找公钥,但是如果当前密钥环没有所需要的公钥,则会报错。(使用 --homedir 选项能够指定gpg所的使用的gpg密钥环目录,此目录默认为 ~/.gnupg)

1
2
3
4
5
mkdir -p -m 700 /tmp/test_gpg
#生成测试用签名
echo "签名验证"|gpg -u example --sign -a|gpg --homedir=/tmp/test_gpg --verify 2>&1
rm /tmp/test_gpg -r
echo -n ""
1
2
3
4
5
gpg: 钥匙箱‘/tmp/test_gpg/pubring.kbx’已创建
gpg: 签名建立于 2020年08月10日 星期一 20时26分17秒 CST
gpg:               使用 RSA 密钥 25EA761F08C41F7B22FA94220EA18957BCB383D0
gpg:                签发者 "[email protected]"
gpg: 无法检查签名:没有公钥

如上所示,临时指定了一个gpg密钥环位置,这个密钥环位置没有所解密签名所需要的公钥导致签名验证出错。

一般情况下,在没有公钥的话,可以在签名验证前使用 --import 将密钥导入密钥环。

不过,某些时候并不想这么做,这时可以使用 --keyring 5选项来指定公钥文件验证。

gpg的 --keyring 选项会将指定文件添加到当前密钥环中,该选项会要求一个文件名。不过,此选项对文件的解析相对比较奇怪——如果文件名中,不包含"/“则会假定该文件在GnuPG主目录(--homedir 选项可以指定,默认为 ~/.gnupg/)中。

另外,需要注意的是, --keyring 选项 不支持 ascii格式的公钥文件。

1
2
3
4
5
6
gpg --export -u example >/tmp/example.pub
mkdir -p -m 700 /tmp/test_gpg
#生成测试用签名
echo "签名验证"|gpg -u example --sign -a|gpg --homedir=/tmp/test_gpg --verify --keyring /tmp/example.pub 2>&1
rm /tmp/test_gpg -r
echo -n ""
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
gpg: 钥匙箱‘/tmp/test_gpg/pubring.kbx’已创建
gpg: 签名建立于 2020年08月10日 星期一 21时33分12秒 CST
gpg:               使用 RSA 密钥 25EA761F08C41F7B22FA94220EA18957BCB383D0
gpg:                签发者 "[email protected]"
gpg: /tmp/test_gpg/trustdb.gpg:建立了信任度数据库
gpg: 完好的签名,来自于 “example (仅用于示例) <[email protected]>” [未知]
gpg: 警告:此密钥未被受信任签名认证!
gpg:       没有证据表明此签名属于其声称的所有者。
主密钥指纹: 0339 DCAA 11EA 0C1A CEB3  6A5C 7F91 99B8 737F 5089
     子密钥指纹: 25EA 761F 08C4 1F7B 22FA  9422 0EA1 8957 BCB3 83D0

就像这样,成功验证了签名。(关于 没有证据表明此签名属于其声称的所有者 这一提示,可以参阅下章。)

1
2
3
4
5
6
gpg --export -a -u example >/tmp/example.pub.asc
mkdir -p -m 700 /tmp/test_gpg
#生成测试用签名
echo "签名验证"|gpg -u example --sign -a|gpg --homedir=/tmp/test_gpg --verify --keyring /tmp/example.pub.asc 2>&1
rm /tmp/test_gpg -r
echo -n ""
1
2
3
4
5
6
7
8
9
gpg: 钥匙箱‘/tmp/test_gpg/pubring.kbx’已创建
gpg: 签名建立于 2020年08月10日 星期一 21时46分11秒 CST
gpg:               使用 RSA 密钥 25EA761F08C41F7B22FA94220EA18957BCB383D0
gpg:                签发者 "[email protected]"
gpg: [don't know]: invalid packet (ctb=2d)
gpg: keydb_search failed: 无效的数据包
gpg: [don't know]: invalid packet (ctb=2d)
gpg: keydb_search failed: 无效的数据包
gpg: 无法检查签名:没有公钥

但是,试图使用 ascii 格式公钥时,则会报错。这也算是一个缺点吧?

签名的意义与对证书信任的前提

对证书的信任

一个有效的签名,所能证明的,只是该签名所对应的证书(私钥)的持有者曾经对具有某一散列值的文件进行过签名的行为。但是,证书和人的联系,不能通过证书自身来证明。并且,签名这个动作的意义,有时也需要人为赋予。

证书真正的持有者,和所认为的持有者可能是毫无关系两个人。毕竟,任何人都能够以某一特定用户标识来生成一份新证书,来声称这张新证书属于谁。

因此,为了安全,在导入证书以及验证签名时,有必要仔细核对证书是否确实是所认为的证书持有者所有,而不是某个第三方劫持者所伪造的证书。

使用 --import 导入公钥至gpg目录后,应当使用 --fingerprint 选项列出密钥指纹,并将其与联系所认为的私钥持有者那儿获取的证书指纹进行对比(无论是官网/电话/电子邮件/线下会面还是从哪获取的公钥,总之,渠道越多越好),以避免错误的相信恶意劫持者所提供的证书。

确认证书所有者是持有者本人后,可以使用 --edit-key 选单的trust命令来设置本地信任级别,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
gpg --homedir=t --edit-key example
gpg (GnuPG) 2.2.21; Copyright (C) 2020 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  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2021-07-31  可用于:SCEA
     信任度:未知        有效性:未知
sub  rsa4096/0EA18957BCB383D0
     创建于:2020-08-06  有效至:2020-11-14  可用于:SEA 
[ 未知 ] (1). example (仅用于示例) <[email protected]>

gpg> trust 
pub  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2021-07-31  可用于:SCEA
     信任度:未知        有效性:未知
sub  rsa4096/0EA18957BCB383D0
     创建于:2020-08-06  有效至:2020-11-14  可用于:SEA 
[ 未知 ] (1). example (仅用于示例) <[email protected]>

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

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

您的决定是什么? 3

pub  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2021-07-31  可用于:SCEA
     信任度:勉强        有效性:未知
sub  rsa4096/0EA18957BCB383D0
     创建于:2020-08-06  有效至:2020-11-14  可用于:SEA 
[ 未知 ] (1). example (仅用于示例) <[email protected]>

gpg> 

1=未知、2=永不、3=勉强、4=完全、5=绝对(虽然我觉得"永不信任"的信任级别应该比“未知”信任级别要低就是了……)。这是gpg对信任级别的描述。

不过,至于这些信任级别所对应的现实意义,依旧由使用者决定。比如说,网友(通过网络分发和验证公钥)是分配“永不”还是“勉强”,面对面验证了身份证件和证书所有权是分配“勉强”还是“完全”,家人是分配“完全”还是“绝对”,朋友是分配“完全”还是“勉强”……这些均由使用者决定。

关于使用主证书签名认证其他证书相关的信息,此处不做介绍。可参见下文GPG公钥认证

签名之意义

用不同的证书为不同的被签名文件签名所蕴含的意义,也各不相同。比如说——

在本文中作为例子而创建的证书 example 所签署的签名,就和用纸笔在白纸上练习签名一般,除了证明签过名外,毫无任何意义和可信度。

而软件作者(们)在软件发布时为软件签名,则意味着这一软件的这一版本被软件作者所确认,确认发布。

pacman包管理器安装软件时,也会验证gpg签名是否有效。·而此处软件包仓库管理者签发签名则意味着包管理仓库管理者会确认软件包经过测试而保证正常以及可用。

而使用代表自己的证书对协议文本签署数字签名,除了证明"我看过这一协议"外,往往还带有"我会遵守这一协议要求"的含义。就和在纸质的协议文书上签字一般。

因此,进行数字签名,尤其是使用自己的数字签名确认协议的时候, 应当谨慎确认和慎重对待 。如果不慎重对待,可能会使你,以及所使用的证书 因缺乏信用而不再被其他人信任

一些其他选项

忽略弱摘要签名

默认情况下,gpg会拒绝使用弱摘要算法所签发的签名,并报错"无效的摘要算法”。目前,而言,md5算法已经被认为是弱摘要算法而被拒绝使用。

1
2
3
echo "使用md5摘要算法所生成的弱摘要签名"|gpg -u example --personal-digest-preferences md5 -s|gpg -d 2>&1
#--allow-weak-digest-algos
echo -n ""
1
2
3
4
5
6
使用md5摘要算法所生成的弱摘要签名
gpg: 签名建立于 2020年08月11日 星期二 08时58分47秒 CST
gpg:               使用 RSA 密钥 25EA761F08C41F7B22FA94220EA18957BCB383D0
gpg:                签发者 "[email protected]"
gpg: 注意:使用 MD5 算法的签名已被拒绝
gpg: 无法检查签名:无效的摘要算法

就像这样,使用md5算法的签名被拒绝。不过,可能有时候,需要验证使用md5摘要的弱签名,这时可以要求gpg禁用弱摘要算法检查。

gpg提供了 --allow-weak-digest-algos 2选项来禁用弱摘要签名检查,使用此选项能够忽略弱摘要错误。

注意,弱摘要的抗碰撞攻击的能力很弱,禁用弱摘要检查会提高被散列碰撞攻击的概率,应当避免使用此选项

1
2
3
echo "使用md5摘要算法所生成的弱摘要签名"|gpg -u example --personal-digest-preferences md5 -s|gpg -d --allow-weak-digest-algos 2>&1
#--allow-weak-digest-algos
echo -n ""
1
2
3
4
5
使用md5摘要算法所生成的弱摘要签名
gpg: 签名建立于 2020年08月11日 星期二 08时51分29秒 CST
gpg:               使用 RSA 密钥 25EA761F08C41F7B22FA94220EA18957BCB383D0
gpg:                签发者 "[email protected]"
gpg: 完好的签名,来自于 “example (仅用于示例) <[email protected]>” [绝对]

就像这样,gpg忽略了弱摘要问题,并对签名进行了检查。

指定其他摘要算法为弱摘要

当前,gpg默认只会将 MD5 摘要算法i视为弱摘要算法。不过,为了更安全,可以手动指定其他摘要算法为弱摘要。这些使用弱摘要算法的签名将会被拒绝信任。

可以使用 --weak-digest 2选项指定其他弱摘要算法。

1
2
3
4
echo "-----使用sha1摘要算法所生成的弱摘要签名-----"|gpg -u example --personal-digest-preferences sha1 -s|gpg -d --weak-digest sha1 2>&1
#--allow-weak-digest-algos
echo "----使用sha256摘要算法所生成的弱摘要签名----"|gpg -u example --personal-digest-preferences sha256 -s|gpg -d --weak-digest sha256 2>&1
echo -n ""
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
-----使用sha1摘要算法所生成的弱摘要签名-----
gpg: 签名建立于 2020年08月11日 星期二 09时04分18秒 CST
gpg:               使用 RSA 密钥 25EA761F08C41F7B22FA94220EA18957BCB383D0
gpg:                签发者 "[email protected]"
gpg: 注意:使用 SHA1 算法的签名已被拒绝
gpg: 无法检查签名:无效的摘要算法
----使用sha256摘要算法所生成的弱摘要签名----
gpg: 签名建立于 2020年08月11日 星期二 09时04分18秒 CST
gpg:               使用 RSA 密钥 25EA761F08C41F7B22FA94220EA18957BCB383D0
gpg:                签发者 "[email protected]"
gpg: 注意:使用 SHA256 算法的签名已被拒绝
gpg: 无法检查签名:无效的摘要算法

就像这样,上面这两条命令分别将sha1算法和sha256算法视为弱摘要算法,并拒绝由对应算法所生成的签名。

忽略“时空扭曲”而导致的签名检查错误

正如gpg忽略时间戳错误一节所述,gpg验证签名时,也会对签名的时间戳进行检查,并会拒绝验证具有异常时间戳的签名。就像下面这样

1
2
date|gpg -a --faked-system-time $(date -d "2020-1-1" +%Y%m%dT%H%M%S) --ignore-time-conflict -s -u example |gpg --verify 2>&1
(echo -n >&2) 2>&1
1
2
3
4
5
gpg: 签名建立于 2020年01月01日 星期三 08时00分00秒 CST
gpg:               使用 RSA 密钥 0339DCAA11EA0C1ACEB36A5C7F9199B8737F5089
gpg:                签发者 "[email protected]"
gpg: 公钥 7F9199B8737F5089 在其签名之后的 217 天生成
gpg: 无法检查签名:时间冲突

虽然一般来说,时间戳错误,是不可信的。不过,一张有效但时间冲突的签名,至少能够证明,签发签名的私钥,曾经在某一时间对文件进行签名(没有私钥不可能签发签名,哪怕是具有时间戳错误的也不可能)。

这时可以使用 --ignore-time-conflict 2选项要求gpg忽略时间戳错误, 不过,尽管一份有效的签名能够证明签名是由私钥签发的,但依旧不建议使用。一张存在时间戳错误的签名可能说明签名可信度不高,应当要求签名者重新以正确签名时间重新签发签名

1
2
date|gpg -a --faked-system-time $(date -d "2020-1-1" +%Y%m%dT%H%M%S) --ignore-time-conflict -s -u example |gpg --ignore-time-conflict --verify 2>&1
(echo -n >&2) 2>&1
1
2
3
4
5
gpg: 签名建立于 2020年01月01日 星期三 08时00分00秒 CST
gpg:               使用 RSA 密钥 0339DCAA11EA0C1ACEB36A5C7F9199B8737F5089
gpg:                签发者 "[email protected]"
gpg: 公钥 7F9199B8737F5089 在其签名之后的 217 天生成
gpg: 完好的签名,来自于 “example (仅用于示例) <[email protected]>” [绝对]

《中华人民共和国电子签名法》对于数字签名发送时间的规定,可供参考。

1
2
3
  [第十一条]数据电文进入发件人控制之外的某个信息系统的时间,视为该数据电文的发送时间。
 收件人指定特定系统接收数据电文的,数据电文进入该特定系统的时间,视为该数据电文的接收时间;未指定特定系统的,数据电文进入收件人的任何系统的首次时间,视为该数据电文的接收时间。
 当事人对数据电文的发送时间、接收时间另有约定的,从其约定。

加密与解密[E]

非对称加密简介

正如非对称加密体系简述一章所述,非对称加密就是一种知道加密方法,无法通过逆操作(或者逆操作成本过高)来解密密文的一系列加密算法的总称。

常见的非对称加密算法有RSA、ECC等。通过非对称加密,能够保证收件方公钥是可靠的情况下,保证发送的信息不被任何第三方窃取监听。

使用非对称加密讯息的大致流程

使用非对称加密进行通讯的流程大致如下所示。假设通讯双方A(lice)和B(ob)需要使用不可靠线路避免被第三方监听,这时使用非对称加密进行加密通讯的步骤大致如下

中间人攻击

不过,如果在公钥交换的时候就出了差错,非对称加密将无法保证加密通讯的安全性

就像这样,Alice和Bob错误的将中间人控制的密钥当成了对方所使用的公钥,导致双方所有的通讯都能被中间人所监听。

GPG非对称加密与解密的简单使用

简单加密

gpg命令行下,使用 --encrypt 1(可简写为"-e")来进行非对称加密。这个选项将会使用一个到多个公钥,把从标准输入输入的数据或者指定的文件加密并输出/保存。

如果没有设置收件人(公钥),单独使用此选项将会进入一个输入模式,要求输入收件人。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
gpg -e 待加密文件.pdf
您没有指定用户标识。(您可以在命令行中用“-r”指定)

当前接收者:

输入用户标识。以空白行结束: example
当前接收者:
rsa4096/0EA18957BCB383D0 2020-08-06 "example (仅用于示例) <[email protected]>"

输入用户标识。以空白行结束: 

像这样,将会使用example的公钥组中的子密钥[0EA18957BCB383D0]来加密文件"待加密文件.pdf",并会将加密过的文件保存到"待加密文件.pdf.gpg"。使用公钥加密不需要输入任何密码。

也可以使用 --recipient 6(可简写为 -r)指定收件人ID(或者密钥标识ID),并且, ~-e~选项可以从管道接受待加密的文本。

1
echo "加密给example"|gpg -a -r example -e
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
-----BEGIN PGP MESSAGE-----

hQIMAw6hiVe8s4PQAQ//Rx6tIGh/ZDiYCVM3nx25IlU5mWVNGo2fvGjUgOyQVZOq
9Cg0/ExUMnWfWoi64ycRotMLCtlPsD26JalDLlgykqWDp1/30/NzfzazKLowWTmY
riUqlMsp7Q31vHcoVdi8D6CCx4f3PI+yYrrMcLPsD/P8TZc962LvQRcD3kLGvnba
qnC6xCG2W0XVcCa6c/DZwN/nVaLClmPpFpu2H0h63sG2QsrsvC+2WvzjI6nLZf0R
mebycZJCVOCliGCnZ//oy0ztRTfsN+kph4WYmejZgGNKuKrkE+QXaVbTOclylGVW
1zIhb0OKrpKmzUN0gg8evfxmCskWRQm6tNfSRCMBe8z/FoVVJxcKORFE2Rp4MpM1
1j7LYoxnc10TgMMktEwfBL0u27GFPpfBD4w1+70l43Bokrt6ymfWX+P5NXzJeh9M
Z0nWYOJVS6F/8A2dP9hDQpxoQlQw3AnmXHEkJOM8I5pjSfAQROOwSGE7sfKsEvjD
fZXe/b0fCie9Dnc6RJ+3m5T8+kEsVG8Ic6DXcQ/kT0N1h9xOJvE4WuW3S4zzUtFz
nieD9oBRPAX10ByVq5YrTaIwkXt/OjuCZy2ovu1RS1W9knDptAvr8AMNhrZaUvPZ
OmHydqXgR+r1GAtMbZM+uQ7RCWvXG+SWtbBhEk8iKQboP+Z03DuFMQcq7TRnCMTS
TQE+j51/ShzOP6fjO2Ljrohm11qZKY7Tv5clDuRfYqiI17MsReebPKVRk+ZzESLG
IW6iIxrgMgQsd06MYHt8wUo08jNN6DR8IwuGhnin
=TnQP
-----END PGP MESSAGE-----

像这样,指定所使用收件人(公钥)example生成一个密文,这一密文就只能被对应的私钥的持有者所解密。

简单解密

gpg使用 --decrypt 1选项(可简写为 -d)来进行数据解密,这一选项将会从指定的加密文件(或标准输入)中解密数据。如果输入的文件已被签名,则签名也会自动验证。

1
echo -e "使用私钥解密\n这样子的一条密文"|gpg -r example -e|gpg -d 2>&1
1
2
3
4
gpg: 由 4096 位的 RSA 密钥加密,标识为 0EA18957BCB383D0,生成于 2020-08-06
      “example (仅用于示例) <[email protected]>”
使用私钥解密
这样子的一条密文

如上所示,gpg完成了对密文的解密。

关于子密钥相关的注意事项

需要注意的是,如果用户ID所匹配的密钥组中存在多个具有"E"(加密/解密)功能的(子)密钥,那么使用ID加密时只能使用最近创建的具有"E"功能的子密钥解密。如果本地不存在对应的(子)私钥,即使有主私钥或其他子私钥也无法解密。

因此,如果不同的设备存放的解密子密钥不同的话,在收到别人的加密文件时,解密可能会很麻烦。

所以,可以要求发件方使用指定密钥ID来进行加密。(虽然发件人不一定会听,因为这会增加发件方的步骤。)

1
2
3
gpg -K --with-colons example |grep -E 'sec|ssb'
echo -e "|||||输出密钥ID信息|||||\n"
echo "此处使用example的主私钥而非子密钥进行加密,主私钥的密钥ID为[7F9199B8737F5089]"|gpg -a -e -r "7F9199B8737F5089"|gpg -d 2>&1
1
2
3
4
5
6
7
sec:u:4096:1:7F9199B8737F5089:1596592245:1627696245::u:::escaESCA:::+:::23::0:
ssb:u:4096:1:0EA18957BCB383D0:1596679712:1605319712:::::esa:::+:::23:
|||||输出密钥ID信息|||||

gpg: 由 4096 位的 RSA 密钥加密,标识为 0EA18957BCB383D0,生成于 2020-08-06
      “example (仅用于示例) <[email protected]>”
此处使用example的主私钥进行加密,主私钥的密钥ID为[7F9199B8737F5089]

就像这样,GPG在这里使用了主密钥 7F9199B8737F5089 而非子密钥 0EA18957BCB383D0 ,使用其他特定密钥加密也可以此类推。

不过,尽管可以如此要求发件人使用特定密钥加密,不过,默认所使用的加密(子)密钥最好应该能够保存在不影响安全性的情况下最方便的位置。

一些不常用的选项

设置公钥文件用作收件人

有时候,拥有一个人的公钥文件,会想使用公钥文件加密一段信息给他,但是不想导入公钥至文件夹。

这时,可以用 --recipient-file6(可简写为 -f)选项来指定收件人的公钥文件进行加密。

1
2
gpg --yes  -o /tmp/example.pub --export example
echo "指定公钥文件加密"|gpg -f /tmp/example.pub -e|gpg -d 2>&1
1
2
3
gpg: 由 4096 位的 RSA 密钥加密,标识为 0EA18957BCB383D0,生成于 2020-08-06
      “example (仅用于示例) <[email protected]>”
指定公钥文件加密

上面那条命令将会使用收件人的公钥文件 /tmp/example.pub 进行加密。

密文中隐藏收件人密钥

gpg支持在生成的密文中(隐藏)移除收件人的密钥ID,这能够在一定程度上隐藏接触者,并在一定程度上能够增大流量分析的有限对策。

不过效果不大。这样会使收件人需要尝试其密钥链所有可用私钥来试图解密,而延长收件人的解密时间。而能解密信息的其他人,往往也能通过社会工程学能够推测出一个密文的可能收件人2

gpg选项 --hidden-recipient 类似于 -r 能够指定一个密钥收件人ID,也可简写为 -R 来在加密文件中隐藏收件人密钥。

1
echo -e "example是匿名接收者哦!测试用密文"|gpg -R example -e|gpg -d 2>&1 
1
2
3
4
5
6
7
8
gpg: selecting card failed: 没有那个设备
gpg: 匿名接收者;正在尝试使用私钥 057D38C6B1CA2C0A ……
gpg: 匿名接收者;正在尝试使用私钥 36FC5D0C78D778AE ……
gpg: 匿名接收者;正在尝试使用私钥 7F9199B8737F5089 ……
gpg: 匿名接收者;正在尝试使用私钥 0EA18957BCB383D0 ……
gpg: 很好,我们就是匿名接收者。
gpg: 由 RSA 密钥加密、密钥号为 0000000000000000
example是匿名接收者哦!测试用密文

(解密时输入了三次密码……所以匿名收件人这种功能很麻烦的对吧)

gpg选项 --hidden-recipient-file 类似于 -f 能指定一个公钥文件作为收件人,也可简写为 -F 来在加密文件中隐藏收件人密钥。

同时,选项 --throw-keyids 2可以要求gpg不将任何密钥ID放入密文中,等同于对所有收件人指定 --hidden-recipient 选项。

在匿名接收中,指定尝试解密的密钥

如果大致能够推测出匿名接收的时加密所用的密钥文件,这时,可以指定优先尝试的私钥来减少试错时间(而不是a-z慢慢试)。

可以使用 --try-secret-key 选项来指定优先使用的密钥。

1
echo -e "example是匿名接收者哦!测试用密文"|gpg -R example -e|gpg -d --try-secret-key example 2>&1 
1
2
3
4
5
gpg: 匿名接收者;正在尝试使用私钥 7F9199B8737F5089 ……
gpg: 匿名接收者;正在尝试使用私钥 0EA18957BCB383D0 ……
gpg: 很好,我们就是匿名接收者。
gpg: 由 RSA 密钥加密、密钥号为 0000000000000000
example是匿名接收者哦!测试用密文

忽略密文中的接收人

解密时使用 --try-all-secrets 6选项会忽略存储在密文中的收件人,而会依次尝试所有可用的密钥进行解密。此选项会强制使用对匿名收件人使用的行为,在密文中包含伪造的密钥ID的情况下可能派的上用场。

1
2
echo -e "谁都不能解密"|gpg -f /tmp/shattered/VeraCrypt_PGP_public_key.asc -e|gpg -d --try-all-secrets 2>&1 
echo -n ""
1
2
3
4
5
6
7
8
gpg: selecting card failed: 没有那个设备
gpg: 匿名接收者;正在尝试使用私钥 057D38C6B1CA2C0A ……
gpg: 匿名接收者;正在尝试使用私钥 36FC5D0C78D778AE ……
gpg: 匿名接收者;正在尝试使用私钥 7F9199B8737F5089 ……
gpg: 匿名接收者;正在尝试使用私钥 0EA18957BCB383D0 ……
gpg: 由 4096 位的 RSA 密钥加密,标识为 200B5A9D26878A32,生成于 2018-09-11
      “VeraCrypt Team (2018 - Supersedes Key ID=0x54DDD393) <[email protected]>”
gpg: 解密失败:没有秘匙

就像上面这样子,把可用密钥强制都试了一遍。

从其他途径指定密码

在脚本中,可能希望能够自动输入密码来签名或解密文件,这时,可以从其他途径来指定密码解密。

gpg除了默认的手动密码输入方式外,还提供了几种额外的指定密码的方式。此处介绍两种。

使用 --passphrase 2选项能够指定一个字符串作为密码。 注意,在多用户系统中,任何能够查看系统进程的用户都可能获取该选项所指定的密码,如果可能,应当避免使用 。gpg版本2.0以上,需要同时给出 --batch5(启用批处理模式),gpg版本2.1以上还需要将 --pinentry-mode2(密码输入模式?)设置为 loopback 。否则会拒绝 --passphrase 选项所指定的密码。

1
echo -e "批处理模式密码自动输入"|gpg -s -u example --batch --pinentry-mode loopback --passphrase "test"|gpg --verify 2>&1
1
2
3
4
gpg: 签名建立于 2020年08月12日 星期三 00时10分56秒 CST
gpg:               使用 RSA 密钥 25EA761F08C41F7B22FA94220EA18957BCB383D0
gpg:                签发者 "[email protected]"
gpg: 完好的签名,来自于 “example (仅用于示例) <[email protected]>” [绝对]

就像这样,成功完成了密码自动输入,自动化签名。(反正示例用私钥,没必要弄多强的密码)

还有一个类似的选项是 --passphrase-file ,类似于 --passphrase 也需要启用批处理模式,并将 --pinentry-mode 设置为 loopback 。这一选项将会从指定文件读取第一行作为密码输入,其他行将会被忽略。 很明显的,如果有其他用户拥有此文件的读写权限,则密码短语的安全性不能得到保证,也应当避免使用

对称加密简介

对称加密和非对称加密相反,知道加密方法同时也会知道解密方法。对称加密的安全性依赖于加密密钥的保密性。

对称加密能够保证在密码不泄露的情况下,破解加密文件非常困难。相对于非对称加密来说,对称加密的解密速度很快,比较适合大量数据的加密传递。

但是,对称加密的密码必须得到安全传递,否则任何知道密文和密码的人都能进行解密。

gpg可以使用对称加密算法加密文件或数据,其默认使用AES对称加密算法进行对称加密。

GPG对称加密的简单使用

对称加密与解密

gpg使用 --symmetric 1(可简写为 -c)来进行对称加密操作。

使用对称加密会要求输入一个密码用于密钥加密,该密码也会用于对称解密。因此,请小心记录和传递对称加密密码,并且应当避免设置弱密码以避免密码字典攻击。

1
2
3
echo -e "对称加密"|gpg -a -c --batch --passphrase "test"

echo -e "对称加密"|gpg -c --batch --passphrase "test"|gpg -d --passphrase "test" --batch 2>&1
1
2
3
4
5
6
7
8
9
-----BEGIN PGP MESSAGE-----

jA0EBwMCmr+vGx5TfYnf0kMB8OMSkjlRcxyeFsuxUe0Ysd/569HhoEdcI4gQaeJM
rG6WLK23FA+uL+p6PnmluwVS3bjmtnJCComyTxhlz+o52Je5
=zzCq
-----END PGP MESSAGE-----
gpg: AES.CFB encrypted data
gpg: 以 1 个密码加密
对称加密

上述命令,会使用密码"test"来加密和解密数据(不过这个密码估计很容易被密码字典攻击爆破)。如果不指定密码的话,会弹出一个密码输入界面要求密码输入。(感觉对称加密的密文大小比非对称加密的密文要短呢)

在同一个人所控制的不同设备间偶尔传递文件时,使用对称加密很非常方便。(如果是偶尔进行这种加密的话,使用非对称加密会增加很多步骤,并且也不会增加安全性,原因请参阅下节[混合加密])

指定对称加密算法

gpg支持使用很多的对称加密算法,可以使用 gpg --version 查看gpg所支持的对称加密算法

1
gpg --version|grep 密文 -A 1
1
2
密文: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
    CAMELLIA128, CAMELLIA192, CAMELLIA256

默认gpg会使用AES128算法。不过可能有时候希望能够使用其他对称算法进行加密。这时可以要求GPG使用其他对称加密算法。

可以使用 --personal-cipher-preferences 4选项来设置对称加密算法,这一选项会更改对称算法首选项为制定对称算法(列表)。此选项可以安全的覆盖收件方密钥首选算法,因为该选项会使用所有收件人都支持的对称加密算法。设置该选项为 none 可以忽略对称算法的偏好设置。

1
echo -e "---使用3DES算法进行对称加密此文本---"|gpg -c --batch --passphrase "test" --personal-cipher-preferences 3des |gpg -d --passphrase "test" --batch 2>&1
1
2
3
gpg: 3DES.CFB encrypted data
gpg: 以 1 个密码加密
---使用3DES算法进行对称加密此文本---

上面那条命令,使用了对称加密算法 3DES 进行加密。

另外,gpg还支持使用 --cipher-algo 2选项来更改对称加密算法,不过一般不建议使用,因为该选项会允许使用收件方不兼容的对称加密算法而引起兼容性问题。

混合加密与会话密钥

对称加密和非对称加密都有其优点和缺点。 对称加密 的解密效率高,适合大量数据传递,在密码没有泄露的情况下能够保证安全性,但是需要保证加密密钥在传输过程中不会泄密。而 非对称加密 ,解密效率差,不适合大量数据传递,但是知道加密方法(公钥)无法解密数据,除私钥持有者外谁都无法解密,而私钥由持有者保密,不参与密钥交换。

这样,可以将这两种加密方法组合起来,使用对称加密算法加密保护原始文件,然后用公钥加密保护对称加密密码。这种同时使用对称加密和非对称加密的方法叫做 混合加密 7,这种方法在一定程度上取得了解密效率和安全性的折中。

PGP和GPG都使用混合加密,被加密的文件会先用随机生成的密码进行对称加密,再使用收件人公钥加密密码。而接收者则会先使用私钥解密密码,然后再使用密码解密密文。这里随机生成的对称加密密钥一般会被称作会话密钥。

不过混合加密的安全性并不一定比单独使用对称或非对称加密要强,以破解难度较弱者为准。在PGP和GPG中,公钥密钥(非对称加密)可能是两者中攻击难度比较弱的那个。不过,幸运的是,如果攻击者能够解密会话密钥,则只能解密使用那一会话密钥加密的密文,想要解密其他密文必须重新进行攻击。

同时使用密码和公钥进行加密,以允许使用密码或私钥解密

gpg可以通过同时指定 -e (公钥加密)和 -c (密码加密)选项来启用私钥和密码加密。

1
2
echo "同时使用密码和公钥解密
并允许使用私钥或密码解密"|gpg -r example -c -e -a|gpg -d 2>&1
1
2
3
4
5
6
7
gpg: AES.CFB encrypted session key
gpg: 以 1 个密码加密
gpg: 由 4096 位的 RSA 密钥加密,标识为 0EA18957BCB383D0,生成于 2020-08-06
      “example (仅用于示例) <[email protected]>”
gpg: 公钥解密失败:操作已取消
同时使用密码和公钥解密
并允许使用私钥或密码解密

就像这样,输出的结果可以同时使用私钥或者密码进行解密。

输出会话密钥

有时候,可能需要允许第三方解密某一条其他人发来的加密文本。但是,不希望让私钥泄露而令所有能被该私钥解密的密文泄露。

这时,可以通过输出密文的会话密钥来实现这一点。GPG提供了 --show-session-key 2选项来允许输出会话密钥。

1
echo "显示一条加密消息的会话密钥"|gpg -r example -e -a|gpg --show-session-key 2>&1
1
2
3
4
5
gpg: 警告:没有提供命令。正在尝试猜测您的意图...
gpg: 由 4096 位的 RSA 密钥加密,标识为 0EA18957BCB383D0,生成于 2020-08-06
      “example (仅用于示例) <[email protected]>”
gpg: session key: '9:9220AFE4F9A9D725928A8409AFC79AA4139FF9EEFD8FDB2DF6E9B7C219BAB2D3'
显示一条加密消息的会话密钥

上面这条命令的输出中,以 gpg: session key 开头的就是这条密文的会话密钥了(看起来这里还没得到翻译)。 知晓该会话密钥的任何人都能够解密这一会话密钥所对应的密文

然后,你就可以将这里输出的会话密钥提供给你希望透露的第三方了。比如说收到一封加密的侮辱邮件,可以向邮件服务管理员提供会话密钥以允许向邮件管理员证明密文和具有侮辱性的明文的关联性,以允许邮件管理员对发件人采取措施。或者警方要求协助调查时,向警方提供会话密钥以允许警方解密犯罪者发来的密文。

(思考)希望所有人都不会遇到需要使用这一个选项的时候。

使用会话密钥解密

在获得会话密钥后,可以使用会话密钥对密文进行解密。

gpg可以使用 --override-session-key2来设置会话密钥来进行解密。

1
2
3
4
5
6
7
echo "-----BEGIN PGP MESSAGE-----

jA0ECQMC2fyxWuRR6S5g0k4Bgmo/8lrzGWNwjTs8nh8NNFlz3zDMC5B+7fLpWfVg
Tp86U/DofGAuBGe0bCvfNhxYS8SPK42gkc+Db53v8WdrfSW1HFZPfKCjtnIQjyA=
=Qerb
-----END PGP MESSAGE-----
"|gpg -d --override-session-key "9:5179D595C6DC29297E02870B939185B510F1651FFEA16701548190C8F92B48A4" 2>&1
1
2
3
gpg: AES256.CFB encrypted data
gpg: 以 1 个密码加密
使用会话密钥解密

就像这样,上面那条命令使用了会话密钥成功完成了解密。 注意,从选项传入会话密钥会让任何能够访问全局进程表的用户能够获取此会话密钥

gpg还提供有一个选项 --override-session-key-fd2这个选项会让GPG从指定的文件描述符中获取会话密钥。

因为目前对文件描述符这个概念并不了解,不做详细介绍(毕竟我自己现在都不清楚怎么用……)。故此处仅列举此选项的简单用法——从标准输入获取会话密钥。

1
2
3
4
5
6
7
echo "-----BEGIN PGP MESSAGE-----

jA0ECQMC2fyxWuRR6S5g0k4Bgmo/8lrzGWNwjTs8nh8NNFlz3zDMC5B+7fLpWfVg
Tp86U/DofGAuBGe0bCvfNhxYS8SPK42gkc+Db53v8WdrfSW1HFZPfKCjtnIQjyA=
=Qerb
-----END PGP MESSAGE-----" >/tmp/tmpFTGHBY.asc
echo "9:5179D595C6DC29297E02870B939185B510F1651FFEA16701548190C8F92B48A4"|gpg -d --override-session-key-fd 0  /tmp/tmpFTGHBY.asc 2>&1
1
2
3
4
gpg: DBG: seskey: 9:5179D595C6DC29297E02870B939185B510F1651FFEA16701548190C8F92B48A4
gpg: AES256.CFB encrypted data
gpg: 以 1 个密码加密
使用会话密钥解密

--override-session-key-fd 设置为0会让gpg从文件描述符0获取解密用会话密钥,而文件描述符0就是指从管道传入的标准输入。(1是标准输出,2是错误输出)。

加密并签名

gpg可以在加密的同时附上签名,这能够帮助收件人确认文件没有受到篡改,并且也会告诉收件人发件人具体是谁。

同时将 --sign (-s) 与 --encrypt (-e) 和/或 --symmetri (-c) 选项共同使用,将会在加密的同时对文件进行签名。带有签名的加密文件在使用 --decrypt (-d) 选项解密时将会同时对签名进行检查。

1
echo "同时使用签名和非对称加密"|gpg -u example -r example -e -s -a|gpg -d 2>&1
1
2
3
4
5
6
7
gpg: 由 4096 位的 RSA 密钥加密,标识为 0EA18957BCB383D0,生成于 2020-08-06
      “example (仅用于示例) <[email protected]>”
同时使用签名和非对称加密
gpg: 签名建立于 2020年08月12日 星期三 17时35分07秒 CST
gpg:               使用 RSA 密钥 25EA761F08C41F7B22FA94220EA18957BCB383D0
gpg:                签发者 "[email protected]"
gpg: 完好的签名,来自于 “example (仅用于示例) <[email protected]>” [绝对]

诺,就像这样子。

其他关于加密的一些选项

此处简单记录一下一些加密时可能有用的其他选项

设置使用的压缩算法

gpg在加密文件(或者生成合并签名)时默认会对文件进行压缩。

gpg支持很多压缩算法,可以通过 gpg --version 查看可用的压缩算法。

1
gpg --version|grep 压缩
1
压缩:  不压缩, ZIP, ZLIB, BZIP2

可以使用 --personal-compress-preferences 4选项来指定首选压缩算法(列表)。这一选项能够安全的覆盖默认压缩算法首选项,因为gpg只会使用所有收件人都能兼容的压缩算法。

1
echo "指定压缩算法为bzip2"|gpg -u example --sign --personal-compress-preferences bzip2 -a |gpg -d 2>&1
1
2
3
4
5
指定压缩算法为bzip2
gpg: 签名建立于 2020年08月12日 星期三 18时19分15秒 CST
gpg:               使用 RSA 密钥 25EA761F08C41F7B22FA94220EA18957BCB383D0
gpg:                签发者 "[email protected]"
gpg: 完好的签名,来自于 “example (仅用于示例) <[email protected]>” [绝对]

上述命令将会指定使用bzip2压缩算法来压缩数据。

另外,也可以使用 --compress-algo2选项来强制指定使用的压缩算法,不过这样可能会导致出现不兼容的情况。任何版本的PGP(商用软件)都不支持 zipnone 以外的任何压缩算法。

指定压缩级别

gpg也可以设置压缩级别,越大的压缩级别加/解密耗时越长,但是文件会尽可能的压缩的越小。

可以使用 -z 选项设置压缩级别,gpg的默认压缩级别是6级。

选项 --compress-level 会设置 zip 和 zlib 算法的压缩级别,而选项 --bzip2-compress-level 将设置 bzip2 算法的压缩级别,因为bzip2为每个附加的压缩级别增加了大量的内存消耗。

选项 -z 会同时设置这两者,如果压缩级别设置为0将会禁用压缩。

1
echo xxx|gpg -z 0 -u example --sign|gpg -d 2>&1
1
2
3
4
5
xxx
gpg: 签名建立于 2020年08月12日 星期三 18时55分32秒 CST
gpg:               使用 RSA 密钥 25EA761F08C41F7B22FA94220EA18957BCB383D0
gpg:                签发者 "[email protected]"
gpg: 完好的签名,来自于 “example (仅用于示例) <[email protected]>” [绝对]

上面这条命令指定gpg的压缩级别为0,因而禁用了压缩。

认证[C]与信任网络和密钥编辑与管理

证书授权中心(CA)与信任网络

上面介绍了非对称加密的特性,非对称加密能够保证加密无法被公钥所对应的私钥持有者以外的人解密。

不过,这样就带来了一个新的问题,即, 如何保证拿到的公钥所对应的私钥的持有者就是我所认为的私钥持有者,而不是某个第三方伪造的?

理论上,不存在一个 可靠的、不事先协商的公钥交换机制 ,因为在公钥交换之前,加密通讯还未开始,交换过程自然没有保障。

为了解决公钥交换的问题,有几种主要的公钥分发验证方式。此处介绍其中两种。

集中式证书授权验证与证书授权中心

集中式证书授权验证是一种比较主要的证书授权验证证书的方式。

在这一授权方式中,有一种叫做证书授权中心(CA)的权威机构,这类机构负责证书的签发和验证。

如果想要让别人信任一张证书,则需要去找证书授权中心进行验证(很多需要收费),证书授权中心验证通过后,就会使用证书授权中心的证书对待签发的证书进行认证与签名。

而证书授权中心的公钥,则往往会被内置于客户端(比如浏览器)之中。毕竟证书授权中心一般变化并不频繁。

集中式证书授权验证大致如下图所示

当然,向这样进行公钥交换并不完美,因为这依赖于一个权威第三方的可靠性。

如果证书授权中心没有认真履行职责,严格验证证书可靠性。那么客户端是无法验证这种攻击的。

分布式证书授权验证与信任网络

集中式的验证方式看起来很好,不过……对一些希望去中心化的人来说,信任一个[权威第三方],是不能被接受的。

于是,一种去中心化的信任方式被提出——这就是信任网络。

信任网络是一种去中心化的验证方式,其依赖于朋友之间的信任,构筑一个信任网络。

信任网络的最开始,私钥持有者在互相检查确认过身份后会对他人(的公钥)进行信任签名,以确认他确实持有公钥所对应的私钥,并信赖那个人。这又被称作 直接信任

同时,这一信任签名会通过某种途径(最常用的方式是密钥服务器)向其他人广播,并允许其他人获取。

当一份公钥证书还没有得到直接信任,但是,这份公钥证书得到了所信任的人的信任,那么,这份证书可以认为得到了信任者的检查。如果信任这份证书的人很多的话,那么,基本上就能确定这份证书是可信的。而这又称作 间接信任

通过这些熟悉的人之间的信任关系,所构筑的这样的这样的一张网络,就是 信任网络


一个简单的信任网络的信任过程大致如上图所示。

认证密钥

”有效“与”信任“

在GPG中,有两个容易混淆的词,即” 有效 “和” 信任 “。(我也花了好长时间大概搞清楚……)。8 , 9

在GPG的使用中, 有效 (有效性)一般指,相信密钥被确认与某个人所关联,那个人确实持有密钥所对应的私钥。即,密钥被信任属于某个人。

信任 (信任度)则是指,对私钥的所有者的信任,比如说,相信那位特定的私钥所有者不会乱给别人签发签名。即,密钥所对应的所有者是可信的。

常规的认证签名

GPG通过信任模型来对其他人进行信任,并确认有效。在经过仔细的检查后,可以通过对公钥签名来认证密钥,以表示相信这一公钥确实是某个人所拥有。此操作仅能由具有 认证C 功能的密钥进行。

gpg可以使用 --sign-key 10选项来对公钥进行签名。或者也可以在edit-key界面使用sign命令进行签名。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ gpg -u example --sign-key 5069A233D55A0EEB174A5FC3821ACD02680D16DE

pub  rsa4096/821ACD02680D16DE
     创建于:2018-09-11  有效至:永不       可用于:SC  
     信任度:未知        有效性:未知
sub  rsa4096/200B5A9D26878A32
     创建于:2018-09-11  有效至:永不       可用于:E   
sub  rsa4096/0F5AACD65483D029
     创建于:2018-09-11  有效至:永不       可用于:A   
[ 未知 ] (1). VeraCrypt Team (2018 - Supersedes Key ID=0x54DDD393) <[email protected]>


pub  rsa4096/821ACD02680D16DE
     创建于:2018-09-11  有效至:永不       可用于:SC  
     信任度:未知        有效性:未知
 主密钥指纹: 5069 A233 D55A 0EEB 174A  5FC3 821A CD02 680D 16DE

     VeraCrypt Team (2018 - Supersedes Key ID=0x54DDD393) <[email protected]>

您真的确定要签名这个密钥,使用您的密钥
“example (仅用于示例) <[email protected]>” (7F9199B8737F5089)

真的要签名吗?(y/N) y

就像这样,一切使用默认配置对这个公钥创建了一个简单的认证签名。(此处以Veracrypt的签名为例)

指定验证级别

gpg支持在签名时对签名的检查级别(有效性)做出设定。

可以使用 --ask-cert-level 选项在签名时提示输入签名级别。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ gpg -u example --ask-cert-level --sign-key Veracrypt

pub  rsa4096/821ACD02680D16DE
     创建于:2018-09-11  有效至:永不       可用于:SC  
     信任度:未知        有效性:未知
sub  rsa4096/200B5A9D26878A32
     创建于:2018-09-11  有效至:永不       可用于:E   
sub  rsa4096/0F5AACD65483D029
     创建于:2018-09-11  有效至:永不       可用于:A   
[ 未知 ] (1). VeraCrypt Team (2018 - Supersedes Key ID=0x54DDD393) <[email protected]>


pub  rsa4096/821ACD02680D16DE
     创建于:2018-09-11  有效至:永不       可用于:SC  
     信任度:未知        有效性:未知
 主密钥指纹: 5069 A233 D55A 0EEB 174A  5FC3 821A CD02 680D 16DE

     VeraCrypt Team (2018 - Supersedes Key ID=0x54DDD393) <[email protected]>

您有多仔细地检查过您正要签名的密钥确实属于具有以上名字的人呢?
如果您不知道这个问题的答案,请输入“0”。

   (0) 我不作答。  (default)
   (1) 我根本没有检查过。 
   (2) 我随意检查过。 
   (3) 我非常小心地检查过。 

您的选择是?(输入‘?’以获得更多的信息): 2
您真的确定要签名这个密钥,使用您的密钥
“example (仅用于示例) <[email protected]>” (7F9199B8737F5089)

我随意检查过这个密钥。

真的要签名吗?(y/N) y

就像这样,GPG弹出了提示要求输入验证级别。

GPG提供了四种认证级别,这四种认证级别分别代表着在验证证书是否属于证书声称的持有者进行了哪些层次的检查工作/待验证证书通过了哪些验证级别。

(0) 我不作答。 (default)
表示不声明证书的检查情况,对证书的检查情况无要求,此值是签名时的默认值。使用此值签名后,被签名的证书将视为 完全有效
(1) 我根本没有检查过。
表示未对签名进行任何检查,不会改变证书的有效性。
(2) 我随意检查过。
一般表示对被签名证书进行了简单、随意的检查。比如说简单的比较了密钥指纹。关于这一级别的检查究竟意味着什么,由使用者决定。使用此值签名后,被签名的证书将被视为 完全有效
(3) 我非常小心地检查过。
一般表示对证书进行了非常详细和谨慎的检查。比如说通过线下面对面交换公钥,并且仔细核对了由政府当局出具的身份证件。关于这一级别的检查究竟意味着什么,亦由使用者决定。使用此值签名后,被签名的证书将被视为 完全有效

一份具有 有效性 的证书,将会被认为,证书对应的私钥的持有者对应着一个独立的人/组织,会代表自身,根据一定的规则去签发签名和解密文件,而不是恶意第三方为了欺骗/中间人攻击等目的而创建/伪造的证书。在验证有效证书对应私钥所签发的签名或指定其为收件人时,将会正常通过验证。而不会像下面那样输出警告。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
gpg: 0296C3D9E4374AE1:不保证这个密钥属于其声称的所有者

sub  elg..../................ xxxx-xx-xx .......
 主密钥指纹: .... .... .... .... ....  .... .... .... .... ....
      子密钥指纹: .... .... .... .... ....  .... .... .... .... ....

这个密钥并不一定属于其用户标识中声称的那个人。如果您真的
知道自己在做什么,您可以在下一个问题回答 yes。
------------------------------------------
gpg: 警告:此密钥未被受信任签名认证!
gpg:       没有证据表明此签名属于其声称的所有者。
主密钥指纹: .... .... .... .... ....  .... .... .... .... ....

也可以使用 --default-cert-level 选项来设置密钥签名时(默认)检查级别,该选项仅接受 0、1、2、3 这四种值。

更改签名有效期

默认情况下,证书签名是没有有效期的,不过可以使用 --ask-cert-expire--default-cert-expire 选项来设置签名的有效期,关于这两个选项的区别和用法,可以参考类比gpg设置签名有效期这一节对 --ask-sig-expire--default-sig-expire 这两个选项的介绍。

创建信任签名

虽然,理论上说,如果对其他人的信任根本不放心的话,也可以自己一个一个的仔细检查他人的公钥,并赋予有效性签名。不过这样做会非常麻烦,尤其在需要验证的人很多的时候。

这时,可以选择 信任 别人对其他人的信任/相信,来通过信任模型信任其他人。在gpg中,这是对公钥创建 信任签名 来实现的,

不过,创建信任签名似乎并没有单独的选项,此操作需要在–edit-key选单进行。

在密钥编辑选单下输入 tsign 命令进入信任签名创建流程。(可使用 --ask-cert-level 选项要求输入签名/验证有效性)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$ gpg --ask-cert-level -u example --edit-key Veracrypt 
gpg (GnuPG) 2.2.21; Copyright (C) 2020 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  rsa4096/821ACD02680D16DE
     创建于:2018-09-11  有效至:永不       可用于:SC  
     信任度:未知        有效性:未知
sub  rsa4096/200B5A9D26878A32
     创建于:2018-09-11  有效至:永不       可用于:E   
sub  rsa4096/0F5AACD65483D029
     创建于:2018-09-11  有效至:永不       可用于:A   
[ 未知 ] (1). VeraCrypt Team (2018 - Supersedes Key ID=0x54DDD393) <[email protected]>

gpg> tsign

pub  rsa4096/821ACD02680D16DE
     创建于:2018-09-11  有效至:永不       可用于:SC  
     信任度:未知        有效性:未知
 主密钥指纹: 5069 A233 D55A 0EEB 174A  5FC3 821A CD02 680D 16DE

     VeraCrypt Team (2018 - Supersedes Key ID=0x54DDD393) <[email protected]>

您有多仔细地检查过您正要签名的密钥确实属于具有以上名字的人呢?
如果您不知道这个问题的答案,请输入“0”。

   (0) 我不作答。  (default)
   (1) 我根本没有检查过。 
   (2) 我随意检查过。 
   (3) 我非常小心地检查过。 

您的选择是?(输入‘?’以获得更多的信息):3

指定信任级别

gpg在这里将会询问对此密钥所有者的信任程度,可以根据实际对密钥的信任程度进行选择,此处的指定的信任级别会影响到信任网络的构筑结果。

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

  1 = 我勉强相信
  2 = 我完全相信

您的选择是? 1

需要注意的是, 请谨慎了解对方私钥持有者的可信情况(人品)后再赋予此信任,否则可能会由于他人不严谨的审核而导致错误的信任了恶意证书

GPG在设置信任签名时,提供了两个对私钥持有者的信任级别的分级,大致可以解释为

我勉强相信
信任值-勉强,不完全相信他能够严格审核他人证书,不过如果有多个达到一定数量的人同时信任某个人则可以信任。默认设置下为3个人相信即为有效。
我完全相信
信任值-完全,完全相信他能够严格审核他人的证书,不过也可以通过额外设置要求多个完全信任者信任人才会相信。默认设置下为1个人相信即为有效。

当然,对于"勉强"与”完全“究竟对应着现实中的哪些内容/事物,由用户决定。

指定信任深度

在信任级别设置完成后,gpg将会询问对此密钥的信任深度。

1
2
3
4
5
6

请输入此信任签名的深度。
深度若大于 1 您将签名的这个密钥将可以
代表您进行信任签名。

您的选择是? 5

信任深度的意思是,是否信任他信任的其他信任签名,如果信任,信任几级。

如上图所示,为简化处理,上图所有关系都是信任签名关系。

  • 0级 和常规签名毫无区别,只信任被信任者自身有效、
  • 1级 会信任被信任者所直接信任的证书有效、
  • 2级 会信任被受信任者签发给第三方的信任证书所所直接信任信任的人的有效性,即在一定程度上信任了Bob所发布的信任签名。
  • 3级及其以上 以此类推。

信任层级越多,相对来说,自己需要亲自验证的越少,但是,错误相信恶意第三方签名有效的可能性越大。

设置正则限制

有时候,会希望对信任签名加上一定的限制范围。比如说,信任某个同事对同公司的人的信任的有效性(比如说uid是公司邮箱域名的证书),但是不信任他对公司外的人的信任有效性。可以通过在这里设置限制。支持正则表达式。其语法可参考 RFC4880_OpenPGP消息格式_正则表达式部分(en) 的介绍,此处不做介绍。

1
2
3
请输入一个限制此签名的域,如果没有请按回车。

您的选择是? 

输入”y“,然后输入密码解锁主私钥,然后信任签名就完成了。

1
2
3
4
5
6
7
8
您真的确定要签名这个密钥,使用您的密钥
“example (仅用于示例) <[email protected]>” (7F9199B8737F5089)

我非常小心地检查过这个密钥。

真的要签名吗?(y/N) y

gpg>

改变本地信任度

本地信任度一般不会包涵在所导出的公钥中,但是仍然能够影响本地信任网络的计算。

更改本地信任度可以通过 --edit-key 选单的 trust 命令来编辑。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
gpg> trust 
pub  rsa4096/821ACD02680D16DE
     创建于:2018-09-11  有效至:永不       可用于:SC  
     信任度:未知        有效性:未知
sub  rsa4096/200B5A9D26878A32
     创建于:2018-09-11  有效至:永不       可用于:E   
sub  rsa4096/0F5AACD65483D029
     创建于:2018-09-11  有效至:永不       可用于:A   
[ 未知 ] (1). VeraCrypt Team (2018 - Supersedes Key ID=0x54DDD393) <[email protected]>

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

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

您的决定是什么? 

和信任签名不同的是,本地信任度设置提供了5种信任级别,除去之前介绍的 “勉强"和”完全“外,还多出了 ”未定义“、”永不“和”绝对“。其大致含义如下

我不知道或不作答
信任值-未定义,类似于"未知"信任值,不过此处是手动分配的信任值。
我不相信
信任值-永不,黑名单,不会相信此密钥所签发的任何签名。
我绝对相信
信任值-绝对,不建议使用此值标注第三方签名,具有此信任级别的密钥对其他密钥所签发的任何有效签名都会被认为有效。新创建的密钥默认拥有此信任级别。一般用于标注私钥所有者所持有的密钥。默认设置下类似于"完全”

与公钥服务器同步公钥

使用公钥同步服务器可以快速而方便的同步和分发本地的密钥链条。并且也能够方便的搜索关联用户的密钥。

此处简单记录一下相关选项。(毕竟了解不深)

从服务器拉取远程公钥至本地

使用 --receive-keys 1 (可简写为 --recv-keys)选项可以从服务器获取具有指定密钥ID的公钥,并导入本地密钥环。

1
$ gpg --recv-keys "680D16DE"

大致就像这样子,默认情况下会导出公钥所包含的签名

上传本地公钥至公钥服务器

使用 --send-keys 选项可以将本地签名i推送至服务器。

从服务器拉取公钥的更新

使用 --refresh-keys 1选项可以从服务器拉取远程公钥的更新至本地。

更改GPG所使用的密钥服务器

使用 --keyserver 选项可以更改gpg所使用的密钥服务器。

此选项支持 hkp ldap mailto 等协议的密钥服务器。

1
gpg --keyserver hkps://keyserver.ubuntu.com/ --recv-key "26a66d8f435dce50"

就像这样。不过此选项不建议在命令行中给出,而是应该在配置文件中使用。

关于sks密钥服务器的注意事项

现在仍然在工作的绝大多数密钥服务器都是使用的sks密钥服务器(组),其有以下几个特性:

  1. 分布式,提交的密钥提交至任何一个在sks服务器池的服务都会很快与其他位于sks池的程序同步。通过分布式提高了sks池整体的可用性可靠性和稳定性。
  2. 不可删除,即使你控制着一个sks密钥服务器,删除了一个公钥,很快就会通过sks的公钥算法同步,而想要命令所有的sks池同时删除一个指定的公钥几乎是不可能的。这样可以阻止恶意第三方恶意删除公钥,但是也阻止了正常的公钥删除流程。

因此, 在上传公钥至sks服务器池的时候,应当谨慎确认,一旦上传至sks池,将不可能从sks公钥服务器删除公钥 。顶多只能在公钥上面加上一段"我从此以后不再信任/使用该证书"的声明(又称 吊销密钥) 。

针对SKS密钥服务器的投毒攻击

sks密钥池的不可删除性,也让滥用者/攻击者有可乘之机。

恶意攻击者,生成大量的密钥对一张证书进行签名,并将这份包含大量无效签名的证书上传至sks密钥服务器。并同步到所有的sks密钥服务。

而这导致了GPG本地从sks服务器获取公钥时,会花费大量的时间去下载这些无效签名,并导致GPG获取公钥超时而报错。这种被大量无效签名者所签名的证书又被称为”受污染/被投毒的证书“,而sks密钥服务器的 不可删除 这一特性,亦使sks无法有效处理这些恶意签名。11

在一定程度上,影响深远。

密钥签名的吊销和删除

当遇到某些事情时,决定不再信任某个人的证书,或者因为某种原因,需要对签名更新时。可以吊销或删除已有的私钥。

吊销签名

如果签名过的公钥已经上传至服务器,则需要对签名进行吊销操作,以向其他人表示"声明,不再确认于xxxx年xx月xx日签署的签名有效"这样一段信息。

吊销签名可以在 --edit-key 操作选单使用 revsig 命令进入吊销选单。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
gpg --ask-cert-level -u example --edit-key Veracrypt 
gpg (GnuPG) 2.2.21; Copyright (C) 2020 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  rsa4096/821ACD02680D16DE
     创建于:2018-09-11  有效至:永不       可用于:SC  
     信任度:未定义     有效性:完全
sub  rsa4096/200B5A9D26878A32
     创建于:2018-09-11  有效至:永不       可用于:E   
sub  rsa4096/0F5AACD65483D029
     创建于:2018-09-11  有效至:永不       可用于:A   
[ 完全 ] (1). VeraCrypt Team (2018 - Supersedes Key ID=0x54DDD393) <[email protected]>

gpg> revsig 
您已经签名来密钥 821ACD02680D16DE 上的这些用户标识:
     VeraCrypt Team (2018 - Supersedes Key ID=0x54DDD393) <[email protected]>
   由您的密钥 7F9199B8737F5089 吊销于 2020-08-15
   由您的密钥 7F9199B8737F5089 于 2020-08-16 签名

用户标识:“VeraCrypt Team (2018 - Supersedes Key ID=0x54DDD393) <[email protected]>”
由您的密钥 7F9199B8737F5089 于 2020-08-16 签名
要为这份签名生成一份吊销证书吗?(y/N) y
您正在吊销这些签名:
     VeraCrypt Team (2018 - Supersedes Key ID=0x54DDD393) <[email protected]>
   由您的密钥 7F9199B8737F5089 于 2020-08-16 签名
真的要生成吊销证书吗?(y/N) y

进入吊销选单后需要连续确认两次是否吊销。

1
2
3
4
5
请选择吊销的原因:
  0 = 未指定原因
  4 = 用户标识不再有效
  Q = 取消
您的决定是什么? 

然后就会要求输入吊销原因。0就是不特别著名吊销原因,而4则是声明此用户标识无效。输入Q取消吊销

输入描述/注释,允许对注销的原因进行额外说明。

1
2
3
4
5
6
7
请输入描述(可选);以空白行结束:
> 移除测试用签名
> 
吊销原因:未指定原因
移除测试用签名
这样可以吗? (y/N) y
gpg> save

然后,会要求密码以解密私钥,然后完成了对签名的吊销。最后输入 save 保存注销操作并退出。

然后可以把吊销签名后的公钥推送至服务器或者导出文件发送给其他人。

不过,如果是要重新签名的话,在吊销完成后还需要输入 clean 命令来清除多余的/被吊销的签名,就像下面这样

1
2
gpg> clean
用户标识 “VeraCrypt Team (2018 - Supersedes Key ID=0x54DDD393) <[email protected]>”:2 个签名被移除

不然依旧会报错"已签名,无需签名"

删除签名

如果签名过的证书还没有分发,这时,直接删除签名可能更方便一些。

--edit-key 选单中,使用 deisig 来删除密钥签名(包括已经被吊销的),不过在这之前需要先用 uid 命令选择一个用户标识

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
gpg> delsig 
您必须选择至少一个用户标识。
(使用‘uid’ 命令。)

gpg> uid 1

pub  rsa4096/821ACD02680D16DE
     创建于:2018-09-11  有效至:永不       可用于:SC  
     信任度:未定义     有效性:未知
sub  rsa4096/200B5A9D26878A32
     创建于:2018-09-11  有效至:永不       可用于:E   
sub  rsa4096/0F5AACD65483D029
     创建于:2018-09-11  有效至:永不       可用于:A   
[ 未知 ] (1)* VeraCrypt Team (2018 - Supersedes Key ID=0x54DDD393) <[email protected]>

gpg> 

[未知] (1) 后面有一个 ”*“ 星号表示已经被选中,可以用 deisig 命令对签名进行删除。delsig会对每一个签名都询问一次是否删除。

1
2
3
4
gpg> delsig 
uid  VeraCrypt Team (2018 - Supersedes Key ID=0x54DDD393) <[email protected]>
rev!         7F9199B8737F5089 2020-08-16  example (仅用于示例) <[email protected]>
删除这个完好的签名吗?(y/N/q)y

rev 开头的表示这个签名已经被吊销

1
2
3
uid  VeraCrypt Team (2018 - Supersedes Key ID=0x54DDD393) <[email protected]>
sig!3        821ACD02680D16DE 2018-09-11  [自签名]
删除这个完好的签名吗?(y/N/q)n

而[自签名]则用于证明私钥与公钥之间的关系,一般不需要删除。 sig 开头则表示这是一个完好的未被吊销的签名。

1
2
3
已经删除了 1 个签名。

gpg> save

签名删除完成后,保存,退出。

认证链条件

默认认证链条件

在默认情况下,一张新的证书要被信任有效,需要符合下述的两个条件9

  1. 此证书已被足够多的有效密钥所签名,如
    • 已经得到亲自签名。(被绝对信任私钥所签名)
    • 已经被一个及其以上的完全可信的证书所签名。(被信任值-完全的证书签名)
    • 已经被三个及其以上的边缘可信的证书所签名。(被三个以上的[信任值-勉强]的证书所签名)
  2. 并且,有一条信任路径从待信任者到自己的密钥的已签名私钥路径为5步或更短。

符合上述条件的签名将会被标记为[有效-完全]。

如果一张签名符合条件2,但是不完全符合条件1(比如说,被不足三份勉强受信任的人所签名,但有人对其签名)则该签名则会被标记为[有效-勉强]。

更改信任签名数量要求

gpg提供两个选项来分别改变相信第三方证书[有效-完全]所需要的[完全]和[勉强]的签名数量要求。

使用 --completes-needed5选项可以指定[有效-完全]需要多少具有[信任-完全]的证书所签发的有效密钥签名,默认是1。

而使用 --marginals-needed5选项则可以指定[有效-完全]需要多少具有[信任-边缘]的证书所签发的有效密钥签名,默认是3.

也可以在配置文件中设置这两个选项,如下所示。(其实上面很多选项也能通过配置文件指定,不过很多介绍的选项也具有"临时更改"的要求,不过对信任条件的变更一般并不是临时更改)

1
2
3
4
#file ~/.gnupg/gpg.conf

completes-needed 2
marginals-needed 5

上面那个配置文件会要求证书要有两个以上完全信任或5个以上边缘信任的证书所签发的签名才会被视为有效-完全。

此选项设置越大的值,越难被直接信任,而越小的值,引入信任风险证书的概率会越大。请根据自身要求妥善决定。

更改认证链的最大深度

使用 --max-cert-depth 选项可以更改条件2-认证链的最大深度。该选项默认值为5。

此选项设置越大的值,越容易错误引入风险证书,而越小的值,越难被间接信任,需要当事人花更多工作去直接验证。

另外,此选项设置的最大深度和信任签名所设置的深度,以较小者为准。

1
2
3
#file ~/.gmupg.gpg.conf

max-cert-depth 3

此配置文件会设置最大信任深度为3级

密钥管理

此处简单记录一些密钥管理相关操作

更新密钥有效期

在edit-key界面可以使用 expire 命令来更新延长密钥有效期。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
sec  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2021-07-31  可用于:SCEA
     信任度:绝对        有效性:绝对
ssb  rsa4096/0EA18957BCB383D0
     创建于:2020-08-06  有效至:2020-11-14  可用于:SEA 
[ 绝对 ] (1). example (仅用于示例) <[email protected]>

gpg> expire
将要变更主密钥的过期时间。
请设定这个密钥的有效期限。
         0 = 密钥永不过期
      <n>  = 密钥在 n 天后过期
      <n>w = 密钥在 n 周后过期
      <n>m = 密钥在 n 月后过期
      <n>y = 密钥在 n 年后过期
密钥的有效期限是?(0) 2y
密钥于 2022年08月07日 星期日 08时00分52秒 CST 过期
这些内容正确吗? (y/N) y


sec  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2022-08-07  可用于:SCEA
     信任度:绝对        有效性:绝对
ssb  rsa4096/0EA18957BCB383D0
     创建于:2020-08-06  有效至:2020-11-14  可用于:SEA 
[ 绝对 ] (1). example (仅用于示例) <[email protected]>

像这样就能更新/延长了主私钥的有效期了(此处从2021-07-31更新到2022-08-07)。然后,可以将更新后的证书公钥发布给证书服务器,以及重要的合作伙伴。

因此,如果私钥丢失不要指望有效期自动失效,请立刻进行吊销操作。

更新子密钥有效期

类似于上节,不过在执行 expire 之前需要执行 key 选择指定的子密钥。

key命令会接受数字(子密钥的序号)参数,无参数不选择任何内容。(或者说选择主密钥?)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
gpg> key 

sec  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2022-08-07  可用于:SCEA
     信任度:绝对        有效性:绝对
ssb  rsa4096/0EA18957BCB383D0
     创建于:2020-08-06  有效至:2020-11-14  可用于:SEA 
[ 绝对 ] (1). example (仅用于示例) <[email protected]>

gpg> key 1

sec  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2022-08-07  可用于:SCEA
     信任度:绝对        有效性:绝对
ssb* rsa4096/0EA18957BCB383D0
     创建于:2020-08-06  有效至:2020-11-14  可用于:SEA 
[ 绝对 ] (1). example (仅用于示例) <[email protected]>

gpg> 

被选择的子密钥, ssb 后面会有一个”*“星号。然后就可以执行 expire 更改子密钥有效期了。

1
2
3
4
5
6
7
8
9
gpg> expire 
将要变更子密钥的过期时间。
请设定这个密钥的有效期限。
         0 = 密钥永不过期
      <n>  = 密钥在 n 天后过期
      <n>w = 密钥在 n 周后过期
      <n>m = 密钥在 n 月后过期
      <n>y = 密钥在 n 年后过期
密钥的有效期限是?(0) 

就像这样。

新增密钥用户标识

有时,可能希望添加一些用户标识。这时,可以通过 --edit-key 界面的 adduid 命令来新增用户标识。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
gpg> adduid 
真实姓名: exampl2
电子邮件地址: [email protected]
注释: 依旧仅用于示例
您正在使用‘utf-8’字符集。
您选定了此用户标识:
    “exampl2 (依旧仅用于示例) <[email protected]>”

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

sec  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2021-07-31  可用于:SCEA
     信任度:绝对        有效性:绝对
ssb  rsa4096/0EA18957BCB383D0
     创建于:2020-08-06  有效至:2020-11-14  可用于:SEA 
[ 绝对 ] (1)* example (仅用于示例) <[email protected]>
[ 未知 ] (2). exampl2 (依旧仅用于示例) <[email protected]>

gpg> save

就像这样。然后可以把更改的密钥分发给其他人了。

新增照片标识/头像

使用edit-key的 addphoto 选项可以设置一张图片,可以用来做头像?

更改密钥/子密钥的功能标识[SEO]

使用密钥编辑菜单的 change-usage 命令可以更改密钥(或子密钥)的功能标识。此处以主密钥为例,如需要更改子密钥需要先使用 ”key“命令选择子密钥。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
gpg> change-usage
变更主密钥的用途。

RSA 密钥的可实现的功能: 签名(Sign) 认证(Certify) 加密(Encrypt) 身份验证(Authenticate) 
目前启用的功能: 签名(Sign) 认证(Certify) 加密(Encrypt) 身份验证(Authenticate) 

   (S) 签名功能开关
   (E) 加密功能开关
   (A) 身份验证功能开关
   (Q) 已完成

您的选择是? A

RSA 密钥的可实现的功能: 签名(Sign) 认证(Certify) 加密(Encrypt) 身份验证(Authenticate) 
目前启用的功能: 签名(Sign) 认证(Certify) 加密(Encrypt) 

   (S) 签名功能开关
   (E) 加密功能开关
   (A) 身份验证功能开关
   (Q) 已完成

您的选择是? Q

就像这样,移除了主密钥的[身份验证A]功能。

生命周期的尾声

当密钥/子密钥/用户标识不再使用时,或者私钥遗失,这种情况下需要对其进行吊销和/或删除操作,不再使用。

吊销和移除一个已有的用户标识

在edit-key界面,使用 uid 选择待吊销/删除的用户标识

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
gpg> uid 1

sec  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2021-07-31  可用于:SCEA
     信任度:绝对        有效性:绝对
ssb  rsa4096/0EA18957BCB383D0
     创建于:2020-08-06  有效至:2020-11-14  可用于:SEA 
[ 绝对 ] (1)* exampl2 (依旧仅用于示例) <[email protected]>
[ 绝对 ] (2)  example (仅用于示例) <[email protected]>

gpg> 

如上所示。然后就可以使用 deluid 来删除本地标识。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
gpg> deluid 
真的要移除此用户标识吗?(y/N) y

sec  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2021-07-31  可用于:SCEA
     信任度:绝对        有效性:绝对
ssb  rsa4096/0EA18957BCB383D0
     创建于:2020-08-06  有效至:2020-11-14  可用于:SEA 
[ 绝对 ] (1)  example (仅用于示例) <[email protected]>

gpg> 

就像这样,用户标识被删除了。不过 需要注意的是,此删除仅作用于本地,如果已经上传分发,请使用[吊销]来声明此用户标识已不再使用

在同界面上,使用 revuid 来吊销选定的标识。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
gpg> revuid 
真的要吊销此用户标识吗?(y/N) y
请选择吊销的原因:
  0 = 未指定原因
  4 = 用户标识不再有效
  Q = 取消
(也许您会想要在这里选择 4)
您的决定是什么? 4
请输入描述(可选);以空白行结束:
> 吊销测试用exampl2用户标识
> 
吊销原因:用户标识不再有效
吊销测试用exampl2用户标识
这样可以吗? (y/N) y
gpg: 警告:有一份用户标识签名的日期标记为 706 秒后的未来

sec  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2021-07-31  可用于:SCEA
     信任度:绝对        有效性:绝对
ssb  rsa4096/0EA18957BCB383D0
     创建于:2020-08-06  有效至:2020-11-14  可用于:SEA 
[ 已吊销 ] (1). exampl2 (依旧仅用于示例) <[email protected]>
[ 绝对 ] (2)  example (仅用于示例) <[email protected]>

gpg> 

就像这样。不过,对于吊销,也需要注意 [吊销]仅仅是声明了此用户标识无效,一般的公钥分发服务并不会删除已吊销的用户标识,在设置和分发用户标识时请谨慎决定,上传至密钥服务器的隐私信息很难被删除

吊销和移除一个子密钥

首先,使用 key 命令选择待删除的子私钥。然后输入 delkey 命令删除给定子私钥,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
gpg> key 2
sec  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2021-07-31  可用于:SCEA
     信任度:绝对        有效性:绝对
ssb  rsa4096/0EA18957BCB383D0
     创建于:2020-08-06  有效至:2020-11-14  可用于:SEA 
ssb* rsa2048/C71325239E03B423
     创建于:2020-08-07  吊销于:2020-08-07  可用于:S   
[ 绝对 ] (1). example (仅用于示例) <[email protected]>

gpg> del
delkey  delsig  deluid  
gpg> delkey 
您真的要删除此密钥吗?(y/N) y

sec  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2021-07-31  可用于:SCEA
     信任度:绝对        有效性:绝对
ssb  rsa4096/0EA18957BCB383D0
     创建于:2020-08-06  有效至:2020-11-14  可用于:SEA 
[ 绝对 ] (1). example (仅用于示例) <[email protected]>

就像这样,被选择的子私钥被删除了。

不过,如果公钥已经被分发,则使用 吊销 功能更为合适。使用 revkey 命令会将选定的子证书吊销。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
gpg> revkey 
您真的要吊销这个子密钥吗?(y/N) y
请选择吊销的原因:
  0 = 未指定原因
  1 = 密钥已泄漏
  2 = 密钥被替换
  3 = 密钥不再使用
  Q = 取消
您的决定是什么? 0
请输入描述(可选);以空白行结束:
> 吊销测试子密钥
> 
吊销原因:未指定原因
吊销测试子密钥
这样可以吗? (y/N) y

sec  rsa4096/7F9199B8737F5089
     创建于:2020-08-05  有效至:2021-07-31  可用于:SCEA
     信任度:绝对        有效性:绝对
ssb  rsa4096/0EA18957BCB383D0
     创建于:2020-08-06  有效至:2020-11-14  可用于:SEA 
下列密钥在 2020-08-07 被 RSA 的密钥 7F9199B8737F5089 exampl2 (依旧仅用于示例) <[email protected]> 所吊销
ssb  rsa2048/C71325239E03B423
     创建于:2020-08-07  吊销于:2020-08-07  可用于:S   
[ 绝对 ] (1). example (仅用于示例) <[email protected]>

gpg> 

吊销与删除一整个密钥

对整个密钥进行吊销和删除,不能通过密钥编辑选单进行。

吊销一个证书需要先生成一份吊销证书,这可以通过 --generate-revocation 10(可简写为 --gen-revoke)。

如下所示。一份吊销证书被生成了。在实际吊销证书之前,如果有时间的话,建议先对用户标识进行修改,因为吊销后证书将不允许任何修改。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
gpg --homedir=t --generate-revocation example

sec  rsa4096/7F9199B8737F5089 2020-08-05 exampl2 (依旧仅用于示例) <[email protected]>

要为这个密钥创建一个吊销证书吗?(y/N) y
请选择吊销的原因:
  0 = 未指定原因
  1 = 密钥已泄漏
  2 = 密钥被替换
  3 = 密钥不再使用
  Q = 取消
(也许您会想要在这里选择 1)
您的决定是什么? 0
请输入描述(可选);以空白行结束:
> 吊销测试用证书
> 
吊销原因:未指定原因
吊销测试用证书
这样可以吗? (y/N) y
已强行使用 ASCII 字符封装过的输出。
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: This is a revocation certificate

iQJLBCABCAA1FiEEAzncqhHqDBrOs2pcf5GZuHN/UIkFAl85EGIXHQDlkIrplIDm
tYvor5XnlKjor4HkuaYACgkQf5GZuHN/UInlJBAAvfTTZGpx8s4tspA60h6hQBTr
5hDphy2J0qm5vaeONDxoFnqAoeSSTYkRfeydJHbFbnrwpgsh2EuhLKoRq72BOR1T
c48dyzarx08vonOcnGMo0ZRPZtbuCRmeoM4QpMa9IJev93QTxJeLCFR2jloHKzRx
xSvRhf6T5R/OWDscTpPK/LAuVjf2j9iRgtJo6o7eLVZOvghlBAfkpf/Tl6i26bWu
Ko1EfMIL8Bq/3fbgDoId8gyfOoPuDx7B6LWpoxjmoEK3wGUQeIBcm2JblcnL6lLI
KlJfxrn5X1VJSeEFhNi+tDQfL01G73vDg2rAV4nFt3rircK96B3+ZbkWp2czhprB
rjO1OO4DkswrVj9KDzlm9FhkiOmMtJe+dpNO1DxxFz+CLlnPwihxIAe0al+Kw0B1
H2xYWWI2vC1oZm9+tVeqycobWiuLd2YJ99T9boGkPP4iNp+MAZGyBsnoL3QeFDqx
Sj8wnaCvVdgZCzu5EXGEr+/RAlncSfm5TunMVY079DdHn7XhXsf2lTfYBV7iRMAj
h1cL5sjnIXRfieSW9Xpir5fE1Jp5eG7rMDO7HLN7H/5LsMcm1ZxXN5j7nEY6QEPh
m8FovJfFLhs2Fiy5MH1q5oZsqmORDh0MniBXRuGG+3nfxzlVGUC15/YBgFUDFF6c
xPfNFJ/vvmhaAxxzvsY=
=QfZb
-----END PGP PUBLIC KEY BLOCK-----
已创建吊销证书。

请把这个文件转移到一个您可以藏起来的介质上;如果坏人获取到了这
份证书的话,那么他就能使用它并让您的密钥无法继续使用。把此证书
打印出来再存放到安全的地方也是很好的方法,以免您的保存媒体变得
不可读。但是千万小心:您机器上的打印系统可能会在打印过程中储存
这些数据,并使得其他人看到!

吊销证书生成后,要实际吊销一份证书则需要将吊销证书与私钥导入(合并)。通过 --import 可以实现这一点,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ gpg --import example.rev 
gpg: 密钥 7F9199B8737F5089:“exampl2 (依旧仅用于示例) <[email protected]>” 吊销证书已被导入
gpg: 处理的总数:1
gpg:    新的密钥吊销:1
gpg: 未找到任何绝对信任的密钥
$ gpg -k
/tmp/shattered/t/pubring.kbx
----------------------------
pub   rsa4096 2020-08-05 [SCEA] [吊销于:2020-08-16]
      0339DCAA11EA0C1ACEB36A5C7F9199B8737F5089
uid           [ 已吊销 ] exampl2 (依旧仅用于示例) <[email protected]>
uid           [ 已吊销 ] example (仅用于示例) <[email protected]>

证书吊销完成后,请尽快导出吊销后的公钥证书,上传至服务器,并分发给伙伴,以表示签名已吊销。

吊销完成后,可以使用 --delete-secret-and-public-key 1来删除密钥,此选项将会先删除指定密钥的私钥,然后删除指定密钥的公钥。就像下面这样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
gpg --homedir=t --delete-secret-and-public-key example
gpg (GnuPG) 2.2.21; Copyright (C) 2020 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.


sec  rsa4096/7F9199B8737F5089 2020-08-05 exampl2 (依旧仅用于示例) <[email protected]>

要从钥匙环里删除这个密钥吗?(y/N) y
这是一个私钥!- 真的要删除吗?(y/N) y

pub  rsa4096/7F9199B8737F5089 2020-08-05 exampl2 (依旧仅用于示例) <[email protected]>

要从钥匙环里删除这个密钥吗?(y/N) y

不过,删除私钥时除了终端询问外,还有一个图形化界面的询问提示,如下图。

另外,也可以使用 --delete-secret-keys 1--delete-keys 1这两个选项来分别删除私钥和公钥。

(不过,使用 --delete-keys 选项删除公钥时,需要保证私钥已经被删除,否则删除将会失败)

也可以使用

一些其他相关信息

这里是在写这篇文章时所找到的各种感觉比较有趣/有用/奇怪的选项。

使用GPG输出随机值

可以使用 --gen-random 1选项来输出指定条件的随机字节。其接受两个参数,第一个参数是随机数的质量,范围是0-2。第二个选项则是输出字符串的数量,0或者不给出则永不停止。

1
gpg -a --gen-random 2 100

不过,请不要随便使用这个命令,这个命令会大量消耗系统的随机熵。并可能导致系统的其他依赖于随机数发生器的程序卡住。

维护信任值数据库

使用 --update-trustdb 1会执行信任数据库维护操作,此选项是一个交互式操作,因为gpg可能会询问用户对某个密钥的信任级别(ownertrust值)。

1
gpg --update-trustdb 2>&1
1
2
3
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: 深度:0  有效性:  2  已签名:  0  信任度:0-,0q,0n,0m,0f,2u
gpg: 下次信任度数据库检查将于 2021-07-31 进行

如果不希望交互,也可以使用 --check-trustdb 1选项进行非交互式信任值检查,类似于上面那选项。不过会跳过缺失信任值的密钥的信任值计算。

导出/导入/备份信任值

使用 --export-ownertrust 1选项会导出当前信任数据库存储的信任值,备份很好用。毕竟信任值也勉强算非公开的私人数据嘛~~~

1
gpg --export-ownertrust
1
2
3
4
5
# 已指定的信任度的列表,创建于 2020年08月16日 星期日 20时01分28秒 CST 
# (请用“gpg --import-ownertrust”导入这些信任度)
6C8C5C85CD1E22CE34BEC72E057D38C6B1CA2C0A:6:
0339DCAA11EA0C1ACEB36A5C7F9199B8737F5089:6:
5069A233D55A0EEB174A5FC3821ACD02680D16DE:2:

导出的信任值也可以用 --import-ownertrust 1选项导入至其他信任度数据库中。

解决[对设备不适当的 ioctl 操作]错误

在使用ssh等远程终端使用GOG时,可能报错以下信息,而无法输入密码。

1
2
gpg: 公钥解密失败:对设备不适当的 ioctl 操作
gpg: 解密失败:没有秘匙

可以通过设置环境变量来不使用图形界面以允许命令行密码输入。

1
export GPG_TTY=$(tty)

就像这样子。也可以参考调用GPG-AGENT(使用GNU Privacy Guard)(en)的说明

结束语

经过漫长的时间,这篇文章终于大致上完成了……

自8月2日开始动笔,到今天8月16日完成……

絮絮叨叨,写了很多呢。感觉都有点快手册的感觉……(思考)

虽然说查了很多资料,不过也有很多不确定的地方来着……如有错误,欢迎指正。

大致可以开始心里所想的下一步工作了吧(或者先休息一会?)……希望一切顺利。

一些参考链接

(也有很多链接是以脚注的形式引用的,可以到页面的最下面查看脚注部分链接)

https://www.ruanyifeng.com/blog/2013/07/gpg.html GPG入门教程 - 阮一峰的网络日志

https://emacsist.github.io/2019/01/01/gnupg2%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8C%97/#%E5%8A%A0%E8%A7%A3%E5%AF%86 GnuPG2使用指北 - emacsist

https://zhuanlan.zhihu.com/p/29575509 GPG 与端到端加密:论什么才是可以信任的 - 知乎

https://wiki.debian.org/Subkeys?action=show&redirect=subkeys debian关于gpg子密钥的配置(en)

https://blog.starryvoid.com/archives/348.html GPG 密钥的创建、管理和使用 - StarryVoid - Blog

https://tanguy.ortolo.eu/blog/article9/pgp-signature-infos 具有信任和验证级别的PGP签名-Tanguy Ortolo(en) :emacs:移动设备:ssh:termux:


  1. gpg文档:选择操作类型的选项——https://www.gnupg.org/documentation/manuals/gnupg/Operational-GPG-Commands.html ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. gpg文档:做一件平时不想做的事情:https://www.gnupg.org/documentation/manuals/gnupg/GPG-Esoteric-Options.html ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  3. 谷歌对于这一碰撞的介绍:https://shattered.it/ ↩︎

  4. gpg文档,openpgp协议特定选项:https://www.gnupg.org/documentation/manuals/gnupg/OpenPGP-Options.html ↩︎ ↩︎ ↩︎

  5. gpg文档-如何更改配置:https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html ↩︎ ↩︎ ↩︎ ↩︎

  6. gpg文档:密钥相关选项:https://www.gnupg.org/documentation/manuals/gnupg/GPG-Key-related-Options.html ↩︎ ↩︎ ↩︎

  7. gnu隐私手册_混合密码:https://www.gnupg.org/gph/en/manual.html#AEN210 ↩︎

  8. 具有信任和验证级别的PGP签名-Tanguy Ortolo: https://tanguy.ortolo.eu/blog/article9/pgp-signature-infos ↩︎

  9. gpg教程_验证公共密钥环的其他密钥: https://www.gnupg.org/gph/en/manual/x334.html ↩︎ ↩︎

  10. GPG文档-如何管理密钥:https://www.gnupg.org/documentation/manuals/gnupg/OpenPGP-Key-Management.html ↩︎ ↩︎

  11. GPG SKS 同步网络被投毒事件及其影响 - 王玄的博客 https://blog.wangxuan.name/2019/11/30/gpg-sks-network-poisoned/ ↩︎