openssl
{Back to Index}

Table of Contents

1 基本知识

1.1 安全的四个特性

机密性可以采用加密手段,摘要实现完整性,数字签名用于身份认证。

  • 机密性(Confidentiality)

    借助加密防止第三方窃听

  • 完整性/一致性(Integrity)

    借助消息认证码(MAC)保障数据完整性,防止消息篡改

  • 身份认证(Authentication)

    借助数字证书认证服务器端和客户端身份,防止身份伪造

  • 不可否认(Undeniable)

1.2 非对称算法

1.2.1 RSA

基于 整数分解 的数学难题,使用两个超大素数的乘积作为生成密钥的材料。密钥的推荐长度是 2048 以上。

1.2.2 DSA

一般用于数字签名和认证,在 DSA 数字签名和认证中,发送者使用自己的私钥对文件或消息进行签名,接受者收到消息后使用发送者的公钥来验证签名的真实性。

DSA 只是一种算法,和 RSA 不同之处在于 它不能用作加密和解密,也不能进行密钥交换,只用于签名 ,它比 RSA 要快很多。

1.2.3 ECC

基于 椭圆曲线离散对数 的数学难题,子算法 ECDHE 用于密钥交换,ECDSA 用于数字签名。

目前比较常用的两个曲线是 P-256(secp256r1,在 OpenSSL 称为 prime256v1)和 x25519。P-256 是 NIST(美国国家标准技术研究所)和 NSA(美国国家安全局)推荐使用的曲线,而 x25519 被认为是最安全、最快速的曲线。

ECC 在安全强度和性能上都有明显的优势。160 位的 ECC 相当于 1024 位的 RSA,而 224 位的 ECC 则相当于 2048 位的 RSA。因为密钥短,所以相应的计算量、消耗的内存和带宽也就少,加密解密的性能就好。

ECC 虽然定义了公钥和私钥,但不能直接实现密钥交换和身份认证,需要搭配 DH、DSA 等算法,形成专门的 ECDHE、 ECDSA。
RSA 比较特殊,本身即支持密钥交换也支持身份认证。

1.2.4 ECDH/ECDHE

ECDH是基于椭圆曲线的 DH 算法, 原理上跟 DH 基本一样 ,主要是把有限域上的模幂运算替换成了椭圆曲线上的点乘运算。相比DH算法速度更快,可逆更难。

DH和ECDH都是使用的一个固定密钥,一旦密钥泄漏,以前所有的密文消息就都都破解了。
ECDHE则提供了前向安全性,它每次使用一个临时的密钥,基于这个临时密钥进行密钥交换生成会话密钥。就算这个临时密钥泄漏了,也只影响本次SSL会话的消息。

1.3 AEAD(Authenticated Encryption with Associated Data)

在TLS1.3时代,只保留了 AEAD 类型的分组加密模式。AEAD在加密的同时增加了认证的功能,常用的有GCM、CCM、Ploy1305。

1.4 TLS 协议

Sorry, your browser does not support SVG.

SSL/TLS协议被设计为一个两阶段协议,分为握手阶段和应用阶段。

  • 握手阶段

    也称协商阶段,在这一阶段主要目标就是进行我们前面已经提到过的协商安全参数和算法套件、身份认证(基于数字证书)以及密钥交换生成后续加密通信所使用的密钥。

  • 应用阶段

    双方使用握手阶段协商好的密钥进行安全通信。

1.4.1 SSL Record

Sorry, your browser does not support SVG.

工作流如下:

  1. 记录层接收到应用层的数据
  2. 将接收到的数据分块
  3. 使用协商的MAC key计算MAC或HMAC,并增加到记录块中
  4. 使用协商的Cipher Key将记录数据加密

对于握手阶段的消息,payload是明文,当然就没有MAC和Padding。其他消息的payload都是密文。

对于AEAD算法,因为认证已经包含在算法里了 ,所以没有后面的 MAC 和 Padding 字段。Payload之前有一个外部nonce字段。

1.5 算法套件

一个算法套件是一个SSL连接中用到的这些算法类型的组合,包含如下几个部分:

  • Key Exchange(Kx)
  • Authentication(Au)
  • Encryption(Enc)
  • Message Authentication Code(Mac)

可以通过如下命令查看每种算法套件的详细情况: openssl ciphers -V | column -t | less

CipherSuite1_2.png

以ECDHE-ECDSA-AES128-GCM-SHA256为例,前面的ECDHE表示密钥交换算法,ECDSA表示身份认证算法,AES128-GCM表示对称加密算法,其中128表示块长度,GCM是其模式,SHA256表示哈希算法。对于AEAD因为消息认证和加密已经合并到一起了,最后的SHA256只表示密钥派生函数的算法,而对于传统的数据加密和认证分开的算法套件,它还表示MAC的算法。

