作者: Angus.Fenying <i.am.x.fenying@gmail.com>
日期: 2016-11-10 10:35 PM
本文介绍 OpenSSL 命令行进行 RSA 加密、解密、签名、验证的操作,但不涉及 RSA 算法原理解析,如有兴趣,可以阅读阮一峰的《RSA算法原理》。如果你只想知道 RSA 是什么,那么你只要记住:RSA 是一种加密算法,使用两个密钥,一个叫公钥,一个 叫私钥,使用公钥加密的密文只有使用私钥才可以解密,反之亦然。
Section 0: 生成随机文件
由于 OpenSSL 创建密钥文件是随机生成的,因此有必要为之提供一份随机数据源。
可以用 openssl 的 rand
命令创建一个 64MB 的随机文件,保存为文件 randSrc.bin。
openssl rand -out ./randSrc.bin 67108864
还可以使用 -base64
或者 -hex
两个参数之一指定输出格式为 BASE64 或者 HEX。
Section 1: 生成一个密钥文件
小贴士:
- 根据目前的普遍需求,应当使用 AES256 为加密标准。
- 通常 RSA 私钥文件命名为 name.pem,公钥文件名为 name_pub.pem。
- OpenSSL 生成的密钥文件默认是 PEM 格式的。
openssl genrsa \
-rand randSrc.bin \
-aes256 \
-out rsa.pem 2048
这个命令的意思是:
genrsa
: 生成 RSA 密钥文件。-aes256
: 使用 AES256 算法加密生成的密钥文件, 因此你需要输入加密用的密码(并记住)。-rand randSrc.bin
使用文件 randSrc.bin 作为随机数来源。-out rsa.pem
: 将生成的密钥文件保存为rsa.pem
。2048
: 生成 2048 Bits 的 RSA 密钥文件。
2048 Bits 是指 RSA 算法里 N 的长度,而不是说公钥和私钥都是 2048 Bits。
生成文件样例:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,A6F8DD9D1D994363907C278CDF9B644C
MfsPjXK6izOmmzMseG3M2aBKque20ao13+oFg/JdJtlCK0Vb11hLqq8h/ICnY3lI
z1xuBKiXVykl521YumeTS6C+WtSkb71cy1u6lHBwdO44tWxklEqcl1sLYIWKyNaB
VgKmS4BhfuUq8XlSt3LnuQT/BJWPP7+GUUaZG6/stMWAx+XBg9mMahxGCqo7aRcz
...............
nLGRE27iklwGgSagaK40FDiSe69HcIBkHCUQYaYtXQzHNgjoQRkcotzo+vxM7XcL
5y5DHwA8IFwt9c5f14lxZ2cXF9p54JA3UMy+T7XggINDgBFuOPR/U3eBS2x6hHW6
eoGX+khw+s5atpNJaF4s6n2ViDseQsW+b8NfSdlX0j5f5xSasFcYgFsDZtBy/FqZ
-----END RSA PRIVATE KEY-----
这是一个密钥文件,而不是一个纯粹的私钥文件,它包含了完整的密钥信息,即是说,里面 既有公钥也有私钥。但是只能把它当成私钥使用,公钥部分请参考 _Section 3_。
Section 2: 去除密钥文件的密码
从 Section 1 生成的密钥文件中提取没有密码的密钥文件,以便给 Nginx 等服务器使用。
openssl rsa -in rsa.pem -out rsa_pri.pem
Section 3: 根据私钥生成公钥
从 Section 1 生成的私钥文件中提取公钥文件。
openssl rsa -in rsa.pem -pubout -out rsa_pub.pem
Section 4: 使用公钥加密
RSA 加密和解密都是使用 openssl 的 rsautl 命令。
在 Section 3 里面生成了公钥文件 rsa_pub.pem,下面使用它进行加密。 (先生成个数据文件)
echo 1234567890 > test.txt
md5sum test.txt
openssl rsautl \
-encrypt \
-in test.txt \
-out test.secret \
-pubin \
-inkey rsa_pub.pem
可以看到源文件 test.txt 的 MD5 值为 7c12772809c1c0c3deda6103b10fdfa0
。
源文件 test.txt 只有 10 个字节大小,但是加密结果文件 test.secret* 居然有 256 字节(2048 位长),这是因为 RSA 密钥是 2048 位长度的,而 RSA 加解密算法的操作 单位必须和密钥长度一致。所以加密结果的大小一定和密钥长度的一致。所以加密长度必须小于
密钥长度 - 填充长度
。关于填充数据,默认使用
PKCS#1 v1.5
填充格式。
Section 5: 使用私钥解密
在 Section 1 里面生成了密钥文件 rsa.pem,下面使用它进行加密。
openssl rsautl -decrypt -in test.secret -out test.raw -inkey rsa.pem
md5sum test.raw
这里使用的是带密码的密钥文件,因此需要输入 AES 密码先把密钥文件解密出来。如果使用的是 Section 2 中生成的无密码密钥文件,那么则不需要输入密码了。
可以看到解密出来的文件 test.raw 的 MD5 值为 7c12772809c1c0c3deda6103b10fdfa0
。
Section 6: 使用私钥签名
加解密是使用公钥加密,私钥解密,因为公钥是公开的,私钥是保密的。签名和校验则反过来, 私钥签名,公钥校验。
这里假设你要发送消息给你朋友,消息存在文件 test.txt 中,你已经拥有了你朋友的 公钥文件 fr_pub.pem。
那么先用他的公钥对文件进行加密:
openssl rsautl -encrypt -in test.txt -out test.msg -pubin -inkey fr_pub.pem
得到了加密后的文件 test.msg,下面使用你自己的私钥进行签名。
先用 SHA256 算法生成文件的哈希校验码:
openssl sha256 -out test.hash test.msg
得到了哈希校验文件 test.hash,内容如下:
SHA256(test.msg)= ************************
下面使用 RSA 私钥对其进行签名:
openssl pkeyutl \
-sign \
-in test.hash \
-out test.sign \
-inkey rsa_raw.pem
或者
openssl rsautl \
-sign \
-in test.hash \
-out test.sign \
-inkey rsa_raw.pem
RSA 私钥签名的实质是:使用 RSA 私钥对数据的哈希校验码进行加密,这样就可以用 对应的 RSA 公钥解密得到数据的哈希校验码。
得到了签名后的文件 test.sign,将它和 test.msg、rsa_pub.pem 一起发给你的朋友。
Section 7: 使用公钥校验
现在你的朋友收到了你发给他的三个文件,分别是:公钥、签名、消息。现在他怎么确定 消息是发给他的呢?当然是使用公钥校验啦。
首先,他同样使用 openssl 生成文件 test.msg 的哈希校验码。
openssl sha256 -out test.hash test.msg
然后使用你的公钥文件对其进行校验:
openssl pkeyutl \
-verify \
-in test.hash \
-sigfile test.sign \
-pubin \
-inkey rsa_pub.pem
如果校验通过,则会看到提示:Signature Verified Successfully
。
然后他再使用他的私钥解密 test.msg 文件即可得到你发给他的消息了。
这里不使用
openssl rsautl -verify
命令,因为rsautl
的-verify
指令 仅仅是将输入的文件用公钥解密,但不与计算出来的哈希校验码进行比较,不如 pkeyutl 的-verify
方便:openssl rsautl \ -verify \ -in test.sign \ -pubin \ -inkey rsa_pub.pem
前面说了,rsa.pem 里面既包含私钥又包含公钥,这里可以小小地证明一下。 直接使用 rsa_raw.pem 文件进行签名校验。
openssl pkeyutl \ -verify \ -in test.hash \ -sigfile test.sign \ -pubin \ -inkey rsa_raw.pem
如何,结果是不是一样呢?
那么,问题来了。如果传输过程被人拦截了怎么办?这就是下一篇文章的内容了。