数字签名与证书实战:使用OpenSSL亲手实验

引言:从理论到实践

在上一篇文档《数字签名与数字证书深度解析》中,我们深入探讨了数字签名与证书背后的密码学原理。但是理论是理论,实际上密钥长什么样,怎么生成、加密以及证书如何生成,还是需要自己去实践。

本篇将我通过终端,使用强大的openssl工具集,亲手模拟和复现数字签名与证书的全过程。你将直观地看到:

  • 一个合法的签名如何被验证通过。
  • 当文件被篡改或签名被伪造时,验证如何失败。
  • 一个权威的CA是如何为他人签发“数字身份证”(证书)的。

一、 准备工作

首先,我们需要一个工作目录和一份用于签名的原始文件。

1
2
3
4
5
6
# 创建一个名为 "openssl_practice" 的工作目录并进入
mkdir -p openssl_practice
cd openssl_practice

# 创建一份模拟的合同文件
echo "这是一份非常重要的合同文件,内容不可篡改。" > contract.txt

二、 实验一:标准的数字签名与验证

我们将模拟小明生成密钥对,对contract.txt文件签名,然后小红使用其公钥进行验证。

步骤1:生成私钥

小明首先需要一个独一无二的私钥。

命令:

1
openssl genpkey -algorithm RSA -out xiaoming_private_key.pem

终端输出:

1
2
.....+++++++++++++++++++++++++++++++++++++++*..+...+...+.+......+++++++++++++++++++++++++++++++++++++++*..+..+............+.+.....+.+......+..+.......+.........+............+.....+...+....+..+...............+...+.+....+.............+.....+....+...+.....+...+........................+.+.....+....+.................+......+.......+.......................+.......+...+.....+.........+....+......+.....+.+..+...+.........+.+................+...............+.........+...+.....+...+..................+.+...+...+..+......+.+.....+.+..+.............+....+..+...+....+...+...+...........+............................+............+............+...+......+.....+.+.........+..+.....+...+..........................++++++
..+..+.......+...+...+......+...+..+......+....+...+........+++++++++++++++++++++++++++++++++++++++*...+......+.......+...+.....+.+.....+...+.............+..+.+...........+.+.....+.......+..+....+.....+......+....+.........+...........+.........+.......+...+++++++++++++++++++++++++++++++++++++++*.....+.....++++++

此时,目录下生成了xiaoming_private_key.pem文件,这就是小明的“数字印章”的根基。

步骤2:提取公钥

小明需要从私钥中提取出公钥,以便分发给其他人(如小红)用来验证。

命令:

1
openssl pkey -in xiaoming_private_key.pem -pubout -out xiaoming_public_key.pem

此命令执行后没有复杂的输出,它会静默地生成xiaoming_public_key.pem文件。

步骤3:进行数字签名

小明现在使用他的私钥对合同文件进行签名。这个过程包含计算哈希用私钥加密哈希两个步骤,openssl会一步完成。

命令:

1
openssl dgst -sha256 -sign xiaoming_private_key.pem -out contract.sig contract.txt

执行后,会生成一个contract.sig文件,这就是数字签名。它是一个二进制文件,无法直接阅读。

步骤4:验证数字签名

小明将contract.txt(原文)、contract.sig(签名)、xiaoming_public_key.pem(公钥)三者都交给了小红。小红开始验证。

命令:

1
openssl dgst -sha256 -verify xiaoming_public_key.pem -signature contract.sig contract.txt

终端输出:

1
Verified OK

Verified OK这个输出明确地告诉我们:验证通过! 小红可以确信,这份文件确实来自小明,并且内容完好无损。


三、 实验二:模拟攻击,观察验证失败

“安全”的最好证明,就是看它在“不安全”的情况下如何反应。

场景1:文件内容被篡改

假设攻击者小黑在传输过程中修改了合同。

命令:
我们将合同内容修改,然后立即用原始签名小明的公钥进行验证。

1
2
3
4
5
# 修改文件内容
echo "这是一份被篡改过的合同!" > contract.txt

# 尝试验证
openssl dgst -sha256 -verify xiaoming_public_key.pem -signature contract.sig contract.txt

终端输出:

