OpenSSL 命令

PPG007 ... 2022-4-16 About 11 min

# OpenSSL 命令

# genrsa

genrsa 用于生成 RSA 私钥,不会生成公钥,因为公钥提取自私钥,如果要查看公钥或者生成公钥可以使用 openssl rsa 命令。

openssl genrsa [-out filename] [-passout arg] [-des] [-des3] [-idea] [numbits]

  • -out filename:将私钥输出到指定的文件,如果不指定此参数会输出到标准输出。

  • passout arg:加密私钥文件时传递密码的格式,如果要加密私钥文件时没有指定此参数则提示输入密码。

    密码格式:

    • 格式一:pass:password,password 表示传递的明文密码。
    • 格式二:env:var,从环境变量 var 获取密码。
    • 格式三:file:filename,filename 文件中的第一行作为密码,如果同时使用此格式传递给 passin 和 passout 选项,则第一行是 passin,第二行是 passout。
    • 格式三:stdin:从标准输入中获取要传递的密码。
  • numbits:指定要生成的私钥的长度,默认为 1024,该项必须是命令行的最后一项参数。

  • -des|-des3|-idea:指定加密私钥文件用的算法,这样每次使用私钥文件都将输入密码。

使用下面的命令生成一个不加密的私钥并输出到文件 private.key:

openssl genrsa -out private.key
1

生成一个加密的私钥文件并输出到 private-secret.key 文件中:

openssl genrsa -out private-secret.key -passout pass:test
1

# rsa、pkey

openssl rsa 和 openssl pkey 分别是 RSA 密钥的处理工具和通用非对称密钥处理工具,它们用法基本一致。

openssl rsa [-in filename] [-passin arg] [-passout arg] [-out filename] [-des|-des3|-idea] [-text] [-noout] [-pubin] [-pubout] [-check]

  • -in filename :指定密钥输入文件。默认读取的是私钥,若指定 -pubin 选项将表示读取公钥。将从该文件读取密钥,不指定时将从stdin 读取。
  • -out filename:指定密钥输出文件。默认输出私钥,若指定 -pubin 或 -pubout 选项都将输出公钥。不指定将输出到 stdout。
  • -pubin:指定该选项时,将显式表明从 -in filename 的 filename 中读取公钥,所以 filename 必须为公钥文件。不指定该选项时,默认是从filename中读取私钥。
  • -pubout:指定该选项时,将显示表明从 -in filename 的 filename 中提取公钥并输出,所以 filename 文件必须是私钥文件。不指定该选项时,默认输出私钥。当设置了 -pubin 时,默认也设置了 -pubout。
  • -noout :控制不输出任何密钥信息。
  • -text :转换输入和输出的密钥文件格式为纯文本格式。
  • -check :检查 RSA 密钥是否完整未被修改过,只能检测私钥,因为公钥来源于私钥。因此选项 -in filename 的 filename 文件只能是私钥文件。
  • -des|-des3|-idea:加密输出文件,使得每次读取输出文件时都需要提供密码。
  • -passin arg :传递解密密钥文件的密码。密码格式见 openssl 密码格式。
  • -passout arg:指定加密输出文件的密码。

openssl pkey [-passin arg] [-passout arg] [-in filename] [-out filename] [-cipher] [-text] [-noout] [-pubin] [-pubout]

  • -cipher:等价于 openssl rsa 的 -des|-des3|-idea,例如 -cipher des3

将上面生成的未加密的私钥对应的公钥提取到 public.key 文件中:

openssl rsa -in private.key -pubout -out public.key
1

将上面生成的加密的私钥的公钥提取到 public-secret.key 文件中:

openssl rsa -in private-secret.key -passin pass:test -pubout -out public-secret.key
1

解除加密的公私钥文件:

openssl rsa -in private-secret.key -passin pass:test -out private-withoutsecr
et.key
1
2

直接读取输出即可。

检查私钥文件是否被修改过:

openssl rsa -in private.key -check
1

# 使用成对的公私钥加密解密文件

由于 Go 的官方库中 RSA 只支持公钥加密私钥解密,所以这里我们借助一个开源库:

go get github.com/farmerx/gorsa
1

然后使用下面的代码进行公私钥加密解密:

package main

