在經歷了人生的不少至暗時刻後,你讀到了這篇文章,你會後悔甚至憤怒:爲何你沒有早點寫出這篇文章?!javascript
你的至暗時刻包括:html
1.你所在的項目須要對接銀行,對方須要你提供一個加密證書。你手上只有一個六級英語證書,不肯定這個是否知足對方需求。因爲你遲遲沒法提供正確的證書,項目所以延期,加薪計劃泡湯,月供斷了,女友分手了,你感受人生完了。java
2. 你老驥伏櫪 2 個月,終於搞懂了.crt 格式證書。加入到新項目,項目在進行證書託管改造。哈哈,這題我會,就是把證書文件上傳到託管系統。你對項目組成員大喝一聲,放開那些證書,讓我來!擠進去一看,是陳年老項目了,根本沒有證書,當時使用是公鑰和私鑰,如何公鑰和私鑰變成證書⋯⋯因爲你遲遲沒法提供正確的證書,項目所以延期,加薪計劃泡湯,月供斷了,女友分手了,你感受人生完了。node
3. 你臥薪嚐膽 3 個月,摸清楚了 SSL 證書的前因後果。躊躇滿志加入到新項目,你向項目經理痛陳血淚史,經此一役,你已經成長爲安全證書方面的專家。項目經理喜出望外,正好項目在進行數據安全改造,數據庫須要啓用 SSL,來得正是時候,不着急,明天下班前提供幾個密鑰文件就行。越明日,下班前半小時,你緩緩走向項目經理,「你要的貨到了」,便排出三個證書,這個是 key 文件,這個是公鑰文件,這個是證書文件。項目經理點點頭又搖搖頭,我要的是JKS 文件呀。你說,明天提供。越明日,下班前的半個小時,你把 JKS 格式文件交給項目經理,項目經理點點頭又搖搖頭,密碼呢?沒有密碼怎麼行?因爲你遲遲沒法提供正確的證書,項目所以延期,加薪計劃泡湯,月供斷了,女友分手了,你感受人生完了。mysql
本文將從如下幾部分來揭示 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, 等等。
因此在實際應用中,能夠經過私鑰導出公鑰。
爲了方便理解RSA密鑰的原理,本人創造性地發明了RSA密鑰六層模型概念。每一層定義了本身的職責和邊界,層級越低,其表示的內容越傾向於抽象和理論;層級越高,其表示的內容越傾向於實際應用。
下面對每一層進行具體說明。
從上文可知,祕鑰是一個數據結構,每一個結構包含了 2 個或更多的成員(公鑰包含 m 和 e,私鑰包含 m,d,e 以及其餘一些中間結果)。爲了將這些數據結構保存在文件中,須要定義某種格式對祕鑰進行序列化。
目前常見的定義數據結構的格式包括 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。
ASN.1 全稱是 Abstract Syntax Notation dot one,(抽象語法記號第1版)。數字1被ISO加在ASN的後邊,是爲了保持ASN的開放性,可讓之後功能更增強大的ASN被命名爲ASN.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 還不是一種定義數據的格式,而是一種語法標準,按照這種標準,能夠制定各類各樣的格式。
根據祕鑰文件用途不一樣,如下標準定義了不一樣的結構來對祕鑰數據進行 ASN.1 編碼。一般而言,不一樣格式的祕鑰暗示了不一樣的結構。
這些格式的具體區別比較參見下文3.5.2
能夠看到 ASN.1 及其編碼規則(BER, CER, DER)定義的是二進制規則,保存在文件中也是二進制格式。因爲當時的電子郵件標準不支持二進制內容的傳輸,若是祕鑰文件經過電子郵件傳輸,就須要將二進制文件轉換成文本文件。這就是 PEM(Privacy-Enhanced Mail, 私密加強郵件)的由來。所以,PEM 文件中保存的祕鑰內容是 ASN.1 編碼生成的二進制內容,再進行 base64 編碼後的文本。
另外,爲了方便用戶識別是何種格式,中文件的首尾加上一行表示身份的文本。PEM 文件通常包含三部分:首行標籤,BASE64 編碼的文本數據,尾行標籤。
-----BEGIN <label>----- <BASE64 ENCODED DATA> -----END <label>-----
針對不一樣的格式,<label> 值不同。
在實際使用中,不只僅須要使用公私鑰對數據進行加解密,還須要根據不一樣的使用場景,解決密鑰的分發、驗證等。第5節列舉了RSA密鑰的一些常見使用場景。
注意:下面的命令中-RSAPublicKey\_in, -RSAPublicKey\_out選項須要openssl1.0以上版本支持,若是報錯,請檢查 openssl 版本。
# 生成 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
#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
#查看公鑰信息 openssl rsa -in public.pem -pubin -text -noout #查看私鑰信息 openssl rsa -in private.pem -text -noout
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
#將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
因爲HTTP協議是明文傳輸,爲了保證HTTP報文不被泄露和篡改,HTTPS經過SSL/TLS協議對HTTP報文進行加解密。
簡單來講,HTTPS協議要求客戶端和服務端創建鏈接的過程當中,首先進行會話密鑰交換,而後使用該會話密鑰對通訊報文進行加解密。整個通訊過程以下:
- 服務端經過4.1.4所示方法建立RSA證書server.crt和私鑰server.key,並在WEB服務器中進行配置。
- 客戶端與服務端創建鏈接,服務端向客戶端發送證書server.crt。
- 客戶端對服務端證書進行校驗,並隨機生成會話密鑰,將經過服務端證書對會話密鑰進行加密,傳給服務端。
- 服務端經過server.key對加密後的會話密鑰進行解密,得到會話密鑰原文。
- 客戶端經過會話密鑰對HTTP報文進行加密,傳給服務端。
- 服務端經過會話密鑰對HTTP加密報文進行解密,得到HTTP報文原文。
- 服務端經過會話密鑰對HTTP響應報文進行加密,返回給客戶端。
- 客戶端經過會話密鑰對HTTP響應報文進行解密,得到HTTP響應報文原文。
(圖1. HTTPS單向認證)
5.1節描述的HTTPS場景是一個通用場景,整個過程只有客戶端對於服務端的驗證,即客戶端拿到服務端的證書後,會對證書進行有效性驗證,好比是不是CA簽名的,是否仍處於有效期內等。這種單向驗證在瀏覽器訪問等場景中沒有問題,由於這種服務設計地目的就是對外數以萬計的用戶提供服務。可是在某些場景,好比說僅對特定企業、商戶提供服務,服務端須要對客戶端進行驗證,經過驗證的受信客戶端才能正常。
訪問服務端時,就須要用到HTTPS雙向認證。
HTTPS雙向認證的過程,就是在HTTPS單向認證的基礎之上,增進服務端對客戶端的認證。解決方案的思路就是,客戶端保存客戶端證書client.crt,可是客戶端證書不是客戶端本身簽名或者CA簽名,而是由服務端的root.key進行簽名。在HTTPS雙向認證過程當中,客戶端須要將客戶端證書client.crt發送給服務端,服務端使用root.key進行驗證無誤後,方可進行後續通訊;不然,該客戶端即非受信客戶端,服務端拒絕提供後續服務。
具體通訊過程以下所示:
- 服務端經過4.1.4所示方法建立RSA證書server.crt和私鑰server.key,並在WEB服務器中進行配置。
- 客戶端與服務端創建鏈接,服務端向客戶端發送證書server.crt。
- 客戶端對服務端證書進行校驗,驗證經過後繼續後續流程;驗證不經過則斷開鏈接,流程結束。
- 服務端向客戶端發送報文,請求客戶端發送客戶端證書。
- 客戶端向服務端發送客戶端證書。
- 服務端經過root.key對客戶端證書進行驗證,驗證無誤進行後續流程;不然斷開鏈接,流程結束。
- 客戶端隨機生成會話密鑰,將經過服務端證書對會話密鑰進行加密,傳給服務端。
- 服務端經過server.key對加密後的會話密鑰進行解密,得到會話密鑰原文。
- 客戶端經過會話密鑰對HTTP報文進行加密,傳給服務端。
- 服務端經過會話密鑰對HTTP加密報文進行解密,得到HTTP報文原文。
- 服務端經過會話密鑰對HTTP響應報文進行加密,返回給客戶端。
- 客戶端經過會話密鑰對HTTP響應報文進行解密,得到HTTP響應報文原文。
能夠看出,向較於HTTPS單向認證過程,HTTPS雙向認證過程在客戶端驗證服務端證書以後,在向服務端發送加密的會話密鑰以前,會增長客戶端向服務端發送客戶端證書client.crt,服務端對該證書進行驗證的過程。
(圖2. HTTPS雙向認證)
MySQL提供SSL的原理,與HTTPS相似,不一樣之處在於MySQL提供的服務的對象不會是成千上萬的普通用戶,所以對於CA的需求並不高。
所以實際CA證書一般都是服務端本身生成。
與HTTPS相似,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
(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認證更多細節能夠參考:
RSAPublicKey ::= SEQUENCE { modulus INTEGER , -- n publicExponent INTEGER -- e }
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 }
PublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier , PublicKey BIT STRING } AlgorithmIdentifier ::= SEQUENCE { algorithm OBJECT IDENTIFIER , parameters ANY DEFINED BY algorithm OPTIONAL }
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.
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互聯網技術團隊