1
2
3
Verification Failure
400817F601000000:error:02000068:rsa routines:ossl_rsa_verify:bad signature:crypto/rsa/rsa_sign.c:442:
400817F601000000:error:1C880004:Provider routines:rsa_verify_directly:RSA lib:providers/implementations/signature/rsa_sig.c:1041:

Verification Failure! 验证失败。因为文件内容的哈希值变了,与签名解密后的原始哈希值不匹配。

场景2:签名被伪造

现在,我们将文件恢复原状,但模拟小黑试图用自己的私钥签名,冒充小明。

命令:

1
2
3
4
5
6
7
8
9
10
11
12
# 恢复原始文件
echo "这是一份非常重要的合同文件,内容不可篡改。" > contract.txt

# 小黑生成自己的密钥对
openssl genpkey -algorithm RSA -out xiaohei_private_key.pem
openssl pkey -in xiaohei_private_key.pem -pubout -out xiaohei_public_key.pem

# 小黑用自己的私钥签名
openssl dgst -sha256 -sign xiaohei_private_key.pem -out xiaohei.sig contract.txt

# 小红仍然用她信任的小明的公钥去验证这个假签名
openssl dgst -sha256 -verify xiaoming_public_key.pem -signature xiaohei.sig contract.txt

终端输出:

1
2
3
4
Verification failure
400817F601000000:error:0200008A:rsa routines:RSA_padding_check_PKCS1_type_1:invalid padding:crypto/rsa/rsa_pk1.c:79:
400817F601000000:error:02000072:rsa routines:rsa_ossl_public_decrypt:padding check failed:crypto/rsa/rsa_ossl.c:796:
400817F601000000:error:1C880004:Provider routines:rsa_verify_directly:RSA lib:providers/implementations/signature/rsa_sig.c:1041:

同样Verification Failure!因为小黑的签名只能用小黑的公钥解密。小红用小明的公钥尝试解密,结果自然是失败的。

扩展探讨:验证失败的底层原因

这里可能会有一个疑问:使用错误的公钥进行验证,究竟是在RSA解密时就直接报错了,还是解密出了一段无用的内容?Verification Failure背后的具体含义是什么?

详细拆解如下:

  1. 签名的内部结构:为了安全,签名过程并不是只对原始哈希进行加密。而是会先将哈希值包裹在一个有特定格式的“信封”里,这个过程叫做填充(Padding)。一个典型的填充后结构看起来像:[固定前缀] + [一堆填充字节] + [哈希算法标识] + [原始哈希]。最后,私钥加密的是这个整体

  2. 验证时的第一关:当用公钥去“解密”签名时,密码库(如OpenSSL)的第一件事就是检查解密出来的东西是否符合上述预定义的“信封”格式。

  3. 错误公钥的后果:由于使用的是与签名私钥不匹配的公钥,解密操作会得到一串完全随机、毫无规律的二进制乱码

  4. 失败的根源:当密码库试图在这串乱码中寻找那个严格的填充格式时,会立刻发现格式对不上。因此,它根本不会继续往后走去提取所谓的“原始哈希”进行比较。流程会直接中断,并抛出一个底层的密码学错误,这正是实验输出中看到的:

    • bad signature (签名数据本身格式不正确)
    • padding check failed (填充格式检查失败)

所以,总结来说:验证过程并不是解密出一个“错误的哈希值”再去比较,而是在解密后的第一时间,就因为无法通过最基本的格式校验而提前失败。 这个“格式校验”是防御各种攻击(如伪造签名)的一道关键防线。

场景3:公钥被掉包(中间人攻击)

这是最隐蔽也最危险的攻击。前两个场景的验证都失败了,保护了我们。但如果小黑从一开始就欺骗了小红,让她误以为小黑的公钥就是小明的公钥呢?

命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 恢复原始文件
echo "这是一份非常重要的合同文件,内容不可篡改。" > contract.txt

# 在场景2中,我们已经为小黑生成了密钥对 (xiaohei_private_key.pem 和 xiaohei_public_key.pem)
# 现在,小黑进行欺骗:
# 1. 小黑用自己的私钥对原始合同进行签名
openssl dgst -sha256 -sign xiaohei_private_key.pem -out fake_for_hong.sig contract.txt

