写点什么

深入剖析 RSA 密钥原理及实践

发布于: 2021 年 01 月 19 日

一、前言


在经历了人生的很多至暗时刻后,你读到了这篇文章,你会后悔甚至愤怒:为什么你没有早点写出这篇文章?!


你的至暗时刻包括:

1.你所在的项目需要对接银行,对方需要你提供一个加密证书。你手上只有一个六级英语证书,不确定这个是否满足对方需求。由于你迟迟无法提供正确的证书,项目因此延期,加薪计划泡汤,月供断了,女朋友分手了,你感觉人生完了。


2. 你老骥伏枥 2 个月,终于搞懂了.crt 格式证书。加入到新项目,项目在进行证书托管改造。哈哈,这题我会,就是把证书文件上传到托管系统。你对项目组成员大喝一声,放开那些证书,让我来!挤进去一看,是陈年老项目了,根本没有证书,当时使用是公钥和私钥,如何公钥和私钥变成证书⋯⋯由于你迟迟无法提供正确的证书,项目因此延期,加薪计划泡汤,月供断了,女朋友分手了,你感觉人生完了。


3. 你卧薪尝胆 3 个月,摸清楚了 SSL 证书的来龙去脉。踌躇满志加入到新项目,你向项目经理痛陈血泪史,经此一役,你已经成长为安全证书方面的专家。项目经理喜出望外,正好项目在进行数据安全改造,数据库需要启用 SSL,来得正是时候,不着急,明天下班前提供几个密钥文件就行。越明日,下班前半小时,你缓缓走向项目经理,“你要的货到了”,便排出三个证书,这个是 key 文件,这个是公钥文件,这个是证书文件。项目经理点点头又摇摇头,我要的是 JKS 文件呀。你说,明天提供。越明日,下班前的半个小时,你把 JKS 格式文件交给项目经理,项目经理点点头又摇摇头,密码呢?没有密码怎么行?由于你迟迟无法提供正确的证书,项目因此延期,加薪计划泡汤,月供断了,女朋友分手了,你感觉人生完了。


本文将从以下几部分来揭示 RSA 密钥文件的鲜为人知的秘密:

  • RSA 算法数学基础

  • RSA 秘钥体系六层模型

  • RSA 工具使用

  • RSA 密钥使用场景


注:虽然密钥与证书严格意义上并不等同,但为了表述方便,没有特殊指定的话,本文中的密钥一词涵盖了公钥,私钥,证书等概念。

二、RSA 算法数学基础


RSA 算法是基于数论的,RSA 算法的复杂性的基础在于一个大数的素数分解是 NP 难题,非常难破解。RSA 算法相关的数学概念:


对于任意一个数 x,可以计算出 y:


通过 y,可以计算出 x:


也就是说,x 通过数对 (m,e) 生成了 y 后,可以通过数对 (m, d) 将 y 还原成 x。


这里,我们实际上演示了 RSA 加密解密的数学过程。通过公式 (1),根据 x 计算得出 y 的过程就是加密,通过公式 (2),根据 y 计算得出 x 的过程就是解密。


在实际应用中,RSA 算法通过公钥进行加密,私钥进行解密,因此数对 (m,e) 就是公钥,(m, d) 就是私钥。


实际上为了提高私钥解密速度,私钥会保存一些中间结果,例如 p, q, e, 等等。


所以在实际应用中,可以通过私钥导出公钥。

三、 RSA 秘钥六层模型


为了方便理解 RSA 密钥的原理,本人创造性地发明了 RSA 密钥六层模型概念。每一层定义了自己的职责和边界,层级越低,其表示的内容越倾向于抽象和理论;层级越高,其表示的内容越倾向于实际应用。



  • Data:数据层,定义了 RSA 密钥的数学概念(m,e,p,q 等)或者参与实体(subject, issuer 等)。

  • Serialization:序列化层,定义了将复杂数据结构序列化的方法。

  • Structure:结构层,定义了不同格式的 RSA 密钥的数据组织形式。

  • Text:文本层,定义了将二进制的密钥转换成文本的方法。

  • Presentation:表现层,定义了文本格式密钥的表现形式。

  • Application:应用层,定义了 RSA 密钥使用的各种场景。


