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

1、前言

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

你的至暗時刻包括:html

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

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

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

本文將從如下幾部分來揭示 RSA 密鑰文件的不爲人知的祕密:算法

  • RSA 算法數學基礎
  • RSA祕鑰體系六層模型
  • RSA 工具使用
  • RSA密鑰使用場景
注:雖然密鑰與證書嚴格意義上並不等同,但爲了表述方便,沒有特殊指定的話,本文中的密鑰一詞涵蓋了公鑰,私鑰,證書等概念。

2、RSA 算法數學基礎

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

對於任意一個數 x,能夠計算出 y:數據庫

經過 y,能夠計算出 x:centos

也就是說,x 經過數對 (m,e) 生成了 y 後,能夠經過數對 (m, d) 將 y 還原成 x。瀏覽器

這裏,咱們實際上演示了RSA加密解密的數學過程。經過公式 (1),根據 x 計算得出 y 的過程就是加密,經過公式 (2),根據 y 計算得出 x 的過程就是解密。

在實際應用中,RSA 算法經過公鑰進行加密,私鑰進行解密,所以數對 (m,e) 就是公鑰,(m, d) 就是私鑰。

實際上爲了提升私鑰解密速度,私鑰會保存一些中間結果,例如 p, q, e, 等等。

因此在實際應用中,能夠經過私鑰導出公鑰。

3、 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、工具

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

5、 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互聯網技術團隊
相關文章
相關標籤/搜索