数字证书

1、概述

数字证书集合了多种密码学算法:自身带有公钥信息,可完成相应的加密/解密操作;同时,还带有数字签名,可鉴别消息来源;且自身带有消息摘要信息,可验证证书的完整性;由于证书本身含有用户身份信息,因而具有认证性。

数字证书具备常规加密/解密必要的信息,包含签名算法,可用于网络数据加密/解密交互,标识网络用户(计算机)身份。数字证书为发布公钥提供了一种简便的途径,其数字证书则成为加密算法以及公钥的载体。依靠数字证书,我们可以构建一个简单的加密网络应用平台。

数字证书(Digital Certificate)也称为电子证书,类似于我们生活中的身份证,用于标识网络中的用户(计算机)身份。在现实生活中,我们的身份证需由公安机关的签发,而网络用户(计算机)的身份凭证则需由数字证书颁发认证机构(Certificate Authority,CA)签发,只有经过CA签发的证书在网络中才具备可认证性。

VeriSign (http://www.verisign.com/)、GeoTrust (http://www.geotrust.com/)、Thawte
(http://www.thawte.com/)是国际权威数字证书颁发认证机构的“三巨头”。其中,应用最为广泛的是VeriSign签发的电子商务用数字证书。

通常,这种由国际权威数字证书颁发认证机构颁发的数字证书需要向用户收取昂贵的申请和维护费用。但并不是所有的国际权威数字证书颁发认证机构都收费,CAcert(htp:www.cacert.org)就是一个免费的数字证书颁发国际组织。随着用户群的增大和颁发手段的可信性,这种免费的数字证书可信度也越来越高。

证书的签发过程实际上是对申请数字证书的公钥做数字签名,证书的验证过程实际上是对数字证书的公钥做验证签名,其中还包含证书有效期验证。

通过使用由CA颁发的数字证书,我们可以对网络上传输的数据进行加密/解密和签名验证操作,确保数据的机密性、完整性和抗否认性。同时,数字证书中包含的用户信息可以明确地标识交易实体身份,具有认证性,保证交易实体身份的真实性,从而保障网络应用的安全性。

实际上,数字证书是采用了公钥基础设施(Public Key Infrastructure,PKI),使用了相应的加密算法确保网络应用的安全性:

  • 非对称加密算法用于对数据进行加密/解密操作,确保数据的机密性。
  • 数字签名算法用于对数据进行签名验证操作,确保数据的完整性和抗否认性。
  • 消息摘要算法用于对数字证书本身做摘要处理,确保数字证书完整性。

数字证书有多种文件编码格式,主要包含CER编码、DER编码等:

  • CER(Canonical Encoding Rules,规范编码格式):它是BER(Basic Encoding Rules,基本编码格式)的一个变种,比BER规定得更严格。
  • DER(Distinguished Encoding Rule,卓越编码格式):同样是BER的一个变种,与CER的不同之处在于:DER使用定长模式,而CER使用变长模式。

所有证书都符合公钥基础设施(PKI)制定的ITU-TX509国际标准(X.509标准),目前已有3个版本。目前金融行业里,比较常见的是X.509V3的证书,也就是遵循X.509第三个版本的标准的数字证书。

似乎上述标准已经足够全面,但操作性不强。因而,在实际操作中为对PKI体系进行加密解密、签名、密钥交换、分发格式等操作时,须制定相应的标准,即PKCS。

PKCS(Public-Key Cryptography Standards,公钥加密标准)由RSA实验室和其他安全系统开发商为促进公钥密码的发展而制定的一系列标准。

公钥加密标准描述信息文件名后缀
PKCS#7密码消息语法标准.p7b、.p7c、.spc
PKCS#10证书请求语法标准.p10、.csr
PKCS#12个人信息交换语法标准.p12、.pfx

以上标准主要用于证书的申请和更新等操作,例如,PKCS#10文件用于证书签发申请,PKCS#12文件可作为Java中的密钥库或信任库直接使用。

可以通过KeyTool和OpenSSL生成数字证书,并由此产生响应的密钥库文件或信任库文件。目前主要有JKS和PEM两种编码格式文件:

  • JKS(Java Key Store):Java原生的密钥库/信任库文件。
  • PEM(Privacy Enbanced Mail,隐私增强邮件):使用多种加密方法提供机密性,认证和信息完整性的因特网电子有邮件,在因特网中却没有被广泛配置,但在OpenSSL中,却是最为常见的密钥库文件。

2、模型分析

2.1、证书签发

在这里插入图片描述

数字证书的颁发流程简述如下:

  1. 由数字证书需求方产生自己的密钥对
  2. 由数字证书需求方将算法,公钥和证书申请者身份信息传送给认证机构
  3. 由认证机构核实用户的身份,执行相应必要的步骤,确保请求确实由用户发送而来
  4. 由认证机构将数字证书颁发给用户。

这里的认证机构如果是证书申请者本身,将获得自签名证书。

2.2、加密交互

当客户端获得服务器下发的数字证书后,即可使用数字证书进行加密交互:

在这里插入图片描述

客户端请求服务器将按如下步骤进行:

  1. 由客户端使用公钥对数据加密
  2. 由客户端向服务器端发送加密数据
  3. 由服务器端使用私钥对数据解密

在这里插入图片描述

服务器端完成客户端请求处理后,需经理以下几个步骤完成响应:

  1. 由服务器端使用私钥对待加密数据签名
  2. 由服务器端使用私钥对数据加密
  3. 由服务器向客户端回应加密数据和数字签名
  4. 由客户端使用公钥对数据解密
  5. 由客户端使用公钥和解密数据验证签名

数字证书的最佳应用环境是在HTTPS安全协议中,使用流程远比上述加密交互流程复杂,但相关操作封装在传输层,对于应用层透明。在HTTPS安全协议中使用非对称加密算法交换密钥,使用对称加密算法对数据进行加密/解密操作,提高加密/解密效率。

3、证书管理

任何机构或个人都可以申请数字证书,并使用由CA机构颁发的数字证书为自己的应用保驾护航。要获得数字证书,我们需要使用数字证书管理工具(如KeyTool和OpenSSL)构建CSR(Certificate Signing Request,数字证书签发申请),交由CA机构签发,形成最终的数字证书。

3.1、KeyTool证书管理

KeyTool工具使用方法详解

1)、构建自签名证书

申请数字证书之前,需要在密钥库中以别名的方式生成本地数字证书,建立相应的加密算法、密钥、有效期等信息,同时需要提供用户身份信息,我们可以为自己签发一个数字证书(即自签名证书)。

在构建证书前,需要生成密钥对,也就是基于某一种非对称加密算法的公私钥。KeyTool通过“-genkeypair”命令生成密钥对:

keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 36000 -alias acton -keystore acton.keystore -dname "CN=acton, OU=acton, O=acton, L=BJ, ST=BJ, C=CN" -storepass 123456

经过上述操作后,密钥库中已经创建了数字证书。虽然这时的数字证书并没有经过CA认证,但并不影响我们使用。
我们仍可以将数字证书导出,发送给合作伙伴进行加密交互。KeyTooli通过“-exportcert’”命令导出证书:

keytool -exportcert -alias acton -keystore acton.keystore -storepass 123456 -file acton.cer -rfc

查看导出的证书内容:

keytool -printcert -file acton.cer
所有者: CN=acton, OU=acton, O=acton, L=BJ, ST=BJ, C=CN
发布者: CN=acton, OU=acton, O=acton, L=BJ, ST=BJ, C=CN
序列号: 28ab6921
有效期为 Mon Nov 27 17:22:38 CST 2023 至 Sun Jun 21 17:22:38 CST 2122
证书指纹:
	 MD5:  20:DC:B9:57:65:B1:77:FE:07:33:D6:7B:BE:E5:4A:AC
	 SHA1: 00:1A:D4:2A:A9:F0:56:D7:33:41:4D:05:BA:8A:6B:7E:E4:E7:7D:12
	 SHA256: 38:0F:24:D3:FA:E4:49:D6:D2:B8:E7:84:ED:4A:86:D2:93:4E:9E:5D:56:EA:6F:9F:C3:E9:85:3A:14:F9:E2:AE
签名算法名称: SHA1withRSA
主体公共密钥算法: 2048 位 RSA 密钥
版本: 3

扩展:

#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 07 F6 FF 44 6D CB 88 E1   64 5C BD 09 FB 3D 83 31  ...Dm...d\...=.1
0010: B0 63 5B D5                                        .c[.
]
]