下面对每一层进行具体说明。

3.2 数据层


从上文可知,秘钥是一个数据结构,每个结构包含了 2 个或更多的成员(公钥包含 m 和 e,私钥包含 m,d,e 以及其他一些中间结果)。为了将这些数据结构保存在文件中,需要定义某种格式对秘钥进行序列化。

3.3 序列化层


目前常见的定义数据结构的格式包括 JSON 和 XML 等文本格式。


比如,理论上我们可以把公钥定义为一个 JSON:


JSON 格式密钥


{ "m":"15", "e":"3"}
复制代码


或者,也可以把私钥定义为一个 XML:


<?xml><key> <module>15</module> <e>3</e> <d>3</d> <p>3</p> <q>5</q><key>
复制代码


但是 RSA 发明的时候,这两种格式都还不存在。因此科学家们选择了当时比较流行的语法格式 ASN.1。

3.3.1 ASN.1


ASN.1 全称是 Abstract Syntax Notation dot one,(抽象语法记号第 1 版)。数字 1 被 ISO 加在 ASN 的后边,是为了保持 ASN 的开放性,可以让以后功能更加强大的 ASN 被命名为 ASN.2 等,但至今也没有出现。


ASN.1 描述了一种对数据进行表示、编码、传输和解码的数据格式。它提供了一整套正规的格式用于描述对象的结构,而不管语言上如何执行及这些数据的具体指代,也不用去管到底是什么样的应用程序。

3.3.2 ASN.1 编码规则