# 2. 小红收到了三样东西:
# - 原始合同: contract.txt
# - 小黑伪造的签名: fake_for_hong.sig
# - 小黑的公钥,但文件名被伪装成小明的: xiaohei_public_key.pem (小红以为这就是 xiaoming_public_key.pem)

# 小红开始验证,她以为自己在用小明的公钥,但实际上用的是小黑的公钥
openssl dgst -sha256 -verify xiaohei_public_key.pem -signature fake_for_hong.sig contract.txt

终端输出:

1
Verified OK

Verified OK验证成功了! 这就是中间人攻击的可怕之处。小红的所有验证步骤都是对的,但因为她信任的“根”——也就是她手中的公钥——从一开始就是错的,整个信任链条就崩溃了。她以为自己验证了小明的签名,实际上验证的是小黑的签名。

这三个实验,特别是最后一个,彻底暴露了单纯数字签名的致命弱点:我们无法通过签名技术本身来保证公钥的真实性。小红无法100%确定她手中的xiaoming_public_key.pem就是小明本人的。 这就是为什么我们需要一个更高级的机制——数字证书——来登场解决公钥的信任问题。


四、 实验三:签发与验证数字证书

现在我们升级角色,扮演一个根证书颁发机构(Root CA),为小明签发一份可信的证书。

步骤1:CA生成自己的根密钥对

这是信任的源头。

命令:

1
2
openssl genpkey -algorithm RSA -out root_ca_private_key.pem
openssl pkey -in root_ca_private_key.pem -pubout -out root_ca_public_key.pem

步骤2:CA生成自签名根证书

CA用自己的私钥给自己签名,制作一张“顶级身份证”。这张证书未来将被内置到操作系统和浏览器中。

命令:

1
openssl req -x509 -new -nodes -key root_ca_private_key.pem -sha256 -days 3650 -out root_ca.crt -subj "/C=CN/ST=Beijing/L=Beijing/O=My Root CA/CN=my-ca.com"

我们创建了一个有效期10年的根证书root_ca.crt

步骤3:小明创建证书签名请求(CSR)

小明需要向CA申请证书。他要先生成自己的新密钥对,然后创建一个包含其公钥和身份信息的CSR文件。

命令:

1
2
3
4
5
# 小明为申请证书生成新的密钥对
openssl genpkey -algorithm RSA -out xiaoming_private_key_for_cert.pem

# 小明创建CSR
openssl req -new -key xiaoming_private_key_for_cert.pem -out xiaoming.csr -subj "/C=CN/ST=Shanghai/O=小明的公司/CN=xiaoming-site.com"

步骤4:CA签署小明的CSR,颁发证书

CA收到小明的xiaoming.csr后,进行审核。审核通过后,用自己的根私钥对其进行签名,正式生成小明的数字证书。

命令:

1
openssl x509 -req -in xiaoming.csr -CA root_ca.crt -CAkey root_ca_private_key.pem -CAcreateserial -out xiaoming_site.crt -days 365 -sha256

终端输出:

1
2
Certificate request self-signature ok
subject=C=CN, ST=Shanghai, O=小明的公司, CN=xiaoming-site.com

现在,xiaoming_site.crt就是小明的官方“数字身份证”了。

步骤5:验证小明的证书

小红拿到了xiaoming_site.crt。她要用她电脑里预装的、信任的root_ca.crt来验证这张“身份证”的真伪。

命令:

1
openssl verify -CAfile root_ca.crt xiaoming_site.crt

终端输出:

1
xiaoming_site.crt: OK

**OK!**验证通过。这表明小红可以完全信任xiaoming_site.crt中的公钥确实属于CN=xiaoming-site.com

步骤6:查看证书内容

我们可以像看身份证一样,查看证书里的详细信息。

命令:

1
openssl x509 -in xiaoming_site.crt -noout -text