这里通过KeyTool工具直接导出的证书,是一个自签名的X.509第三版类型的根证书,并以Base64编码保存。自签名证书虽然可以使用,但未经过CA机构认证,几乎没有任何法律效力,也毫无安全可言。在使用自签名证书时,我们需要将其导入系统。

2)、构建CA签名证书

如果要获取CA机构认证的数字证书,需要生成数字证书签发申请(CSR),经由CA机构认
证并颁发,同时将认证后的证书导入本地密钥库和信任库。
KeyTooli通过“-certreq”命令生成证书请求:

keytool -certreq -alias acton -keystore acton.keystore -storepass 123456 -file acton.csr -v

执行上述命令后,将得到一个PKCS#10编码格式的数字证书签发申请文件,即文件acton.csr

目前,由VeriSign(http:/www.verisign.com/)、GeoTrust(http:/www.geotrust.com/)和
Thawte(htp:/www.thawte.com/)国际权威数字证书颁发认证机构“三巨头”签发的数字证书价格不菲,但这三大认证机构几乎都提供了用于测试的数字证书,可以提交CSR文件内容,获得相应的签发数字证书。这些用于测试的数字证书通常在时效上扩展功能等方面有限制。

所以,我们目前只能使用KeyTool工具,自己给自己签发:

keytool -gencert -infile acton.csr -outfile acton_sign.cer -alias acton -keystore acton.keystore -storepass 123456

获得签发的数字证书后,需要将其导人信任库。KeyTooli通过“-importcert”命令导人证书:

keytool -importcert -trustcacerts -alias acton_sign -file acton_sign.cer -keystore acton.keystore -storepass 123456

导人证书后,我们可以通过相关命令查看该证书。KeyTool通过“-list”命令列出密钥库中的条目:

keytool -list -keystore acton.keystore -storepass 123456
密钥库类型: jks
密钥库提供方: SUN

您的密钥库包含 2 个条目

acton_sign, 2023-11-27, trustedCertEntry,
证书指纹 (SHA1): 21:2B:9F:12:14:75:0E:A8:EA:CB:C8:C9:F6:88:B7:45:D9:3D:CD:24
acton, 2023-11-27, PrivateKeyEntry,
证书指纹 (SHA1): 86:43:64:83:AC:3A:AE:F1:9D:BB:CE:E5:C1:07:8C:A5:2A:F0:4E:3E

3.2、OpenSSL证书管理

OpenSSL命令详解

OpenSSL常用示例

OpenSSL(http:/www.openssl.org/)是一个开放源代码软件包,由Eric A.Young和TimJ.Hudson等人编写,实现了SSL及相关加密技术,是最常用的证书管理工具。OpenSSL功能远胜于KeyTool,可用于根证书、服务器证书和客户证书的管理。

4.1、准备工作

创建工作目录:

mkdir ca 

创建子目录:

# 已发行证书存放目录
mkdir certs

# 新证书存放目录
mkdir newcerts

# 私钥存放目录
mkdir private

# 证书吊销列表存放目录
mkdir crl

创建相关文件:

# 索引文件
touch index.txt

# 序列号文件 
echo 01 > serial

配置文件中指定工作目录:

####################
##  证书签发配置  ##
####################
# openssl 的 ca 命令实现了证书签发的功能,其相关选项的默认值就来自于这里的设置。
# 这个字段只是通过唯一的 default_ca 变量来指定默认CA主配置字段的入口(-name 命令行选项的默认值)
[ ca ]
default_ca = CA_default

##### 默认CA主配置字段,()标记表示必需项 #####
[ CA_default ]

# 保存所有信息的文件夹,这个变量只是为了给后面的变量使用
dir = /Users/acton_zhang/Note/ca

#()存放新签发证书的默认目录,证书名就是该证书的系列号,后缀是.pem 。对应 -outdir 命令行选项。
new_certs_dir = $dir/newcerts

#()存放CA自身证书的文件名。对应 -cert 命令行选项。
certificate = $dir/cacert.pem

#()存放CA自身私钥的文件名。对应 -keyfile 命令行选项。
private_key = $dir/private/cakey.pem

# 新签发的证书默认有效期,以天为单位。依次对应 -days , -startdate , -enddate 命令行选项。
default_days = 365
# 新证书默认的生效日期,如果未设置则使用签发时的时间,格式为:YYMMDDHHNNSSZ(年月日时分秒Z)
#default_startdate = 080303223344Z
# 新证书默认的失效日期,格式为:YYMMDDHHNNSSZ(年月日时分秒Z)
#default_enddate = 090303223344Z