import (
	"errors"
	"fmt"
	"github.com/farmerx/gorsa"
	"io/ioutil"
	"os"
)

type Type string

const (
	public  Type = "public"
	private Type = "private"
)

var (
	privateKey string
	publicKey  string
)

func loadKeyFromFile(fileName string) ([]byte, error) {
	return ioutil.ReadFile(fileName)
}

func init() {
	privateKey, err := loadKeyFromFile("private.key")
	if err != nil {
		panic(err)
	}
	publicKey, err := loadKeyFromFile("public.key")
	if err != nil {
		panic(err)
	}
	err = gorsa.RSA.SetPrivateKey(string(privateKey))
	if err != nil {
		panic(err)
	}
	err = gorsa.RSA.SetPublicKey(string(publicKey))
	if err != nil {
		panic(err)
	}
}

func main() {
	stringDate, err := ioutil.ReadFile("source.yaml")
	if err != nil {
		panic(err)
	}
	bytesDate, err := ioutil.ReadFile("source.mkv")
	if err != nil {
		panic(err)
	}
	pubEncryptPriDecryptTest(stringDate, "string")
	priEncryptPubDecryptTest(stringDate, "string")
	pubEncryptPriDecryptTest(bytesDate, "bytes")
	priEncryptPubDecryptTest(bytesDate, "bytes")
}

func encrypt(source []byte, keyType Type) ([]byte, error) {
	if keyType == public {
		return gorsa.RSA.PubKeyENCTYPT(source)
	} else if keyType == private {
		return gorsa.RSA.PriKeyENCTYPT(source)
	} else {
		return nil, errors.New("not supported key type")
	}
}

func decrypt(data []byte, keyType Type) ([]byte, error) {
	if keyType == public {
		return gorsa.RSA.PubKeyDECRYPT(data)
	} else if keyType == private {
		return gorsa.RSA.PriKeyDECRYPT(data)
	} else {
		return nil, errors.New("not supported key type")
	}
}

func pubEncryptPriDecryptTest(source []byte, filePrefix string) {
	file, err := os.Create(fmt.Sprintf("%s-publicEncrypt", filePrefix))
	if err != nil {
		return
	}
	defer file.Close()
	fmt.Println("start pubEncrypt")
	encrypted, err := encrypt(source, public)
	if err != nil {
		return
	}
	fmt.Println("pubEncrypt over")
	_, err = file.Write(encrypted)
	if err != nil {
		return
	}
	fmt.Println("start priDecrypt")
	decrypted, err := decrypt(encrypted, private)
	fmt.Println("priDecrypt over")
	if err != nil {
		return
	}
	decryptedFile, err := os.Create(fmt.Sprintf("%s-privateDecrypt", filePrefix))
	if err != nil {
		return
	}
	defer decryptedFile.Close()
	_, err = decryptedFile.Write(decrypted)
	if err != nil {
		return
	}
}

func priEncryptPubDecryptTest(source []byte, filePrefix string) {
	file, err := os.Create(fmt.Sprintf("%s-privateEncrypt", filePrefix))
	if err != nil {
		return
	}
	defer file.Close()
	fmt.Println("start priEncrypt")
	encrypted, err := encrypt(source, private)
	if err != nil {
		return
	}
	fmt.Println("priEncrypt over")
	_, err = file.Write(encrypted)
	if err != nil {
		return
	}
	fmt.Println("start pubDecrypt")
	decrypted, err := decrypt(encrypted, public)
	fmt.Println("pubDecrypt over")
	if err != nil {
		return
	}
	decryptedFile, err := os.Create(fmt.Sprintf("%s-publicDecrypt", filePrefix))
	if err != nil {
		return
	}
	defer decryptedFile.Close()
	_, err = decryptedFile.Write(decrypted)
	if err != nil {
		return
	}
}
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145

上面这段代码分别对两个文件进行了加解密操作,分别使用公钥加密私钥解密以及私钥加密公钥解密。

# openssl dgst 生成和验证数字签名

数字签名的过程是计算出数字摘要,然后使用私钥对数字摘要进行签名,而摘要是使用 md5、sha512 等算法计算得出的。