ASN.1 的具体语法可以参考维基百科(https://zh.wikipedia.org/wiki/ASN.1),在此只作简要说明。


ASN.1 中数据类型表示是 T-L-V 的形式:头 2 个字节代表数据类型,接下来的 2 个字节代表字节长度,V 代表具体值。常见的基础类型的值包括 Integer, UTF8String, 复合结构包括 SEQUENCE, SET.秘钥和证书都是 SEQUENCE 类型,而 SEQUENCE 的 type 是 0x30,且长度是大于 127 的,因此第 2 个字节是 0x82. ASN.1 编码表示的数据是二进制数据,通常通过 BASE64 转化成字符串保存在 pem 文件中,而 0x3082 经过 BASE64 编码后,就是字符串 MI,因此所有 PEM 文件存储的秘钥开始的前两个字符是 MI。


BER, CER, DER 是 ASN.1 编码规则。其中 DER(Distinguish Encode Rules) 是无歧义编码规则,保证相同的数据结构产生的序列化结果也相同的。


ASN.1 只是定义了抽象数据的序列化方式,但是具体的编码还需要进一步定义。


严格来说,ASN.1 还不是一种定义数据的格式,而是一种语法标准,按照这种标准,可以制定各种各样的格式。

3.4 结构层


根据秘钥文件用途不同,以下标准定义了不同的结构来对秘钥数据进行 ASN.1 编码。通常而言,不同格式的秘钥暗示了不同的结构。


  • pkcs#1 用于定义 RSA 公钥、私钥结构

  • pkcs#7 用于定义证书链

  • pkcs#8 用于定义任何算法公私钥

  • pkcs#12 用于定义私钥证书

  • X.509 定义公钥证书


这些格式的具体区别比较参见下文 3.5.2

3.5 表现层


可以看到 ASN.1 及其编码规则(BER, CER, DER)定义的是二进制规则,保存在文件中也是二进制格式。由于当时的电子邮件标准不支持二进制内容的传输,如果秘钥文件通过电子邮件传输,就需要将二进制文件转换成文本文件。这就是 PEM(Privacy-Enhanced Mail, 私密增强邮件)的由来。因此,PEM 文件中保存的秘钥内容是 ASN.1 编码生成的二进制内容,再进行 base64 编码后的文本。


另外,为了方便用户识别是何种格式,中文件的首尾加上一行表示身份的文本。PEM 文件一般包含三部分:首行标签,BASE64 编码的文本数据,尾行标签。


-----BEGIN <label>-----<BASE64 ENCODED DATA>-----END <label>-----
复制代码


针对不同的格式,<label> 值不一样。

3.5.2 PEM 文件格式小结


3.6 应用层


在实际使用中,不仅仅需要使用公私钥对数据进行加解密,还需要根据不同的使用场景,解决密钥的分发、验证等。第 5 节列举了 RSA 密钥的一些常见使用场景。

四、工具

4.1 openssl


注意:下面的命令中-RSAPublicKey_in, -RSAPublicKey_out 选项需要 openssl1.0 以上版本支持,如果报错,请检查 openssl 版本。

4.1.1 创建秘钥文件


# 生成 pkcs#1 格式2048位的私钥openssl genrsa -out private.pem 2048 #从私钥中提取 pkcs#8 格式公钥openssl rsa -in private.pem -out public.pem -pubout #从私钥中提取 pkcs#1 格式公钥openssl rsa -in private.pem -out public.pem -RSAPublicKey_out
复制代码

4.1.2 秘钥文件格式转换


#pkcs#1 公钥转换成 pkcs#8 公钥openssl rsa -in public.pem -out public-pkcs8.pem -RSAPublicKey_in #pkcs#8 公钥转换成 pkcs#1 公钥openssl rsa -in public-pkcs8.pem -out public-pkcs1.pem -pubin -RSAPublicKey_out #pkcs#1 私钥转换成 pkcs#8 私钥openssl pkcs8 -in private.pem -out private-pkcs8.pem -topk8 #pkcs#8 私钥转换成 pkcs#1 私钥openssl rsa -in private-pkcs8.pem -out private-pkcs1.pem
复制代码

4.1.3 查看秘钥文件信息


#查看公钥信息openssl rsa -in public.pem -pubin -text -noout #查看私钥信息openssl rsa -in private.pem -text -noout
复制代码

4.1.4 证书

RSA 证书


#从现有私钥创建 CSR 文件openssl req -key private.pem -out request.csr -new #从现有 CSR 文件和私钥中创建证书,有效期365天openssl x509 -req -in request.csr -signkey private.pem -out cert.crt -days 365 #生成全新证书和私钥openssl req -nodes -newkey rsa:2048 -keyout root.key -out root.crt -x509 -days 365 #通 过 现 有 证 书 和 私 钥 (作 为CA ) 为 其 他 CSR 文 件 签 名openssl x509 -req -in child.csr -days 365 -CA root.crt -CAkey root.key -set_serial 01 -out child.crt #查看证书信息openssl x509 -in child.crt -text -noout #从证书中提取公钥openssl x509 -pubkey -noout -in child.crt > public.pem
复制代码

4.1.5 JKS


#将CA证书转换成JKS格式keytool -importcert -alias Cacert -file ca.crt -keystore truststoremysql.jks -storepass password123 #将client.crt和client.key转换成PKCS#12格式openssl pkcs12 -export -in client.crt -inkey client.key -name "mysqlclient" -passout pass:mypassword -out client-keystore.p12 #将PKCS#12格式转换成JKS格式keytool -importkeystore -srckeystore client-keystore.p12 -srcstoretype pkcs12 -srcstorepass mypassword -destkeystore clientstore.jks -deststoretype JKS -deststorepass password456
复制代码


五、 RSA 密钥使用场景


5.1 HTTPS 单向认证


由于 HTTP 协议是明文传输,为了保证 HTTP 报文不被泄露和篡改,HTTPS 通过 SSL/TLS 协议对 HTTP 报文进行加解密。


简单来说,HTTPS 协议要求客户端和服务端建立连接的过程中,首先进行会话密钥交换,然后使用该会话密钥对通信报文进行加解密。整个通信过程如下:


  1. 服务端通过 4.1.4 所示方法创建 RSA 证书 server.crt 和私钥 server.key,并在 WEB 服务器中进行配置。

  2. 客户端与服务端建立连接,服务端向客户端发送证书 server.crt。

  3. 客户端对服务端证书进行校验,并随机生成会话密钥,将通过服务端证书对会话密钥进行加密,传给服务端。

  4. 服务端通过 server.key 对加密后的会话密钥进行解密,获得会话密钥原文。

  5. 客户端通过会话密钥对 HTTP 报文进行加密,传给服务端。

  6. 服务端通过会话密钥对 HTTP 加密报文进行解密,获得 HTTP 报文原文。

  7. 服务端通过会话密钥对 HTTP 响应报文进行加密,返回给客户端。

  8. 客户端通过会话密钥对 HTTP 响应报文进行解密,获得 HTTP 响应报文原文。



(图 1. HTTPS 单向认证)

5.2 HTTPS 双向认证


5.1 节描述的 HTTPS 场景是一个通用场景,整个过程只有客户端对于服务端的验证,即客户端拿到服务端的证书后,会对证书进行有效性验证,比如是否是 CA 签名的,是否仍处于有效期内等。这种单向验证在浏览器访问等场景中没有问题,因为这种服务设计地目的就是对外数以万计的用户提供服务。但是在某些场景,比如说仅对特定企业、商户提供服务,服务端需要对客户端进行验证,通过验证的受信客户端才能正常。


访问服务端时,就需要用到 HTTPS 双向认证。


HTTPS 双向认证的过程,就是在 HTTPS 单向认证的基础之上,增进服务端对客户端的认证。解决方案的思路就是,客户端保存客户端证书 client.crt,但是客户端证书不是客户端自己签名或者 CA 签名,而是由服务端的 root.key 进行签名。在 HTTPS 双向认证过程中,客户端需要将客户端证书 client.crt 发送给服务端,服务端使用 root.key 进行验证无误后,方可进行后续通信;否则,该客户端即非受信客户端,服务端拒绝提供后续服务。


具体通信过程如下所示:


  1. 服务端通过 4.1.4 所示方法创建 RSA 证书 server.crt 和私钥 server.key,并在 WEB 服务器中进行配置。

  2. 客户端与服务端建立连接,服务端向客户端发送证书 server.crt。

  3. 客户端对服务端证书进行校验,验证通过后继续后续流程;验证不通过则断开连接,流程结束。

  4. 服务端向客户端发送报文,请求客户端发送客户端证书。

  5. 客户端向服务端发送客户端证书。

  6. 服务端通过 root.key 对客户端证书进行验证,验证无误进行后续流程;否则断开连接,流程结束。

  7. 客户端随机生成会话密钥,将通过服务端证书对会话密钥进行加密,传给服务端。

  8. 服务端通过 server.key 对加密后的会话密钥进行解密,获得会话密钥原文。

  9. 客户端通过会话密钥对 HTTP 报文进行加密,传给服务端。

  10. 服务端通过会话密钥对 HTTP 加密报文进行解密,获得 HTTP 报文原文。

  11. 服务端通过会话密钥对 HTTP 响应报文进行加密,返回给客户端。

  12. 客户端通过会话密钥对 HTTP 响应报文进行解密,获得 HTTP 响应报文原文。


可以看出,向较于 HTTPS 单向认证过程,HTTPS 双向认证过程在客户端验证服务端证书之后,在向服务端发送加密的会话密钥之前,会增加客户端向服务端发送客户端证书 client.crt,服务端对该证书进行验证的过程。



(图 2. HTTPS 双向认证)

5.3 MySQL 开启 SSL


MySQL 提供 SSL 的原理,与 HTTPS 类似,不同之处在于 MySQL 提供的服务的对象不会是成千上万的普通用户,因此对于 CA 的需求并不高。


因此实际 CA 证书通常都是服务端自己生成。


与 HTTPS 类似,MySQL 提供两种形式的 SSL 认证机制:单向认证和双向认证。

5.3.1 MySQL 的 SSL 单向认证


(1)服务端配置文件:ca.crt, server.crt, server.key,其中 server.crt 由 ca.crt 签名生成。

(2)客户端配置文件:ca.crt,ca.crt 与服务端的 ca.crt 相同。

(3)客户端生成 JKS 文件


keytool -importcert -alias Cacert -file ca.crt -keystore truststoremysql.jks -storepass password123
复制代码

(4)通过 jdbc 字符串配置 SSL 选项和 JKS 文件

verifyServerCertificate=true&useSSL=true&requireSSL=true&trustCertificateKeyStoreUrl=file:./truststoremysql.jks&trustCertificateKeyStorePassword=password123
复制代码


5.3.2 MySQL 的 SSL 双向认证


(1)服务端配置文件:ca.crt, server.crt, server.key, 其中 server.crt 由 ca.crt 签名生成。

(2)客户端配置文件:ca.crt, client.crt, client.key, 其中 ca.crt 与服务端的 ca.crt 相同, client.crt 由 ca.crt 签名生成。

(3)客户端生成 trustKeyStore 文件


keytool -importcert -alias Cacert -file ca.crt -keystore truststore.jks -storepass password123
复制代码


(4)客户端生成 clientKeyStore 文件


keytool -importcert -alias Cacert -file ca.crt -keystore clientstore.jks -storepass password456
复制代码


(5)通过 jdbc 字符串配置 SSL 选项和 JKS 文件


verifyServerCertificate=true&useSSL=true&requireSSL=true&trustCertificateKeyStoreUrl=file:./truststore.jks&trustCertificateKeyStorePassword=password123&clientCertificateKeyStoreUrl=file:./clientstore.jks&clientCertificateKeyStorePassword=password456
复制代码


关于 MySQL 的 SSL 认证更多细节可以参考:



附录 A  不同格式的 ASN.1 编码


A.1 pkcs#1

A.1.1 公钥


RSAPublicKey ::= SEQUENCE { modulus INTEGER , -- n publicExponent INTEGER -- e}
复制代码

A.1.2 私钥


RSAPrivateKey ::= SEQUENCE { version Version , modulus INTEGER , -- n publicExponent INTEGER , -- e privateExponent INTEGER , -- d prime1 INTEGER , -- p prime2 INTEGER , -- q exponent1 INTEGER , -- d mod (p-1) exponent2 INTEGER , -- d mod (q-1) coefficient INTEGER , -- (inverse of q) mod p otherPrimeInfos OtherPrimeInfos OPTIONAL}
复制代码

A.2 pkcs#8

A.2.1 pkcs#8 公钥


PublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier , PublicKey BIT STRING}AlgorithmIdentifier ::= SEQUENCE { algorithm OBJECT IDENTIFIER , parameters ANY DEFINED BY algorithm OPTIONAL}
复制代码