# 从当前CRL(证书撤销列表)到下次CRL发布的间隔小时数或天数。依次对应 -crlhours , -crldays 命令行选项。
#default_crl_hours = 72
default_crl_days = 30

# 签发新证书以及CRL时默认的摘要算法,所有 dgst 命令都可以使用(sha256, sha3-256, sha512, sha3-512, ...)
#()此处的设置相当于在命令行上使用 -sha256 选项(对应于"-digest")。
default_md = sha256

#()保存已签发证书的文本数据库文件,初始时可为空。
database = $dir/index.txt

# 同一个"subject"是否只能创建一个证书,默认值为 yes 但建议设为 no 以方便根CA自签名( -selfsign )。
unique_subject = no

#()签发证书时使用的序列号文本文件,里面必须包含下一个可用的16进制数字。
serial = $dir/serial

# 存放当前CRL编号的文件
crlnumber = $dir/crlnumber

# 定义X.509证书扩展项的字段。对应 -extensions 命令行选项。
x509_extensions = usr_cert

# 定义生成CRL时需要加入的扩展项字段。对应 -crlexts 命令行选项。
#crl_extensions = crl_ext

# 通常,证书签发的特种名称(DN)域的各个参数顺序与证书策略的参数顺序一致。
# 但是,如果这里设为yes则保持与证书请求中的参数顺序一致。对应 -preserveDN 命令行选项。
preserve = no

# 默认值为 yes ,设为 no 表示从证书的DN中删除 EMAIL 字段。对应 -noemailDN 命令行选项。
email_in_dn = yes

# 强烈建议不要使用它,对应 -msie_hack 命令行选项。
#msie_hack =

#()定义用于证书请求DN域匹配策略的字段,用于决定CA要求和处理证书请求提供的DN域的各个参数值的规则。
# 对应 -policy 命令行选项。
policy  = policy_match

# 当用户需要确认签发证书时可读证书DN域的显示格式。可用值与 x509 指令的 -nameopt 选项相同。
name_opt = ca_default
# 当用户需要确认签发证书时证书域的显示格式。
# 可用值与 x509 指令的 -certopt 选项相同,不过 no_signame 和 no_sigdump 总被默认设置。
cert_opt  = ca_default

# 是否将证书请求中的扩展项信息加入到证书扩展项中去。取值范围以及解释:
# none: 忽略所有证书请求中的扩展项
# copy: 将证书扩展项中没有的项目复制到证书中
# copyall: 将所有证书请求中的扩展项都复制过去,并且覆盖证书扩展项中原来已经存在的值。
# 此选项的主要用途是允许证书请求提供例如 subjectAltName 之类扩展的值。
copy_extensions = copy

##### 为签发的证书设置扩展项 #####
[ usr_cert ]

# 基本约束(该证书是否为CA证书)。"CA:FALSE"表示非CA证书(不能签发其他证书的"叶子证书")。
#basicConstraints = CA:FALSE

# 一般只能给常规证书这些用途
#keyUsage = nonRepudiation, digitalSignature, keyEncipherment

# PKIX工作组推荐将使用者与颁发机构的密钥标识符包含在证书中
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer

##### 证书请求信息的匹配策略 #####
# 变量名称是DN域对象的名称,变量值可以是:
# match: 该变量在证书请求中的值必须与CA证书相应的变量值完全相同,否则拒签。
# supplied: 该变量在证书请求中必须提供(值可以不同),否则拒签。
# optional: 该变量在证书请求中可以存在也可以不存在(相当于没有要求)。
# 除非preserve=yes或者在ca命令中使用了-preserveDN,否则在签发证书时将删除匹配策略中未提及的对象。
[ policy_match ]
countryName  = match
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName  = supplied
emailAddress  = optional
4.2、构建根证书

构建随机数文件.rand

openssl rand -out private/.rand 1000

OpenSSL通常使用PEM(privacy Enbanced Mail,隐私增强邮件)编码格式保存私钥,接下来构建根证书密钥ca.key.pem,使用aes256加密,口令为123456

openssl genrsa -aes256 -passout pass:123456 -out private/ca.key.pem 2048

根据密钥生成根证书证书请求文件ca.csr

openssl req -new -key private/ca.key.pem -passin pass:123456 -out private/ca.csr -passout pass:123456 -subj "/C=CN/ST=BJ/L=BJ/O=acton/OU=acton/CN=*.acton.zhang"

得到根证书申请文件之后,可以将其发给CA机构签发。当然,也可以自行签发根证书ca.cer

openssl x509 -req -days 3650 -sha1 -extensions v3_ca -signkey private/ca.key.pem -in private/ca.csr -passin pass:123456 -out certs/ca.cer 

OpenSSL产生的数字证书不能在Java语言环境中直接使用,需要将其转化为PKCS#12编码格式ca.p12

# 将私钥和证书打包为pkcs12
openssl pkcs12 -export -password pass:123456 -cacerts -inkey private/ca.key.pem -passin pass:123456 -in certs/ca.cer -out certs/ca.p12

