深入剖析 RSA 密鑰原理及實踐

一、前言

在經歷了人生的很多至暗時刻後,你讀到了這篇文章,你會後悔甚至憤怒:爲什麼你沒有早點寫出這篇文章?!

你的至暗時刻包括:

1.你所在的項目需要對接銀行,對方需要你提供一個加密證書。你手上只有一個六級英語證書,不確定這個是否滿足對方需求。由於你遲遲無法提供正確的證書,項目因此延期,加薪計劃泡湯,月供斷了,女朋友分手了,你感覺人生完了。

2. 你老驥伏櫪 2 個月,終於搞懂了.crt 格式證書。加入到新項目,項目在進行證書託管改造。哈哈,這題我會,就是把證書文件上傳到託管系統。你對項目組成員大喝一聲,放開那些證書,讓我來!擠進去一看,是陳年老項目了,根本沒有證書,當時使用是公鑰和私鑰,如何公鑰和私鑰變成證書⋯⋯由於你遲遲無法提供正確的證書,項目因此延期,加薪計劃泡湯,月供斷了,女朋友分手了,你感覺人生完了。

3. 你臥薪嚐膽 3 個月,摸清楚了 SSL 證書的來龍去脈。躊躇滿志加入到新項目,你向項目經理痛陳血淚史,經此一役,你已經成長爲安全證書方面的專家。項目經理喜出望外,正好項目在進行數據安全改造,數據庫需要啓用 SSL,來得正是時候,不着急,明天下班前提供幾個密鑰文件就行。越明日,下班前半小時,你緩緩走向項目經理,“你要的貨到了”,便排出三個證書,這個是 key 文件,這個是公鑰文件,這個是證書文件。項目經理點點頭又搖搖頭,我要的是JKS 文件呀。你說,明天提供。越明日,下班前的半個小時,你把 JKS 格式文件交給項目經理,項目經理點點頭又搖搖頭,密碼呢?沒有密碼怎麼行?由於你遲遲無法提供正確的證書,項目因此延期,加薪計劃泡湯,月供斷了,女朋友分手了,你感覺人生完了。

本文將從以下幾部分來揭示 RSA 密鑰文件的鮮爲人知的祕密:

  • RSA 算法數學基礎
  • RSA祕鑰體系六層模型
  • RSA 工具使用
  • RSA密鑰使用場景

注:雖然密鑰與證書嚴格意義上並不等同,但爲了表述方便,沒有特殊指定的話,本文中的密鑰一詞涵蓋了公鑰,私鑰,證書等概念。

二、RSA 算法數學基礎

RSA 算法是基於數論的,RSA算法的複雜性的基礎在於一個大數的素數分解是NP難題,非常難破解。RSA 算法相關的數學概念:

5835866f08944d8d24ca47ad23d39be5.png

對於任意一個數 x,可以計算出 y:c4a165936ebced6b033648104228ecff.webp

通過 y,可以計算出 x:

4e0ae6a686f17ac704d3b00f11974e50.webp

也就是說,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密鑰六層模型概念。每一層定義了自己的職責和邊界,層級越低,其表示的內容越傾向於抽象和理論;層級越高,其表示的內容越傾向於實際應用。

f12bb1789eb559a8cdefb60925d7299e.png

  • 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 文件格式小結

0331db65aa95538fbbe222287c3005ba.webp

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響應報文原文。
e958724af8358def94be6016c4953869.png​(圖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,服務端對該證書進行驗證的過程

bde46659ecb55db6c1b62edeb12712aa.webp​(圖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 password45

(5)通過jdbc字符串配置SSL選項和JKS文件

verifyServerCertificate=true&useSSL=true&requireSSL=true&trustCertificateKeyStoreUrl=file:./truststore.jks&trustCertificateKeyStorePassword=password123&clientCertificateKeyStoreUrl=file:./clientstore.jks&clientCertificateKeyStorePassword=password45

關於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互聯網技術團隊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章