1.5.1 TLSv1.3

对于TLSv1.3, 因为已经将密钥交换和身份认证算法从算法套件中独立出去,算法套件只表示加密算法和密钥派生函数。

CipherSuite1_3.png

1.6 密钥计算

TLS1.2_KeyCalculation.png

TLS1_3-Key-Schedule.png

其中exporter_secret是导出密钥,用于用户自定义的其他用途。resumption_master_secret用于生成ticket。client_early_traffic_secret用于推导0-RTT的early-data密钥,*_handshake_traffic_secret用于推到握手消息的加密密钥,*_application_traffic_secret_N用于推导应用消息的加密密钥。

1.7 编码格式

目前有以下两种编码格式:

1.7.1 PEM - Privacy Enhanced Mail

文本格式,以 -----BEGIN----- 开头, -----END----- 结尾,内容是 Base64 编码。

Unix服务器偏向于使用这种编码格式。

1.7.2 DER - Distinguished Encoding Rules

二进制格式不可读。

Java 和 Windows 服务器偏向于使用这种编码格式。

2 常见用法

2.1 工具命令

2.1.1 查看支持的 Cipher Suite

openssl ciphers -V | column -t

2.1.2 使用算法加解密 (openssl enc)

-in <file>     输入文件
-out <file>    输出文件
-pass <arg>    密码
-S             加盐加密
-e             加密操作
-d             解密操作
-a/-base64     是否将结果base64编码
-md            指定密钥生成的摘要算法 默认MD5
2.1.2.1 支持的算法

openssl enc -ciphers

2.1.2.2 加密
openssl enc -des3 -a -in <data.txt> -out <data.des3>

不同的密码输入方式:

# 命令行输入,密码123456
openssl enc -aes-128-cbc -in plain.txt -out out.txt -pass pass:123456

# 文件输入,密码123456
echo 123456 > passwd.txt
openssl enc -aes-128-cbc -in plain.txt -out out.txt -pass file:passwd.txt

# 环境变量输入,密码123456
passwd=123456
export passwd
openssl enc -aes-128-cbc -in plain.txt -out out.txt -pass env:passwd

# 从文件描述符输入
openssl enc -aes-128-cbc -in plain.txt -out out.txt -pass fd:1

# 从标准输入输入
openssl enc -aes-128-cbc -in plain.txt -out out.txt -pass stdin

2.1.2.3 解密
openssl enc -des3 -d -a -in <data.des3> -out <data_decrypted.txt>

2.1.3 计算文件摘要(指纹)

openssl dgst -md5 <filename>

2.1.4 生成随机密码串

openssl passwd -1 -salt <saltvalue>

-1 表示 MD5-Based Password algorithm

2.1.5 查看版本编译信息

openssl version -a

OpenSSL 0.9.8zh 14 Jan 2016
built on: Jan 23 2017
platform: darwin64-x86_64-llvm
options:  bn(64,64) md2(int) rc4(ptr,char) des(idx,cisc,16,int) blowfish(idx)
compiler: -arch x86_64 -fmessage-length=0 -pipe -Wno-trigraphs -fpascal-strings -fasm-blocks -O3 -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -DL_ENDIAN -DMD32_REG_T=int -DOPENSSL_NO_IDEA -DOPENSSL_PIC -DOPENSSL_THREADS -DZLIB -mmacosx-version-min=10.6
OPENSSLDIR: "/System/Library/OpenSSL"

2.1.6 查询可用子命令

openssl ?

2.1.7 测试各种加密算法的速度

openssl speed [ciphername]

2.2 RSA 密钥对

2.2.1 密钥生成

2.2.1.1 生成私钥
openssl genrsa -out <key.pem> 2048
# 指定私钥长度为 2048 比特,默认是 512 比特,但 512 比特长度在现今技术环境下已不够安全,
# 在被攻击的情况下,这个长度的密钥容易被黑客推算还原,可以使用 512 的整数倍值,推荐使用2048,
# 这个长度的密钥已经相对安全可靠。
openssl genrsa -aes128 -out <key.pem> 2048
# 将私钥以 AES-128 算法保护
2.2.1.2 从私钥中提取公钥
openssl rsa -in <key.pem> -pubout -out <pubkey.pem>
2.2.1.3 从证书中提取公钥

openssl x509 -pubkey -noout -in <cert.pem> > <pubkey.pem>

2.2.2 使用密钥对加解密

两次加密生成密文是不一样的,这也是 RSA 加密算法的优势体现,密文是动态的。