个人信息交换文件(PKCS#12)可以作为密钥库或信任库使用,可以通过KeyTool查看该密钥库的详细信息:

keytool -list -keystore certs/ca.p12 -storetype pkcs12 -v -storepass 123456
密钥库类型: PKCS12
密钥库提供方: SunJSSE

您的密钥库包含 1 个条目

别名: 1
创建日期: 2023-12-4
条目类型: PrivateKeyEntry
证书链长度: 1
证书[1]:
所有者: CN=*.acton.zhang, OU=acton, O=acton, L=BJ, ST=BJ, C=CN
发布者: CN=*.acton.zhang, OU=acton, O=acton, L=BJ, ST=BJ, C=CN
序列号: afac69114c1e8bd4
有效期为 Mon Dec 04 12:05:22 CST 2023 至 Thu Dec 01 12:05:22 CST 2033
证书指纹:
	 MD5:  13:50:E1:EC:F1:B0:F8:52:5F:29:6A:4C:A1:08:C2:13
	 SHA1: 02:29:4C:0B:E9:B9:BC:13:71:7E:08:5D:71:03:48:4A:83:DE:C3:79
	 SHA256: 26:22:24:65:60:4F:A6:93:2C:5E:6E:25:E3:DF:34:61:78:9A:0E:3E:52:92:8B:84:43:5B:31:21:14:B7:21:E8
签名算法名称: SHA1withRSA
主体公共密钥算法: 2048 位 RSA 密钥
版本: 1


*******************************************
*******************************************

现在我们已经构建了根证书(ca.cer),可以使用根证书签发服务器根证书和客户证书。

4.3、构建服务器证书

服务器证书的构建与根证书构建相似,首先需要构建私钥server.key.pem

openssl genrsa -aes256 -passout pass:123456 -out private/server.key.pem 2048 

根据密钥,生成服务器证书签发申请server.csr

openssl req -new -key private/server.key.pem -passin pass:123456 -out private/server.csr -passout pass:123456 -subj "/C=CN/ST=BJ/L=BJ/O=acton/OU=acton/CN=www.acton.com"

使用根证书签发服务器证书:

openssl x509 -req -days 3650 -sha1 -extensions v3_req -CA certs/ca.cer -CAkey private/ca.key.pem -passin pass:123456 -CAserial ca.srl -CAcreateserial -in private/server.csr -out certs/server.cer

各个参数的含义如下:

x509		签发X.509格式证书命令
-req		证书输入请求
-days		有效天数,这里为3650
-sha1		证书摘要算法,这里为SHA1算法
-extensions	按OpenSSL配置文件v3_req项添加扩展
-CA			CA证书,这里为certs/ca.cer
-CAkey		CA证书密钥,这里为private/ca.key.pm
-passin		私钥的口令
-CAserial 	CA证书序列号文件,这里为ca.srl
-CAcreateserial 创建CA证书序列号文件
-in			输入文件,这里为private/server.csr
-out		输出文件,这里为certs/server.cer

同样需要将OpenSSL产生的数字证书转换为PKCS#12编码格式:

# 将私钥和证书打包为pkcs12
openssl pkcs12 -export -password pass:123456 -cacerts -inkey private/server.key.pem -passin pass:123456 -in certs/server.cer -out certs/server.p12

现在已经构建了服务器证书(server.cer),并可使用该证书构建基于单向认证的网络交互平台。

4.4、构建客户证书

客户证书的构建与服务器证书构建基本一致,首先需要构建私钥:

openssl genrsa -aes256 -out private/client.key.pem -passout pass:123456 2048

构建客户证书签发请求:

openssl req -new -key private/client.key.pem -passin pass:123456 -out private/client.csr -passout pass:123456 -subj "/C=CN/ST=BJ/L=BJ/O=acton/OU=acton/CN=acton"

使用根证书签发客户证书:

openssl ca -days 3650 -in private/client.csr -out certs/client.cer -cert certs/ca.cer -keyfile private/ca.key.pem
Using configuration from /private/etc/ssl/openssl.cnf
Enter pass phrase for private/ca.key.pem:
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number: 1 (0x1)
        Validity
            Not Before: Dec  4 05:07:12 2023 GMT
            Not After : Dec  1 05:07:12 2033 GMT
        Subject:
            countryName               = CN
            stateOrProvinceName       = BJ
            localityName              = BJ
            organizationName          = acton
            organizationalUnitName    = acton
            commonName                = acton
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                E8:86:FC:7D:D5:F3:4E:BD:84:22:3A:18:D3:E3:B7:1B:CA:03:DE:C9
            X509v3 Authority Key Identifier:
                DirName:/C=CN/ST=BJ/L=BJ/O=acton/OU=acton/CN=*.acton.zhang
                serial:AF:AC:69:11:4C:1E:8B:D4

Certificate is to be certified until Dec  1 05:07:12 2033 GMT (3650 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

将客户证书转换为Java语言可以识别的PKCS#12编发格式:

# 将私钥和证书打包为pkcs12
openssl pkcs12 -export -password pass:123456 -cacerts -inkey private/client.key.pem -passin pass:123456 -in certs/client.cer -out certs/client.p12

至此,完成了双向认证的所需的全部证书。

4、证书文件操作

4.1、JKS文件操作

Java7提供了完善的数字证书管理实现,我们几乎无需关注相关具体算法,仅通过操作密钥库和数字证书就可完成相应的加密/解密和签名验证操作。密钥库管理私钥,数字证书管理公钥,私钥和密钥分属消息传递两方,进行加密消息传递。

因此,我们可以将密钥库看做私钥相关操作的入口,数字证书则是公钥相关操作的入口。

public class CertificateCoder {

    //证书类型X.509
    public static final String CERT_TYPE = "X.509";

    /**
     * 获得KeyStore
     * @param keyStorePath 密钥库路径
     * @param password 密码
     * @return KeyStore 密钥库
     * @throws Exception
     */
    private static KeyStore getKeyStore(String keyStorePath, String password) throws Exception {
        //实例化密钥库
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        //获得密钥库文件流
        FileInputStream is = new FileInputStream(keyStorePath);
        //加载密钥库
        ks.load(is, password.toCharArray());
        //关闭密钥库文件流
        is.close();
        return ks;
    }

    /**
     * 获取证书
     * @param certificatePath 证书路径
     * @return Certificate 证书
     * @throws Exception
     */
    private static Certificate getCertificate(String certificatePath) throws Exception {
        //实例化证书工厂
        CertificateFactory certificateFactory = CertificateFactory.getInstance(CERT_TYPE);
        //获得证书文件流
        FileInputStream in = new FileInputStream(certificatePath);
        //生成证书
        Certificate certificate = certificateFactory.generateCertificate(in);
        //关闭证书文件流
        in.close();
        return certificate;
    }

    /**
     * 获取证书
     * @param keyStorePath 密钥库路径
     * @param alias 别名
     * @param password 密码
     * @return Certificate 证书
     * @throws Exception
     */
    private static Certificate getCertificate(String keyStorePath, String alias, String password) throws Exception {
        //获得密钥库
        KeyStore keyStore = getKeyStore(keyStorePath, password);
        //获得证书
        return keyStore.getCertificate(alias);
    }

    /**
     * 由KeyStore获取私钥
     * @param keyStorePath 密钥库路径
     * @param alias 别名
     * @param password 密码
     * @return PrivateKey 私钥
     * @throws Exception
     */
    private static PrivateKey getPrivateKeyByKeyStore(String keyStorePath, String alias, String password) throws Exception {
        //获得密钥库
        KeyStore ks = getKeyStore(keyStorePath, password);
        //获得私钥
        return (PrivateKey) ks.getKey(alias, password.toCharArray());
    }

    /**
     * 由Certificate获得公钥
     * @param certificatePath 证书路径
     * @return PublicKey 公钥
     * @throws Exception
     */
    private static PublicKey getPublicKeyByCertificate(String certificatePath) throws Exception {
        //获取证书
        Certificate certificate = getCertificate(certificatePath);
        //获得公钥
        return  certificate.getPublicKey();
    }

    /**
     * 私钥加密
     * @param data 待加密数据
     * @param keyStorePath 密钥库路径
     * @param alias 别名
     * @param password 密码
     * @return byte[] 解密后的数据
     * @throws Exception
     */
    public static byte[] encryptByPrivateKey(byte[] data, String keyStorePath, String alias, String password) throws Exception {
        //获取私钥
        PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias, password);
        //对数据加密
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 私钥解密
     * @param data 待解密数据
     * @param keyStorePath 密钥库路径
     * @param alias 别名
     * @param password 密码
     * @return byte[] 解密后的数据
     * @throws Exception
     */
    public static byte[] decryptByPrivateKey(byte[] data, String keyStorePath, String alias, String password) throws Exception {
        //获取私钥
        PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias, password);
        //对数据解密
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥加密
     * @param data 待加密数据
     * @param certificatePath 证书路径
     * @return byte[] 加密后的数据
     * @throws Exception
     */
    public static byte[] encryptByPublicKey(byte[] data, String certificatePath) throws Exception {
        //获取公钥
        PublicKey publicKey = getPublicKeyByCertificate(certificatePath);
        //对数据加密
        Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥解密
     * @param data 待解密数据
     * @param certficatePath 证书路径
     * @return byte[] 解密后的数据
     * @throws Exception
     */
    public static byte[] decryptByPublicKey(byte[] data, String certficatePath) throws Exception {
        //获取公钥
        PublicKey publicKey = getPublicKeyByCertificate(certficatePath);
        //对数据加密
        Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    /**
     * 签名
     * @param sign 数据
     * @param keyStorePath 密钥库路径
     * @param alias 别名
     * @param password 密码
     * @return byte[] 签名
     * @throws Exception
     */
    public static byte[] sign(byte[] sign, String keyStorePath, String alias, String password) throws Exception {
        //获取证书
        X509Certificate x509Certificate = (X509Certificate) getCertificate(keyStorePath, alias, password);
        //构建签名,由证书指定签名算法
        Signature signature = Signature.getInstance(x509Certificate.getSigAlgName());
        //获取私钥
        PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias, password);
        //初始化签名,由私钥构建
        signature.initSign(privateKey);
        signature.update(sign);
        return signature.sign();
    }

    /**
     * 验证签名
     * @param data 数据
     * @param sign 签名
     * @param certificatePath 证书路径
     * @return boolean 验证通过为真
     * @throws Exception
     */
    public static boolean verify(byte[] data, byte[] sign, String certificatePath) throws Exception {
        //获得证书
        X509Certificate x509Certificate = (X509Certificate) getCertificate(certificatePath);
        //由证书构建签名
        Signature signature = Signature.getInstance(x509Certificate.getSigAlgName());
        //由证书初始化签名,实际上是使用了证书中的公钥
        signature.initVerify(x509Certificate);
        signature.update(data);
        return signature.verify(sign);
    }



    public static void main(String[] args) throws Exception{
        String str = "hello world";

        System.out.println("******************公钥加密私钥解密****************");
        byte[] encryptByPublicKey = encryptByPublicKey(str.getBytes(), "jks/acton.cer");
        System.out.println("公钥加密后的数据:" + HexUtil.encodeHexStr(encryptByPublicKey));
        byte[] decryptByPrivateKey = decryptByPrivateKey(encryptByPublicKey, "jks/acton.keystore", "acton", "123456");
        System.out.println("私钥解密后的数据:" + new String(decryptByPrivateKey));

        System.out.println("******************私钥加密公钥解密****************");
        byte[] encryptByPrivateKey = encryptByPrivateKey(str.getBytes(), "jks/acton.keystore", "acton", "123456");
        System.out.println("私钥加密后的数据:" + HexUtil.encodeHexStr(encryptByPrivateKey));
        byte[] decryptByPublicKey = decryptByPublicKey(encryptByPrivateKey, "jks/acton.cer");
        System.out.println("公钥解密后的数据:" + new String(decryptByPrivateKey));

        System.out.println("******************签名和验签****************");
        byte[] sign = sign(str.getBytes(), "jks/acton.keystore", "acton", "123456");
        System.out.println("签名后的数据:" + HexUtil.encodeHexStr(sign));
        boolean success = verify(str.getBytes(), sign, "jks/acton.cer");
        System.out.println("验签后的结果:" + success);

    }
}

4.2、PFX文件操作(PKCS12)

Java遵循PKCS标准,与JKS相对应的PFX密钥库格式名称为PKCS12。其余操作与JKS文件操作一致。

PFX文件由OpenSSL产生,可通过KeyTool导入至JKS密钥库/信任库中,由此完成密钥库的统一。

keytool -importkeystore -v -srckeystore ../ca/certs/ca.p12 -srcstoretype pkcs12 -srcstorepass 123456 -destkeystore acton.keystore -deststoretype jks -deststorepass 123456
# 参数
-importkeystore	导入密钥库,通过格式设定可以将PKCS12文件转换为JKS格式
-v 				显示详情
-srckeystore	源密钥库,这里是 ../ca/certs/ca.p12
-srcstoretype	源密钥库格式,这里为pkcs12
-srcstorepass	源密钥库密码,这里为123456
-destkeystore	目标密钥库,这里为 acton.keystore
-deststoretype	目标密钥库格式,这里为JKS,默认值
-deststorepass	目标密钥库密码,这里为123456

查看当前密钥库证书条目信息:

keytool -list -keystore acton.keystore -storepass 123456 -v
别名: 1
创建日期: 2023-12-4
条目类型: PrivateKeyEntry
证书链长度: 1
证书[1]:
所有者: CN=*.acton.zhang, OU=acton, O=acton, L=BJ, ST=BJ, C=CN
发布者: CN=*.acton.zhang, OU=acton, O=acton, L=BJ, ST=BJ, C=CN
序列号: afac69114c1e8bd4
有效期为 Mon Dec 04 12:05:22 CST 2023 至 Thu Dec 01 12:05:22 CST 2033
证书指纹:
	 MD5:  13:50:E1:EC:F1:B0:F8:52:5F:29:6A:4C:A1:08:C2:13
	 SHA1: 02:29:4C:0B:E9:B9:BC:13:71:7E:08:5D:71:03:48:4A:83:DE:C3:79
	 SHA256: 26:22:24:65:60:4F:A6:93:2C:5E:6E:25:E3:DF:34:61:78:9A:0E:3E:52:92:8B:84:43:5B:31:21:14:B7:21:E8
签名算法名称: SHA1withRSA
主体公共密钥算法: 2048 位 RSA 密钥
版本: 1

注意:这里pkcs12文件导入到JKS文件后,默认的别名就是数字1。如果直接使用改密钥库文件,请注意【别名名称】

//使用pkcs12导jks的证书
//获取私钥
PrivateKey privateKey = getPrivateKeyByKeyStore("jks/acton.keystore", "1", "123456");
//获取证书
Certificate certificate = getCertificate("jks/acton.keystore", "1", "123456");
System.out.println(privateKey);
//获取公钥
PublicKey publicKey = certificate.getPublicKey();
//私钥加密
Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] bytes = cipher.doFinal(str.getBytes());
System.out.println("加密后的数据:" + HexUtil.encodeHexStr(bytes));
//公钥解密
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] bytes1 = cipher.doFinal(bytes);
System.out.println("解密后的数据:" + new String(bytes1));

4.3、PEM文件操作

JKS和PFX都可以通过各种方法获取其密钥对,但是OpenSSL生成的PEM文件又该如何操作获取其密钥对呢?在实际应用中,我们的密钥库文件很可能是由OpenSSL直接生成的PEM文件,这种情况在C++构建的密码系统中最为常见。

1)、bouncycastle实现(推荐)