openssl dgst [-md5|-sha1|...] [-hex | -binary] [-out filename] [-sign filename] [-passin arg] [-verify filename] [-prverify filename] [-signature filename] [file...]

  • file...:指定待签名的文件。
  • -hex:以 hex 格式输出数字摘要。如果不以 -hex 显示,签名或验证签名时很可能乱码。
  • -binary:以二进制格式输出数字摘要,或以二进制格式进行数字签名。这是默认格式。
  • -out filename:指定输出文件,若不指定则输出到标准输出。
  • -sign filename:使用 filename 中的私钥对 file 数字签名。签名时绝对不能加 -hex 等格式的选项,否则验证签名必失败。
  • -signature filename:指定待验证的签名文件。
  • -verify filename:使用 filename 中的公钥验证签名。
  • -prverify filename:使用 filename 中的私钥验证签名。
  • -passin arg:传递解密密码。若验证签名时实用的公钥或私钥文件是被加密过的,则需要传递密码来解密。

支持 md4、md5、ripemd160、sha、sha1、sha224、sha256、sha384、sha512、whirlpool 这几种单向加密算法,即哈希算法。

Tips

openssl dgst -md5 等价于 openssl md5

对 source.yaml 生成 md5 摘要信息及 sha512 摘要信息:

openssl dgst -md5 -out dgst.out source.yaml

openssl dgst -sha512 -out dgst.out source.yaml
1
2
3

使用一个加密过的私钥对 source.yaml 文件签名:

# 加入 -hex 参数防止输出乱码
openssl dgst -sha256 -sign private-secret.key -passin pass:123456 -out dgst.out -hex source.yaml
1
2

验证签名,注意要将签名输出到文件且不能使用 -hex 参数:

# 先生成新签名
openssl dgst -sha256 -sign private-secret.key -passin pass:123456 -out dgst.out source.yaml
# 私钥验证
openssl dgst -sha256 -prverify private-secret.key -passin pass:123456 -signature dgst.out source.yaml
# 公钥验证
openssl dgst -sha256 -verify public-secret.key -passin pass:123456 -signature dgst.out source.yaml
1
2
3
4
5
6

# openssl rsautl 和 pkeyutl

rsautl 是 rsa 的工具,相当于 rsa、dgst 的部分功能集合,可用于生成数字签名、验证数字签名、加密和解密文件。

pkeyutl 是非对称加密的通用工具。

openssl rsautl [-in file] [-out file] [-inkey file] [-pubin] [-certin] [-passin arg] [-sign] [-verify] [-encrypt] [-decrypt] [-hexdump]

openssl pkeyutl [-in file] [-out file] [-sigfile file] [-inkey file] [-passin arg] [-pubin] [-certin] [-sign] [-verify] [-encrypt] [-decrypt] [-hexdump]

  • -in file:指定输入文件。
  • -out file:指定输出文件。
  • -inkey file:指定密钥输入文件,默认是私钥文件,指定了 -pubin 则表示为公钥文件,使用 -certin 则表示为包含公钥的证书文件。
  • -pubin:指定 -inkey file 的 file 是公钥文件。
  • -certin:使用该选项时,表示 -inkey file 的 file 是包含公钥的证书文件。
  • -passin arg:传递解密密码。若验证签名时实用的公钥或私钥文件是被加密过的,则需要传递密码来解密。
  • -sign:签名并输出签名结果,注意,该选项需要提供RSA私钥文件。
  • -verify:使用验证签名文件。
  • -encrypt:使用公钥加密文件。
  • -decrypt:使用私钥解密文件。
  • -hexdump:以 hex 方式输出。
  • sigfile file:待验证的签名文件。

Note

rsautl 和 pkeyutl 的缺陷在于默认只能对短小的文件进行操作。

公钥加密、私钥解密:

# 加密
openssl rsautl -encrypt -inkey public.key -pubin -in source.yaml -out rsautl.yaml
# 解密
openssl rsautl --decrypt -inkey private.key -in rsautl.yaml -out rsautldec.yaml
1
2
3
4

# openssl enc

openssl enc 是对称加密工具。