2.2.2.1 公钥加密
openssl rsautl -encrypt -in <data.txt> -inkey <pubkey.pem> -pubin -out <data_encrypted.txt>
# 加密时,默认导入的密钥是私钥,所以,公钥加密需要加上 -pubin 参数以表明加密操作是以公钥进行
2.2.2.2 私钥解密
openssl rsautl -decrypt -in <data_encrypted.txt> -inkey <key.pem> -out <data_decrypted.txt>

2.2.3 签名(sign)与验证(verify)

使用 RSA 密钥进行签名,实际上就是使用私钥进行加密,只是算法不同,加密对象一般是摘要。

使用 RSA 密钥进行验证,实际上就是使用公钥进行解密。

rsautl -sign 和 rsautl -encrypt 区别:

"rsautl -encrypt" and "rsautl -sign" commands use different flavors of PKCS#1 v1.5 padding:

"rsautl -encrypt" uses 0x02 as the BT (Block Type) and random bytes as padding string.
"rsautl -sign" uses 0x01 as the BT (Block Type) and 0xff bytes as padding string.

So if we are using no padding,
the only difference between "rsautl -encrypt" and "rsautl -sign" commands is what type of RSA keys they taking.
In other words, "rsautl -encrypt -raw" and "rsautl -sign -raw" are identical commands
except that the first takes RSA public keys and the second takes RSA private keys.
2.2.3.1 签名
openssl rsautl -sign -in <digest.txt> -inkey key.pem -out <sig.txt>
2.2.3.2 验证
openssl rsautl -verify -in <sig.txt> -inkey key.pem -out <digest.txt>
# 
openssl rsautl -verify -in <sig.txt> -inkey pub.pem -pubin -out <digest.txt>

2.3 制作自签署证书

自签署证书一般作为 CA 的证书,普通的证书由 CA 来签署,也可以按照该步骤制作自签署的普通证书(将自身作为 CA ):

2.3.1 第一步:生成密钥

openssl genrsa 2048 >ca.key.pem    ## 生成 2048 位私钥

2.3.2 第二步:生成自签署证书

openssl req -new -x509 -key ca.key.pem -out ca.cert.pem -days 365

2.3.3 一步到位

openssl req -x509 -sha256 -nodes -days 3650 -newkey rsa:2048 -keyout key.pam -out cert.pem -subj /CN=test.com/O=CRDC

2.4 查看证书内容

openssl x509 -text -in cert.pem

2.5 签署证书

2.5.1 第一步:部署 CA 证书

mkdir -p $CATOP/private  ## $CATOP: ./demoCA
cp ca.key.pem $CATOP/private/cakey.pem
cp ca.cert.pem $CATOP/cacert.pem

2.5.2 第二步:生成 csr

openssl req -new -key webserver.key -out webserver.csr

2.5.3 第三步:签署 csr

openssl ca -in webserver.csr -out webserver.crt

2.5.4 自动化脚本(可设置证书的有效日期)

#!/usr/bin/env bash
# -*- coding: utf-8 -*-
CATOP=./demoCA
rm -rf $CATOP
mkdir -p $CATOP/certs           #
mkdir -p $CATOP/crl             # 存放 Certificate Revoke List
mkdir -p $CATOP/newcerts        # 存放证书
mkdir -p $CATOP/private         # 存放 CA private key
touch $CATOP/index.txt          # CA log file
openssl genrsa -out usc.key.pem 2048
openssl req -new -key usc.key.pem -out usc.csr.pem \
            -subj "/C=US/ST=Califomia/L=Irvine\
                   /O=Cisco Systems, Inc./OU=Cisco Small Business/CN=Network Orchestrator"
openssl ca -selfsign -keyfile usc.key.pem -startdate 20160901000000Z \
           -days 7300 -batch -in usc.csr.pem -out usc.cert.pem -create_serial
rm -rf demoCA

2.6 证书格式转换

PEM 格式分为 PKCS#1PKCS#8

Format Type Header
PKCS#1 RSAPublicKey BEGIN RSA PUBLIC KEY
PKCS#1 RSAPrivateKey BEGIN RSA PRIVATE KEY
PKCS#8 PrivateKeyInfo BEGIN PRIVATE KEY
PKCS#8 RSAPublicKey BEGIN PUBLIC KEY
PKCS#8 EncryptedPrivateKeyInfo BEGIN ENCRYPTED PRIVATE KEY

2.6.1 PKCS#1 => PKCS#8

openssl 的 genpkey 指令会生成 PKCS#8 格式,而 genrsa 会生成 PKCS#1 格式。

2.6.1.1 私钥
openssl pkcs8 -topk8 -inform pem -in key.pem -outform pem -nocrypt -out key.pkcs8.pem
2.6.1.2 公钥
openssl rsa  -RSAPublicKey_in -in public_pkcs1.pem  -out public_pkcs8.pem