为了解析PEM文件获取密钥对,我们可以使用BouncyCastale提供的相关实现。

<dependency>
     <groupId>org.bouncycastle</groupId>
     <artifactId>bcprov-jdk15on</artifactId>
     <version>1.60</version>
 </dependency>
 <dependency>
     <groupId>org.bouncycastle</groupId>
     <artifactId>bcpkix-jdk15on</artifactId>
     <version>1.60</version>
 </dependency>
public class PEMCoder {

    //公钥
    private static final String PUBLIC_KEY = "PublicKey";

    //私钥
    private static final String PRIVATE_KEY = "PrivateKey";

    /**
     * 获取密钥对
     * @param pemFile PEM文件
     * @param password 口令
     * @return KeyPair 密钥对
     * @throws Exception
     */
    public static KeyPair readKeyPair(File pemFile, char[] password) throws Exception {
        //添加BouncyCastle支持
        Security.addProvider(new BouncyCastleProvider());
        //构建PEMParser
        PEMParser pemParser = new PEMParser(new FileReader(pemFile));
        //获得PEM密钥对对象
        Object object = pemParser.readObject();
        //关闭PEMParser
        pemParser.close();
        //构建PEMDecryptorProvider实例
        PEMDecryptorProvider  decryptorProvider = new JcePEMDecryptorProviderBuilder().build(password);
        //构建JcaPEMKeyConverter实例
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
        //获得密钥对
        KeyPair kp = null;
        if (object instanceof PEMEncryptedKeyPair) {
            kp = converter.getKeyPair(((PEMEncryptedKeyPair)object).decryptKeyPair(decryptorProvider));
        } else {
            kp = converter.getKeyPair((PEMKeyPair)object);
        }
        return kp;
    }