A.2.2 pkcs#8 私钥

OneAsymmetricKey ::= SEQUENCE {    version Version ,    privateKeyAlgorithm PrivateKeyAlgorithmIdentifier ,    privateKey PrivateKey ,    attributes [0] Attributes OPTIONAL ,    ...,    [[2: publicKey [1] PublicKey OPTIONAL ]],    ...}PrivateKey ::= OCTET STRING    -- Content varies based on type of key. The    -- algorithm identifier dictates the format of    -- the key.
复制代码

A.3 X.509

A.3.1 X.509 证书


Certificate ::= SEQUENCE { tbsCertificate TBSCertificate , signatureAlgorithm AlgorithmIdentifier , signatureValue BIT STRING} TBSCertificate ::= SEQUENCE { version [0] EXPLICIT Version DEFAULT v1, serialNumber CertificateSerialNumber , signature AlgorithmIdentifier , issuer Name, validity Validity , subject Name, subjectPublicKeyInfo SubjectPublicKeyInfo , issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL , -- If present , version MUST be v2 or v3 subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL , -- If present , version MUST be v2 or v3 extensions [3] EXPLICIT Extensions OPTIONAL -- If present , version MUST be v3} Version ::= INTEGER { v1(0), v2(1), v3(2) } CertificateSerialNumber ::= INTEGER Validity ::= SEQUENCE { notBefore Time, notAfter Time} Time ::= CHOICE { utcTime UTCTime , generalTime GeneralizedTime} UniqueIdentifier ::= BIT STRING SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier , subjectPublicKey BIT STRING} Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension Extension ::= SEQUENCE { extnID OBJECT IDENTIFIER , critical BOOLEAN DEFAULT FALSE , extnValue OCTET STRING -- contains the DER encoding of an ASN.1 value -- corresponding to the extension type identified -- by extnID}
复制代码


作者:Zhu Ran ,来自 vivo 互联网技术团队

发布于: 2021 年 01 月 19 日阅读数: 461
用户头像

官方公众号:vivo互联网技术,ID:vivoVMIC 2020.07.10 加入

分享 vivo 互联网技术干货与沙龙活动,推荐最新行业动态与热门会议。

评论

发布
暂无评论
深入剖析RSA密钥原理及实践