openssl enc -ciphername [-in filename] [-out filename] [-pass arg] [-e] [-d] [-a/-base64] [-k password] [-S salt] [-salt] [-md] [-p/-P]

  • -ciphername:指定对称加密算法(如 des3),可独立于 enc 直接使用,如 openssl des3 或 openssl enc -des3。推荐在 enc 后使用,这样不依赖于硬件。
  • -in filename:输入文件,不指定时默认是 stdin。
  • -out filename:输出文件,不指定时默认是 stdout。
  • -e:对输入文件加密操作,不指定时默认就是该选项。
  • -d:对输入文件解密操作,只有显示指定该选项才是解密。
  • -pass:传递加、解密时的明文密码。若验证签名时实用的公钥或私钥文件是被加密过的,则需要传递密码来解密。
  • -k:已被 -pass 替代,现在还保留是为了兼容老版本的 openssl。
  • -base64:在加密后和解密前进行 base64 编码或解码,不指定时默认是二进制。
  • -a:等价于 -base64。
  • -salt:单向加密时使用 salt 复杂化单向加密的结果,此为默认选项,且使用随机 salt 值。
  • -S salt:不使用随机 salt 值,而是自定义 salt 值,但只能是 16 进制范围内字符的组合,即 0-9a-fA-F 的任意一个或多个组合。
  • -p:打印加解密时 salt 值、key 值和 IV 初始化向量值(也是复杂化加密的一种方式),解密时还输出解密结果,见后文示例。
  • -P:和 -p 选项作用相同,但是打印时直接退出工具,不进行加密或解密操作。
  • -md:指定单向加密算法,默认 md5。该算法是拿来加密 key 部分的,见后文分析。

进行对称加解密时,加密和解密的密码是一致的,但是如果使用明文密码非常不安全,需要增加密码的复杂度。最简单的方法就是使用哈希函数计算出明文密码的哈希值,将这个哈希值作为对称密钥。如果还想更安全可以在加密完成后再进行 base64 等编码。

对称加密机制

根据指定的哈希算法,对输入的明文密码进行单向加密,得到固定长度的加密密钥,即对称密钥,,再根据指定的对称加密算法进行加密,最后对加密的文件进行编码。

对称解密机制

先解码文件,然后根据同样的哈希算法计算密钥,然后使用对应的对称算法进行解密。

对一个视频文件进行加密解密:

# 加密
openssl enc -aes256 -in source.mkv -e -pass pass:123456 -a -md sha256 -out enc.out
# 解密
openssl enc -aes256 -in enc.out -out enc.mkv -d -md sha256 -pass pass:zzl -a
1
2
3
4

# openssl dhparam

此命令用于生成和管理 dh 文件,dh 是密钥交换协议,可以保证通信双方安全地交换密钥。不是加密算法

openssl dhparam [-in filename] [-out filename] [-dsaparam] [-noout] [-text] [-rand file(s)] [numbits]

  • -in filename:从 filename 文件中读取密钥交换协议参数。
  • -out filename:输出密钥交换协议参数到 filename 文件。
  • -dsaparam:指定此选项将使用 dsa 交换协议替代 dh 交换协议。虽然生成速度更快,但更不安全。
  • -noout:禁止输出任何信息。
  • -text:以文本格式输出 dh 协议。
  • -rand:指定随机数种子文件。
  • numbits:指定生成的长度。

DH 密钥按协商过程:

  • 双方协商一个较大的质数并共享,这个质数是种子数。
  • 双方都协商好一个加密生成器,一般是 AES。
  • 双方各自提出一个质数,这次双方提出的质数是互相保密的,这个质数被认为是私钥。
  • 双方使用自己的私钥、加密生成器以及种子数派生出一个公钥。
  • 交换派生出的公钥。
  • 接收方使用自己的私钥、种子数以及接收到的对方公钥计算出共享密钥。尽管双方的共享密钥是使用对方的公钥以及自己的私钥计算的,但因为使用的算法,能保证双方计算出的共享密钥相同。
  • 这个共享密钥将用于加密后续通信。例如,ssh 连接过程中,使用 host key 对共享密钥进行签名,然后验证指纹来完成主机认证的过程。

由此可见,在计算共享密钥过程中,双方使用的公钥、私钥是相反的。但因为 DH 算法的原因,它能保证双方生成的共享密钥是一致的。而且因为双方在整个过程中是完全平等的,没有任何一方能掌控协商的命脉,再者共享密钥没有在网络上进行传输,使得使用共享密钥做对称加密的数据传输是安全的。

Last update: April 18, 2022 12:19
Contributors: PPG007