    /**
     * 获取KeyPair
     * @param pemFileName pem文件
     * @param password 口令
     * @return KeyPair 密钥对
     * @throws Exception
     */
    public static KeyPair readKeyPair(String pemFileName, char[] password) throws Exception {
        return readKeyPair(new File(pemFileName), password);
    }

    /**
     * 封装密钥到Map中
     * @param keyPair
     * @return
     */
    public static Map<String, Key> initKeyPair(KeyPair keyPair) {
        Map<String, Key> map = new HashMap<>();
        map.put(PUBLIC_KEY, keyPair.getPublic());
        map.put(PRIVATE_KEY, keyPair.getPrivate());
        return map;
    }

    /**
     * 获取公钥
     */
    public static byte[] getPublicKey(Map<String, Key> keyMap){
        Key key = keyMap.get(PUBLIC_KEY);
        return key.getEncoded();
    }

    /**
     * 获取私钥
     */
    public static byte[] getPrivateKey(Map<String, Key> keyMap){
        Key key = keyMap.get(PRIVATE_KEY);
        return key.getEncoded();
    }


    /**
     * 私钥加密
     * @param data 待加密的数据
     * @param privateKey 私钥
     * @return byte[] 加密后的数据
     * @throws Exception
     */
    public static byte[] encryptByPrivateKey(byte[] data, PrivateKey privateKey) throws Exception {
        //对数据加密
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 私钥解密
     * @param data 待解密的数据
     * @param privateKey 私钥
     * @return byte[] 解密后的数据
     * @throws Exception
     */
    public static byte[] decryptByPrivateKey(byte[] data, PrivateKey privateKey) throws Exception {
        //对数据解密
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥加密
     * @param data 待加密的数据
     * @param publicKey 公钥
     * @return byte[] 加密后的数据
     * @throws Exception
     */
    public static byte[] encryptByPublicKey(byte[] data, PublicKey publicKey) throws Exception {
        //对数据加密
        Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥解密
     * @param data 待解密的数据
     * @param publicKey 公钥
     * @return byte[] 解密后的数据
     * @throws Exception
     */
    public static byte[] decryptByPublicKey(byte[] data, PublicKey publicKey) throws Exception {
        //对数据加密
        Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    /**
     * 私钥签名
     * @param sign 待签名的数据
     * @param privateKey 私钥
     * @return byte[] 签名后的数据
     * @throws Exception
     */
    public static byte[] sign(byte[] sign, PrivateKey privateKey ) throws Exception {
        //构建签名,由证书指定签名算法
        Signature signature = Signature.getInstance(privateKey.getAlgorithm());
        //初始化签名,由私钥构建
        signature.initSign(privateKey);
        signature.update(sign);
        return signature.sign();
    }

    /**
     * 公钥验证
     * @param data 原数据
     * @param sign 签名数据
     * @param publicKey 公钥
     * @return boolean 验证结果
     */
    public static boolean verify(byte[] data, byte[] sign, PublicKey publicKey) throws Exception {
        //由证书构建签名
        Signature signature = Signature.getInstance(publicKey.getAlgorithm());
        //由证书初始化签名,实际上是使用了证书中的公钥
        signature.initVerify(publicKey);
        signature.update(data);
        return signature.verify(sign);
    }


    public static void main(String[] args) throws Exception {
        //从PEM文件中获取密钥对
        KeyPair keyPair = readKeyPair("jks/ca.key.pem", "123456".toCharArray());
        //公钥
        PublicKey publicKey = keyPair.getPublic();
        //私钥
        PrivateKey privateKey = keyPair.getPrivate();

        String str = "hello world";

        System.out.println("******************公钥加密私钥解密****************");
        byte[] encryptByPublicKey = encryptByPublicKey(str.getBytes(), publicKey);
        System.out.println("公钥加密后的数据:" + HexUtil.encodeHexStr(encryptByPublicKey));
        byte[] decryptByPrivateKey = decryptByPrivateKey(encryptByPublicKey, privateKey);
        System.out.println("私钥解密后的数据:" + new String(decryptByPrivateKey));

        System.out.println("******************私钥加密公钥解密****************");
        byte[] encryptByPrivateKey = encryptByPrivateKey(str.getBytes(), privateKey);
        System.out.println("私钥加密后的数据:" + HexUtil.encodeHexStr(encryptByPrivateKey));
        byte[] decryptByPublicKey = decryptByPublicKey(encryptByPrivateKey, publicKey);
        System.out.println("公钥解密后的数据:" + new String(decryptByPrivateKey));

        System.out.println("******************签名和验签****************");
        byte[] sign = sign(str.getBytes(), privateKey);
        System.out.println("签名后的数据:" + HexUtil.encodeHexStr(sign));
        boolean success = verify(str.getBytes(), sign, publicKey);
        System.out.println("验签后的结果:" + success);
    }
}
2)、Java手动解析(不推荐)

X.509 是定义公钥证书格式的标准。 因此,这种格式描述了其他信息中的公钥。

DER是最流行的编码格式,用于在文件中存储 X.509 证书、PKCS8 私钥等数据。这是一种二进制编码,无法使用文本编辑器查看生成的内容。

PKCS8是用于存储私钥信息的标准语法。可以选择使用对称算法对私钥进行加密。

该标准不仅可以处理 RSA 私钥,还可以处理其他算法。PKCS8 私钥通常通过 PEM 编码格式进行交换。

PEM是 DER 证书的 base-64 编码机制。PEM 还可以对其他类型的数据进行编码,例如公钥/私钥和证书请求。

从PEM格式的私钥中提取公钥:

openssl rsa -in ca.key.pem -passin pass:123456 -out ca.pub.pem -passout pass:123456 -pubout

PEM格式的私钥如下:

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,4E9FF60E920D352835D1D4A608FE7C8A

3htGygbebF5PBSvObq348mfoj8UnyBHqXAwhsFh3y7PTstghkp2lLK7cCRnTbMYw
Z7RHHsf5C5Qy/b/kJ3gv18FiRo/GCka2zAox1BxaP1pNwOpf6vlZ9KeqmdMrtmMa
dpUqt/3XZWaYRBwd7Hv+Zxm0Nadb1dT8nhb0zZEuUPiNScIydtmmLrC0afAbo03h
gjHhMkHh6JYzTFnMTbvi6LGu010ZAJt/dKsDJ7lZjdNtFMZEtvaL6BWz1YMQAdpf
FIyxa5Pi/z1Q0x42Lh9dpHXpmCfgGPypbxuGMnNudqO/bVe+KPD9ELPgJ920hmbw
rCigwcuh/J5cEuClMeN647Jdfz2L5ACA0UUloSTn/qY/UzPrGPMe9IMLpnI8wZxb
Fnxpc/r8Qi5MU0EMRgirG2K1e0VSuAUIh2MFXeL1jCNyOfKoGNnI0/LogzRzGquj
SQZm17Rqh/MV3YcnHxPInDa5iXhwijwW9/jmu4kt2u3pz46F8INQEjlnqojpDCMM
EfR+505HqYM6YkrOEKGEHtpQOQp+XvJudFRCH5PwDKnLYK+z6pDliOjhLvkGmIO/
3raC/8C9dAUykv78+xhaonBO3pGrTUwOmKnwV9qx1S73Sn9BDtNZT6PRH0RxnNXs
vQwntJP5+Jo5GOADUCSi5RIZwRTDocQXz5Lw6QJpVRkP/qaLW6UvK7N8ZMZ7enIc
+w3QPU14cDhezaaIASydMinwzmv49fz4egfaMaz9dlvXlBogt2CxGHecAeGxA6Ws
FMlZ5KoUdl0UuvVUitl2XbNgt/qjJ550aDslZO8Vn2KPygwQfLNS4l+BmGjuf6/F
CinFSmHSKm2an77voVaUyHW1KjxANQtlxY3toqRewuXlkWmj+QcwwNYyOunbucYo
Dl5z1C+QYi5NvMaJ1u/2Jf9u7MR9i3eV9lyjEPEPMosTCfE5D9A/aWSjoFAH1jhF
L/9O3ZsMdODbGl8LNX5mCjogp16qRzXATafdWBFuvAslOjIQphyIssX12E5KYq4B
e64uxiIv4xISzcUyGpdU/UcT/66nu93Rzg/soDhvrVsW3TjUmF3irXZ5XGLxh6Pg
mQiB/ZucMZOSi83RKgSn+l7Vz3hCKb40GmWw+dy2A2qTHHyU9QCDcScP47SKiG1m
QReAmGV06Z2CiddurH3Taqzxhn6nkFFosaMB76iLlHzuDqmo3fzF4Drqt42iD4vU
cniNBCQKRqiioCORhyc3XdEDWtExxqRVoWTUIB7XknJCDcBlusbLxLHInzVEsBCV
mCjKGUlU4a/r5Cp505r2a7N77ERAMpoFMT0scLNUoIbkYlVvEOQ1wBn+AErjPdG+
3U/kh0ze3qOT7ucUFG4Gx8QJeokg7o+/SlqT26+vqAka75zhzc8uqeNb/W/WYAUA
fkTrYsxy9t5Vh/XoUMAM6ih3jWpAi3g41/K6GmIhdBZP7fsw10mE2DVLx4IXzCIj
vE//RIJleySU/lQ7E8o4jQegDdogasxCPkGicQTCIayy99f3h5HPJYX1qFQAHlZH
hjGI+jgC+0gxo5T4XWKX07N3hcFTCyAD3b80kD2ed9iqemI3l9jhsqithK6NNm32
-----END RSA PRIVATE KEY-----

包含额外的算法信息,不好处理,所以将pem格式转换为PKCS8格式:

openssl pkcs8 -topk8 -nocrypt -inform PEM -in ca.key.pem -passin pass:123456  -outform PEM -out ca.key.pkcs8

PKCS8格式如下:

-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDTbulU4LsakgN3
3954OjnN4yB68+wEVzNXbCLblWK2aCKvZ6KXN7h8EXDV7EsU0M7rYbBVPVoVDJB1
kqhjlcwLtxWx6PDbC7ffkxQlfqGUW7BeHYjeSR8nMMxCLpAgZyYeLqzIpQp+X3q5
oflHzh6CTBRJpulyB387weOePSqKZYXf/zfz+IcEUYb/p0/tDCPvYTCpC1c+2PsR
F7JOAOsQY+0pbKCmStqsgyVIv/5ETekMnqR9AxxJyDpI0cy5FioTT88Undi894/O
z7hc7qerA1f87c+5gb5EFmPs7V5W6OoLqDc9cP0CRB2C8Chh7VAjnhjz2pVd9tz2
Uo47amtxAgMBAAECggEAaO2GBoVw1YLZDcigE3Jx9WdeWyZqXs0Xwa78rZS5ZGTR
sdLwsbMJbykIomGd8TqfNdlJNUQ5THDr0C10O9e/Z6D4t04vGcX6/o1zVhg9/+5w
3IQzxHztZPmmpMGO7UXDeP8Y6IpfZs/ji/SCTcxQ/8DZ4KNg/yIRVoqmd4VLc1p/
4e4YGIRylSrH1/AEkfMrtrMzyx5HHUosi5HaZc8wqdpqhU99UbBO/poxnycuk5dd
xUuVu/l7u8K/FGEa0znCLXABZn61FAp20kaoBQY9CeS5agQ/iwDzUFkYzT9nfYPs
rGJc9+y4kNjfUAU4ZbQOU/0G0Wjxv0/ZxvWNtULlcQKBgQDrkFI2EUKR145jp6Dm
8CyDhaYM1yuOA8MdbSTPceWiVG9EU4rKAKjdSdTe2TxL0QT//2QWi6ZflF4KW2EN
dryr3yvZ/SfGONFzsg8C3s6/ylxzukca5NdBmsv7Ck5MfO/YzVekxS+c/xOo1R6F
Kf45eKuLZCqTdi9emjR4IYK8CwKBgQDlxqvjs/O8vfUwvB179RlTEo8i85PkFybK
O3bqm40Mp1cz75bSYygDgITG4auzP0HY3Qv/IgMKTtm22GA5Wm467YDwqgU8RkEJ
uDYYq9KAMr/dP830h5aSD3xl4DIIhlrXPyQS8JNbTxZCbDuMRlmpw0Ivv/yNgWEY
/UKaYGzn8wKBgQChsFY9tmszH/okfRL8cS6cuEfZ2HeU6xAqSphRq6QrYDlfdNh2
/yzpd5wduo6gm8AK14ojz4dLY9OI2GAtlaq7blvzLMj4Tle2SinzlmvjSUtcRCg6
VYlLBq2sis5jwf3/mjLmblLYOF2OLXrdfI17dVCKRR7USPMKxF3vd1D9MQKBgAd+
9biV9Euh/s+6M1QJ6OuW3WiMfbShNLjAO97neDCDfQrKtCbk6TdECBDc59cPJzzF
6VBHccXyUJPwdyMn2hS+DsjLIySWPiTtB4bChDl+blfvbu6dRcI6ExrXt4ojp+8B
tlGP257M87LzCPZIjUBGiHA6WSjcoahJCtUydBsVAoGBAJOwtwtZP+DJHqKDTn6G
vR96qh/eCP0fWspO32pUmy1KIIPS6Uqilg8pRcZTZE2GH/G0ocwNohwOMOr3n+5T
ZdQbr4UNdvza1LSarrMMHcSBYLcsTT9a+ZH9R8Hi1e+fRywLqrSOWZS66Y8x7PIx
Vi6kMzXEAgurfVIqS441Bgu/
-----END PRIVATE KEY-----

只要去掉页眉、页脚和新行。然后,我们需要将 Base64 编码的字符串解码为其对应的二进制格式。

public class PEMJavaCoder {

    /**
     * 获取公钥
     * @param file pem文件
     * @return RSAPublicKey RSA公钥
     * @throws Exception
     */
    public static RSAPublicKey readPublicKey(File file) throws Exception {
        //读取文件保存到字符串中
        String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());
        String publicKeyPEM = key
                .replace("-----BEGIN PUBLIC KEY-----", "")
                .replaceAll(System.lineSeparator(), "")
                .replace("-----END PUBLIC KEY-----", "");
        byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);

        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
        return (RSAPublicKey) keyFactory.generatePublic(keySpec);
    }

    /**
     * 获取私钥
     * @param file pem文件
     * @return RSAPrivateKey RSA私钥
     * @throws Exception
     */
    public static RSAPrivateKey readPrivateKey(File file) throws Exception {
        //读取文件保存到字符串中
        String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());
        String privateKeyPEM = key
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replaceAll(System.lineSeparator(), "")
                .replace("-----END PRIVATE KEY-----", "");
        byte[] encoded = Base64.getDecoder().decode(privateKeyPEM);

        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
        return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
    }