2.6.2 PKCS#8 => PKCS#1

2.6.2.1 私钥
openssl pkcs8 -in private_pkcs8.pem -nocrypt -out private_pkcs1.pem
2.6.2.2 公钥
openssl rsa -pubin -in public.pem -RSAPublicKey_out -out public_pkcs1.pem

2.6.3 PEM => PKCS#12

openssl pkcs12 -export -in ./cert.pem -inkey ./key.pem -out hello.p12

2.6.4 PEM => DER

openssl x509 -in cert.pem -outform der -out cert.der

2.6.5 DER => PEM

openssl x509 -inform der -in cert.der -outform der -out cert.pem

2.7 同时生成密钥和证书

openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout key.pem

2.8 查看服务器证书内容(包含签发者证书)

echo | openssl s_client -showcerts -connect 10.74.68.89:443 2>/dev/null \
    | sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p'

2.9 建立连接

openssl s_client -connect <ip>:<port>

2.9.1 双向认证

openssl s_client -connect <ip>:<port> -cert <cert> -key <key> -CAfile <cafile>
Verify return code: 0 (ok) 表示 CA 认证成功。
若出现类似 Verify return code: 18 (self signed certificate) ,虽然可以连接上,但 CA 认证实际是失败的。

2.10 证书验证

2.10.1 验证链

只有具备 CA:TRUE 属性的证书才能用于验证:

X509v3 extensions:
            X509v3 Subject Key Identifier:
                83:2B:32:07:7A:F4:EB:56:65:E9:E1:AF:3C:24:E8:96:5B:9F:F8:7D
            X509v3 Authority Key Identifier:
                keyid:83:2B:32:07:7A:F4:EB:56:65:E9:E1:AF:3C:24:E8:96:5B:9F:F8:7D
            X509v3 Basic Constraints:
                CA:TRUE
# root.pem 是用户信任的证书,一般位于 /etc/ssl/certs/ 中
# 因此验证证书需要提供完整的证书链
openssl verify -CAfile <(cat root.pem intermediate.pem) cert_to_verify.pem
openssl verify -CAFile root.pem -untrusted intermediate-ca-chain.pem cert_to_verify.pem

若出现: error 18 at 0 depth lookup:self signed certificate ,虽然返回 OK ,但实际验证失败。

如果系统中没有 root.pem ,只有中间证书:

openssl verify -untrusted intermediate-ca-chain.pem example.pem cert_to_verify.pem
2.10.1.1 使用 CApath
(cd /some/where/certs && c_rehash .) # usally /etc/ssl/certs
openssl verify -CApath /some/where/certs [-untrusted intermediate-ca-chain.pem] cert_to_verify.pem

2.10.2 验证签名

CERT=$1
ISSUER=$2

openssl x509 -in $ISSUER -noout -pubkey > /tmp/issuer-pub.pem

# extract hex of signature
SIGNATURE_HEX=$(openssl x509 -in $CERT -text -noout -certopt ca_default \
                        -certopt no_validity -certopt no_serial \
                        -certopt no_subject -certopt no_extensions -certopt no_signame \
                    | grep -v 'Signature Algorithm' | tr -d '[:space:]:')
# create signature dump
echo ${SIGNATURE_HEX} | xxd -r -p > /tmp/cert-sig.bin

# obtain hash function
openssl rsautl -verify -inkey /tmp/issuer-pub.pem \
        -in /tmp/cert-sig.bin -pubin > /tmp/cert-sig-decrypted.bin
hash_fun=$(openssl asn1parse -inform der -in /tmp/cert-sig-decrypted.bin \
               | grep '4:d=2  hl=2 l=   9 prim: OBJECT' \
               | awk 'BEGIN {FS = ":"} {print $4}')
echo "Hash: $hash_fun"

openssl asn1parse -in $CERT -strparse 4 -out /tmp/cert-body.bin -noout
# openssl dgst -sha256 /tmp/cert-body.bin
openssl dgst -$hash_fun -verify /tmp/issuer-pub.pem \
        -signature /tmp/cert-sig.bin /tmp/cert-body.bin

2.10.3 Verify that a private key matches a certificate

Compare the modulus of the public key in the certificate against the modulus of the private key.

openssl rsa -modulus -noout -in <key.pem> | openssl md5
openssl x509 -modulus -noout -in <cert.pem> | openssl md5

3 Reference

Author: Hao Ruan (ruanhao1116@gmail.com)

Created: 2019-12-28 Sat 00:00

Updated: 2024-01-08 Mon 17:34

Emacs 27.2 (Org mode 9.4.4)