终端输出:

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
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
60:bd:c4:46:47:ae:84:34:1e:73:ce:93:ad:fb:6b:6c:61:fb:06:fb
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=CN, ST=Beijing, L=Beijing, O=My Root CA, CN=my-ca.com
Validity
Not Before: Aug 3 09:25:02 2025 GMT
Not After : Aug 3 09:25:02 2026 GMT
Subject: C=CN, ST=Shanghai, O=小明的公司, CN=xiaoming-site.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:b3:ae:be:07:7d:1e:09:34:ff:0d:b3:a2:29:e0:
92:26:d7:08:a2:7e:f4:09:3a:54:72:37:75:17:10:
95:e0:a6:3a:dd:e9:82:91:b8:fa:80:94:fe:40:d7:
d6:bd:45:32:ad:0e:4d:a1:fe:e3:0f:c3:a7:2c:83:
e3:e8:d9:60:94:ba:0f:03:1e:d0:b4:31:cc:61:b5:
50:cc:e3:df:a1:3c:2d:fb:42:81:98:f4:05:74:5f:
bd:5a:62:eb:08:51:69:97:56:ab:4a:68:b8:7a:5d:
33:62:ab:01:ad:3b:77:65:c1:4e:15:f5:81:3f:92:
ab:ce:4a:91:e0:e3:05:35:58:d7:70:a4:38:65:cc:
fd:02:3f:8f:86:9b:3f:5e:07:1d:4f:a5:26:ca:16:
8b:15:5a:80:08:07:77:cc:ec:77:99:e8:4a:f4:68:
18:08:fb:42:d8:ae:b6:44:ea:54:1f:21:da:ba:4e:
9d:bc:fe:f0:27:a7:b3:19:b6:25:a3:7b:fa:5d:53:
83:f8:20:f5:19:77:51:4c:01:6b:2f:a2:3f:f3:31:
1b:c9:c9:a7:7b:ec:e8:80:d3:53:b9:4b:8a:a1:3b:
9d:c0:aa:3a:88:b3:02:13:00:7a:cb:ad:14:78:e4:
49:30:f7:df:76:bc:ec:78:d6:40:43:34:f6:58:cd:
e9:f5
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
82:FD:11:5E:82:6F:DF:B4:34:3D:94:C2:F5:5D:25:C4:4D:40:78:27
X509v3 Authority Key Identifier:
90:90:4C:31:B6:32:C9:D3:BF:C6:46:22:BB:15:12:EA:9F:AB:08:60
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
83:fe:7a:7c:ae:db:a1:29:ac:54:81:4b:05:9d:40:68:fb:c3:
34:2d:cc:55:1d:0a:e0:8e:6d:ed:5c:9f:d3:84:9f:b1:f6:0a:
41:2e:d5:c8:e4:6b:6d:59:d0:d8:c5:76:d6:43:90:00:40:01:
1e:0b:f6:b3:c3:e3:b4:85:c9:47:59:05:3f:56:23:45:39:ac:
55:7d:8d:8e:92:c7:c6:4c:e8:59:f6:0e:3f:0e:ab:53:b4:eb:
3d:11:3e:fb:bd:4b:e1:58:21:6d:78:88:58:36:b0:31:67:38:
08:66:1a:20:05:9d:7f:48:42:d9:bc:06:10:03:be:09:e5:10:
f2:5e:a3:2a:32:12:0f:c7:77:3b:c9:0b:a1:64:54:d9:4b:e2:
ca:4a:0a:39:fc:91:07:83:1c:de:f1:7a:da:36:97:3d:ff:85:
1b:70:ef:7a:f6:ae:c2:10:27:a1:b2:02:ae:cf:58:3c:9d:d5:
b7:2f:04:3b:1d:6c:94:a6:22:02:9f:04:13:14:ed:56:55:b4:
13:68:fb:9e:36:17:df:78:01:e0:0b:fc:1d:44:a8:15:1e:b0:
03:1b:d7:65:5b:be:3b:1b:6a:5c:fe:85:75:d9:4f:56:06:25:
5f:50:a9:23:a5:38:0b:91:a5:cc:f8:1a:93:87:c8:b3:e7:da:
35:5d:c5:26

从输出中,我们可以清晰地看到**颁发者(Issuer)是我们的CA,主题(Subject)**是小明,以及证书中包含的小明的公钥。

结论

通过这一系列动手实验,我们不仅验证了理论,更获得了直观的体感。现在,当你看到浏览器地址栏那把“安全锁”时,你脑海将回是一整套由openssl命令模拟出的、严谨的、环环相扣的签名、签发与验证流程。
数字签名与证书,不仅应用于网络信任体系。在计算机其他方方面面都有广泛应用。我写本篇的契机也是在研究SGX、HyperEnclave时,为之前不够深入理解签名与证书进行补课。