    public static void main(String[] args) throws Exception {
        //读取私钥
        RSAPrivateKey privateKey = readPrivateKey(new File("jks/ca.key.pkcs8"));
        //读取公钥
        RSAPublicKey publicKey = readPublicKey(new File("jks/ca.pub.pem"));

        String str = "hello world";

        System.out.println("******************公钥加密私钥解密****************");
        byte[] encryptByPublicKey = PEMCoder.encryptByPublicKey(str.getBytes(), publicKey);
        System.out.println("公钥加密后的数据:" + HexUtil.encodeHexStr(encryptByPublicKey));
        byte[] decryptByPrivateKey = PEMCoder.decryptByPrivateKey(encryptByPublicKey, privateKey);
        System.out.println("私钥解密后的数据:" + new String(decryptByPrivateKey));

        System.out.println("******************私钥加密公钥解密****************");
        byte[] encryptByPrivateKey = PEMCoder.encryptByPrivateKey(str.getBytes(), privateKey);
        System.out.println("私钥加密后的数据:" + HexUtil.encodeHexStr(encryptByPrivateKey));
        byte[] decryptByPublicKey = PEMCoder.decryptByPublicKey(encryptByPrivateKey, publicKey);
        System.out.println("公钥解密后的数据:" + new String(decryptByPrivateKey));
    }
}
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