Swoole 原始碼分析——Server模組之OpenSSL (上)
前言
自從 Let's Encrypt
上線之後,HTTPS
網站數量佔比越來越高,相信不久的未來就可以實現全網 HTTPS
,大部分主流瀏覽器也對 HTTP
網頁給出明顯的 不安全
標誌。
SSL
是在 TCP
層之上為客戶端服務端之間資料傳輸運用複雜的加密演算法,swoole
使用 SSL
加密只需要兩個步驟:
$serv = new swoole_server("0.0.0.0", 443, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL); $key_dir = dirname(dirname(__DIR__)).'/tests/ssl'; $serv->set(array( 'worker_num' => 4, 'ssl_cert_file' => $key_dir.'/ssl.crt', 'ssl_key_file' => $key_dir.'/ssl.key', ));
SSL
/TLS
安全通訊
由於 HTTPS
的推出受到了很多人的歡迎,在 SSL
更新到 3.0 時,IETF
對 SSL3.0
進行了標準化,並添加了少數機制(但是幾乎和 SSL3.0
無差異),標準化後的 IETF
更名為 TLS1.0
(Transport Layer Security
安全傳輸層協議),可以說 TLS
就是 SSL
的新版本 3.1,並同時釋出“ RFC2246-TLS
加密協議詳解”。
首先我們先來了解一下 SSL
/TLS
的原理。
以下內容來源於參考文獻:TLS和安全通訊
加密技術
TLS
依賴兩種加密技術:
- 對稱加密(
symmetric encryption
) - 非對稱加密(
asymmetric encryption
對稱加密
對稱加密的一方(比如小紅)用祕鑰 K 給文字 M 加密;另一方(比如小明)用同一個祕鑰解密:
小紅 : C = E(M, K)
小明 : M = D(C, K)
這有一個問題:當一方生成了祕鑰 K
之後得把 K
分享給另一方。但是穿越 `SinCity 的道路危險中途很可能有人竊聽到
K`,竊聽者就可以假扮雙方中的任何一方與另一方通訊。這叫中間人攻擊。
非對稱加密
非對稱加密利用成對的兩個祕鑰:K1
和 K2
。小紅用其中一個加密文字,小明可以用另一個解密文字。比如,小紅用 K1
加密,小明用 K2
解密:
小紅 : C = E(M, K1)
小明 : M = D(C, K2)
這樣一來,雙方中的一方(比如小紅)可以生成 K1
K2
,然後把其中一個祕鑰(比如 K1
)私藏,稱為私鑰;另一個(比如K2)公開,稱為公鑰。另一方(比如小明)得到公鑰之後,雙方就可以通訊。
然而,中間人還是可能截獲公鑰 K2
,然後自己弄一對祕鑰(κ1
, κ2
),然後告訴小明說 κ2
是小紅的公鑰。這樣中間人每次可以用截獲的 K2
解密小紅髮給小明的文字(甚至可能修改文字),再用 κ1
加密了發出去;小明用 κ2
解密接收。
數字簽名和CA
為了幫小明確定得到的公鑰確實是小紅的 K2
,而不是中間人偽造的 κ2
,牛人們發明了數字簽名(digital signature)技術。
數字簽名的做法是:
- 小紅把自己的公鑰和
ID
(身份證號碼,或者域名)合為身份證申請(certificate signing request,CSR), - 小紅把
CSR
發給一個德高望重的人(被稱為certificate authority
,CA
),比如小亮, - 小亮用自己的私鑰加密小紅的
CSR
,得到的密文被稱為數字簽名(digital signature), - 小亮把
signature
和CSR
的明文合在一起稱為 CA簽署的身份證(CA
signed certificate
,CRT
),發給小紅,
小紅:CSR = 小紅公鑰+小紅域名
signature = E(CSR, 小亮的私鑰)
CRT = CSR + signature
每當其他人(比如小明)找小紅聊天(建立 HTTPS
連線)的時候,小紅出示自己的小亮簽署的身份證。拿到這個身份證的人,只要他是相信小亮的——在自己機器上安裝了小亮的身份證,就可以
- 從小亮的身份證中的小亮的
CSR
裡提取小亮的公鑰; - 然後用小亮的公鑰解密小紅的身份證中小亮的
signature
,得到一個小紅的CSR
; - 如果這個
CSR
和小紅身份證中的CSR
明文一致,則說明“這個小紅的身份證是小亮確認過並且簽名的”。
小明:小亮的公鑰 = 小亮的CRT.CSR.小亮的公鑰
CSR' = D(CRT.signature, 小亮的公鑰)
if CSR' == CRT.CSR then OK
由此過程可以看出來:隨便誰都可以當 CA
——只要願意公開自己的公鑰,即可用自己的私鑰去加密別人的認證。那我們要是信錯了 CA
,被他擺一道怎麼辦?答案是:沒辦法。我們選擇信任社會,要相信如果 CA
說謊,萬一被識破,就沒有人再相信他了。現實中,很多作業系統(Windows
、Mac OS X
)和瀏覽器(Chrome
、Firefox
、IE
)會內建一些靠譜的 CA
的身份證。
信任鏈
小亮如果擔心沒有人信任自己是個好 CA
(就像沒人信CNNIC一樣),可以找一個大家都信的 CA
,比如老王,用老王的私鑰在小亮的身份證上簽名:
小亮:CSR = 小亮的公鑰+小亮域名
signature = E(CSR, 老王的私鑰)
CRT = CSR + signature
如果瀏覽器或者作業系統裡安裝了老王的公鑰則可以驗證“小亮的身份證是老王確認並且簽名過的”。
這樣,小亮在簽署小紅的身份證的時候,可以在小紅身份證後面附上自己的身份證。這樣小紅的身份證就有“兩頁”了。
當小明和小紅通訊的時候:
- 小明會先要求小紅出示自己的身份證;
- 小明雖然不信任小亮,但是信任老王,所以小明可以用老王的身份證裡的老王的公鑰來驗證小紅身份證附帶的小亮的身份證,於是就可以信任小亮了;
- 然後小明用小亮身份證裡的公鑰驗證小紅的身份證。
要是怕小明連自己也也不信任,老王可以再找一個小明信任的人來簽名確認自己的身份證。這個過程可以不斷遞推,從而形成了一條信任鏈(trust of chain
)
根身份證和自簽名
信任鏈總會有個頂端,被稱為根身份證(root CA)。那麼根身份證是誰簽名的呢?答案是:自己簽名。實際上,我們每個人都可以自己簽名認證自己的身份證,得到自簽名的身份證(self-signed certificate)。具體過程是:
- 生成一對祕鑰:公鑰
K2
和私鑰K1
, - 建立自己的
CSR
, - 用自己的祕鑰加密
CSR
得到signature
,然後把CSR
明文和signature
一起釋出。
任何人只要信任我們自簽名的身份證 CRT
,也就可以用 CRT.CSR.K2
作為公鑰加密要傳遞給我們的文字。我們可以用自己的私鑰 K1
來解密文字。
一般來說,自簽名的根身份證用於公司內部使用。
如果老王就是根 CA
了,那麼上述各位的身份證的信任鏈如下:
小紅:CSR = 小紅公鑰+小紅域名
signature = E(CSR, 小亮的私鑰)
CRT = CSR + signature
小亮:CSR = 小亮的公鑰+小亮域名
signature = E(小亮的CSR, 老王的私鑰)
CRT = 小亮的CSR + signature
老王:CSR = 老王的公鑰+老王的域名
signature = E(老王的CSR, 老王自己的私鑰)
CRT = 老王的CSR + signature
雙方TLS認證
上述例子解釋了通訊的一方如何驗證另一方的身份。這種情況的一個常見應用是:我們通過瀏覽器訪問銀行的網頁。這裡的關鍵是,我們要能驗證銀行的身份證,然後才敢於在網頁裡輸入賬號和密碼。瀏覽器驗證銀行的身份證的過程如下:
- 在瀏覽器和銀行的HTTPS服務建立安全連線的過程中,銀行的HTTPS服務會把它的身份證發給瀏覽器
- 瀏覽器使用內建的CA的身份證來驗證銀行的身份證。
瀏覽器驗證了銀行的 HTTPS
服務的身份之後,就輪到銀行驗證瀏覽器的使用者的身份了:
- 瀏覽器展示銀行HTTPS服務發來的登陸頁面;
- 使用者在這個頁面裡輸入賬號和密碼,銀行的HTTPS服務由此驗證使用者的身份。
在這個過程中,銀行 HTTPS
伺服器的身份是通過 TLS
身份證來驗證的。而我們(使用者)的身份是通過我們輸入的賬號和密碼來驗證的。
有時通訊的雙方都是程式(而不是人)。此時,讓一方輸入賬號和密碼,不如讓雙方都通過 TLS
身份證來互相驗證方便。尤其是在很多分散式系統裡,有多種型別的程式互相通訊,而不只是兩方通訊。
比如在 Kubernetes
機群裡,不光操作機群的客戶端程式 kubectl
要能驗證 Kubernetes master node
(具體的說是 apiserver
)的身份,才能放心地把包括敏感資訊(比如資料庫密碼)的計算作業提交給 apiserver
。類似的,apiserver
也要能驗證 kubectl
的身份,以確認提交作業的是公司的合法僱員,而不是外賊。
為此,通訊各方都需要有各自的身份證。一個公司可以自簽名一個 CA
身份證,並且用它來給每個僱員以及每個程式簽署身份證。這樣,只要每臺電腦上都預先安裝好公司自己的 CA
身份證,就可以用這個身份證驗證每個僱員和程式的身份了。這是目前很多公司的常用做法。
加密和解密的效能
因為 TLS
模式下所有傳輸的資料都是加密的,大家會關注加密和解密的效能。客觀的說,非對稱加密技術的加密和解密比較慢,相對來說,對稱加密技術的加密解密過程更快。所以實際的連線和握手過程中,通訊雙方會協商一個對稱加密祕鑰,之後的資料通訊過程中的加密都是利用對稱加密技術來實現的。
具體的做法是:握手的時候,雙方各自生成一個隨機數,並且以非對稱加密的方式分享給對方。然後每一方都把自己的隨機數和對方的隨機數拼起來,就是接下來通訊時候使用的對稱加密方法的祕鑰了。
OpenSSL
操作指南
接下來,我們來看看如何生成 HTTPS
所需要的證書。
openssl
簡介
OpenSSL
是一個開源專案,其組成主要包括一下三個元件:
- openssl:多用途的命令列工具
- libcrypto:加密演算法庫
- libssl:加密模組應用庫,實現了ssl及tls
openssl
可以實現:祕鑰證書管理、對稱加密和非對稱加密更多簡介和官網。
指令
平時我們使用 openssl
最多的莫過於使用指令了,而最為常見的幾個指令如下:
-
genrsa
生成RSA引數 req
x509
rsa
ca
genrsa
簡介
平時主要用來生成私鑰,選擇使用的演算法、對稱加密密碼和私鑰長度來生成私鑰。也就是生成 key
檔案。
基本用法:
openssl genrsa [args] [numbits]
其中常見的引數:【更多引數檢視:openssl genrsa -help
】
args1 對生成的私鑰檔案是否要使用加密演算法進行對稱加密:
-des : CBC模式的DES加密
-des3 : CBC模式的3DES加密
-aes128 : CBC模式的AES128加密
-aes192 : CBC模式的AES192加密
-aes256 : CBC模式的AES256加密
args2 對稱加密密碼
-passout passwords
其中passwords為對稱加密(des、3des、aes)的密碼(使用這個引數就省去了console互動提示輸入密碼的環節)
args3 輸出檔案
-out file : 輸出證書私鑰檔案
[numbits]: 金鑰長度,理解為私鑰長度
生成一個 2048 位的 RSA
私鑰,並用 des3
加密(密碼為 123456
),儲存為 server.key
檔案
openssl genrsa -des3 -passout pass:123456 -out server.key 2048
// -des3 是第一個引數args1;
// -passout pass:123456 是第二個引數寫法 args2
// -out server.key 第三個引數args3;
// 2048 最後一個[numbits]引數
生成的 key
檔案是 PEM
格式的
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,DB98A9512DD7CBCF
yKTM+eoxBvptGrkEixhljqHSuE+ucTh3VqYQsgO6+8Wbh1docbFUKzLKHrferJBH
...
-----END RSA PRIVATE KEY-----
雖說檔案頭尾都標註著 RSA PRIVATE KEY
,但實際上這個檔案裡既包括公鑰也包括私鑰。
req
命令
req
的基本功能主要有兩個:生成證書請求和生成自簽名證書,也就是 csr
檔案,或者自簽名的 crt
檔案。當然這並不是其全部功能,但是這兩個最為常見;
常見使用方法:
openssl req [args] outfile
主要引數:【更多引數檢視:openssl req -help
】
args1 是輸入輸入檔案格式:
-inform arg
-inform DER 使用輸入檔案格式為DER
-inform PEM 使用輸入檔案格式為PEM
args2 輸出檔案格式:
-outform arg
-outform DER 使用輸出檔案格式為DER
-outform PEM 使用輸出檔案格式為PEM
args3 是待處理檔案
-in inputfilepath
args4 待輸出檔案
-out outputfilepath
args5 用於簽名待生成的請求證書的私鑰檔案的解密密碼
-passin passwords
args6 用於簽名待生成的請求證書的私鑰檔案
-key file
args7 指定輸入金鑰的編碼格式
-keyform arg
-keyform DER
-keyform NET
-keyform PEM
args8 生成新的證書請求
-new
args9 輸出一個X509格式的證書,簽名證書時使用
-x509
args10 使用 X509 簽名證書的有效時間
-days // -days 3650 有效期10年
args11 生成一個bits長度的RSA私鑰檔案,用於簽發【生成私鑰、並生成自簽名證書】
-newkey rsa:bits
args12設定HASH演算法-[digest]【生成私鑰指定的hash摘要演算法】
-md5
-sha1 // 高版本瀏覽器開始不信任這種演算法
-md2
-mdc2
-md4
args13指定openssl配置檔案,很多內容不容易通過引數配置,可以指定配置檔案
-config filepath
args14 顯示格式txt【用於檢視證書、私鑰資訊】
-text
使用的案例:利用私鑰生成證書請求 csr
openssl req -new -key server.key -out server.csr
server.csr
檔案也是 PEM
格式的,檔案頭尾標註為 CERTIFICATE REQUEST
:
-----BEGIN CERTIFICATE REQUEST-----
MIIC0TCCAbkCAQAwgYsxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTERMA8GA1UE
...
-----END CERTIFICATE REQUEST-----
使用案例:利用私鑰生成自簽名證書
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt
x509
命令
x509
是一個功能很豐富的證書處理工具。可以用來顯示證書的內容,轉換其格式,給 CSR
簽名等 X.509
證書的管理工作;
用法如下:
openssl x509 [args]
引數如下:【更多引數檢視:openssl x509 -help
】
args1 是輸入輸入檔案格式:
-inform arg
-inform DER 使用輸入檔案格式為DER
-inform PEM 使用輸入檔案格式為PEM
args2 輸出檔案格式:
-outform arg
-outform DER 使用輸出檔案格式為DER
-outform PEM 使用輸出檔案格式為PEM
args3 是待處理X509證書檔案
-in inputfilepath
args4 待輸出X509證書檔案
-out outputfilepath
args5表明輸入檔案是一個"請求籤發證書檔案(CSR)",等待進行簽發
-req
args6簽名證書的有效時間
-days // -days 3650 有效期10年
args7 指定用於簽發請求證書的根CA證書
-CA arg
args8 根CA證書格式(預設是PEM)
-CAform arg
args9 指定用於簽發請求證書的CA私鑰證書檔案
-CAkey arg
args10 指定根CA私鑰證書檔案格式(預設為PEM格式)
-CAkeyform arg
args11 指定序列號檔案(serial number file)
-CAserial arg
args12 如果序列號檔案(serial number file)沒有指定,則自動建立它
-CAcreateserial
args13設定HASH演算法-[digest]【生成私鑰指定的hash摘要演算法】
-md5
-sha1 // 高版本瀏覽器開始不信任這種演算法
-md2
-mdc2
-md4
使用例項: 使用根 CA
證書[ ca.crt
]和私鑰[ ca.key
]對"請求籤發證書"[ server.csr
]進行簽發,生成 x509
格式證書
openssl x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out serverx509.crt
server.crt
也是 PEM
格式的。檔案頭尾的標記為 CERTIFICATE
:
-----BEGIN CERTIFICATE-----
MIIDlDCCAnwCCQDQ1UvQyFD7jDANBgkqhkiG9w0BAQsFADCBizELMAkGA1UEBhMC
...
-----END CERTIFICATE-----
DER
、CRT
、CER
、PEM
、KEY
、PFX/P12
格式
證書和編碼
X.509
證書,其核心是根據 RFC 5280
編碼或數字簽名的數字文件。
實際上,術語 X.509
證書通常指的是 IETF
的 PKIX
證書和 X.509 v3
證書標準的 CRL
檔案,即如 RFC 5280
(通常稱為PKIX for Public Key Infrastructure(X.509)
)中規定的。
X509檔案擴充套件
我們首先要了解的是每種型別的副檔名。 很多人不清楚DER,PEM,CRT和CER結尾的檔案是什麼,更有甚者錯誤地說是可以互換的。 在某些情況下,某些可以互換,最佳做法是識別證書的編碼方式,然後正確標記。 正確標籤的證書將更容易操縱
編碼--決定副檔名方式
.DER
副檔名
.DER
= DER
擴充套件用於二進位制 DER
編碼證書。
這些檔案也可能承載 CER
或 CRT
擴充套件。 正確的說法是“我有一個 DER
編碼的證書”不是“我有一個 DER
證書”。
.PEM
副檔名
.PEM = PEM
擴充套件用於不同型別的 X.509v3
檔案,是以“ - BEGIN ...”字首的 ASCII(Base64)
資料。
常見的擴充套件
-
.CRT
副檔名
.CRT = CRT
擴充套件用於證書。 證書可以被編碼為二進位制 DER
或 ASCII PEM
。 CER
和 CRT
擴充套件幾乎是同義詞。 最常見的於 Unix
或類 Unix
系統。
-
.CER
副檔名
CER = .crt
的替代形式(Microsoft Convention
)您可以在微軟系統環境下將 .crt
轉換為 .cer
( .DER
編碼的 .cer
,或 base64 [PEM]
編碼的 .cer
)。
-
.KEY
副檔名
.KEY = KEY
副檔名用於公鑰和私鑰 PKCS#8
。 鍵可以被編碼為二進位制 DER
或 ASCII PEM
。
-
PFX/P12
副檔名
predecessor of PKCS#12
,對 *nix
伺服器來說,一般 CRT
和 KEY
是分開存放在不同檔案中的,但 Windows
的 IIS
則將它們存在一個 PFX
檔案中,(因此這個檔案包含了證書及私鑰)這樣會不會不安全?應該不會,PFX
通常會有一個"提取密碼", 你想把裡面的東西讀取出來的話,它就要求你提供提取密碼,PFX
使用的時 DER
編碼,如何把 PFX
轉換為 PEM
編碼?
openssl pkcs12 -in for-iis.pfx -out for-iis.pem -nodes
這個時候會提示你輸入提取程式碼. for-iis.pem
就是可讀的文字.生成 pfx
的命令類似這樣:
openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt -certfile CACert.crt
其中 CACert.crt
是 CA
(權威證書頒發機構)的根證書,有的話也通過 -certfile
引數一起帶進去.這麼看來,PFX
其實是個證書金鑰庫.
常見的 OpenSSL
證書操作
證書操作有四種基本型別。檢視,轉換,組合和提取。
檢視證書
即使PEM編碼的證書是 ASCII
,它們也是不可讀的。這裡有一些命令可以讓你以可讀的形式輸出證書的內容;
- 檢視
PEM
編碼證書
openssl x509 -in cert.pem -text –noout
openssl x509 -in cert.cer -text –noout
openssl x509 -in cert.crt -text –noout
如果您遇到這個錯誤,這意味著您正在嘗試檢視DER編碼的證書,並需要使用“檢視DER編碼證書”中的命令。
unable to load certificate
12626:error:0906D06C:PEMroutines:PEM_read_bio:no start line:pem_lib.c:647:Expecting: TRUSTEDCERTIFICATE
- 檢視
DER
編碼證書
openssl x509 -in certificate.der -inform der -text -noout
如果您遇到以下錯誤,則表示您嘗試使用DER編碼證書的命令檢視PEM編碼證書。在“檢視PEM編碼的證書”中使用命令
unable to load certificate
13978:error:0D0680A8:asn1 encodingroutines:ASN1_CHECK_TLEN:wrong tag:tasn_dec.c:1306:
13978:error:0D07803A:asn1 encodingroutines:ASN1_ITEM_EX_D2I:nested asn1 error:tasn_dec.c:380:Type=X509
轉換證書格式
轉換可以將一種型別的編碼證書存入另一種。(即PEM到DER轉換)
openssl x509 -in cert.crt -outform der-out cert.der
openssl x509 -in cert.crt -inform der -outform pem -out cert.pem
組合證書
在某些情況下,將多個 X.509
基礎設施組合到單個檔案中是有利的。一個常見的例子是將私鑰和公鑰兩者結合到相同的證書中。
組合金鑰和鏈的最簡單的方法是將每個檔案轉換為 PEM
編碼的證書,然後將每個檔案的內容簡單地複製到一個新檔案中。這適用於組合檔案以在 Apache
中使用的應用程式
證書提取
一些證書將以組合形式出現。 一個檔案可以包含以下任何一個:證書,私鑰,公鑰,簽名證書,證書頒發機構(CA)和/或許可權鏈。
SSL
/TLS
握手流程
簡潔版流程
- 第一步,愛麗絲給出協議版本號、一個客戶端生成的隨機數(
Client random
),以及客戶端支援的加密方法。 - 第二步,鮑勃確認雙方使用的加密方法,並給出數字證書、以及一個伺服器生成的隨機數(
Server random
)。 - 第三步,愛麗絲確認數字證書有效,然後生成一個新的隨機數(
Premaster secret
),並使用數字證書中的公鑰,加密這個隨機數,發給鮑勃。 - 第四步,鮑勃使用自己的私鑰,獲取愛麗絲髮來的隨機數(即
Premaster secret
)。 - 第五步,愛麗絲和鮑勃根據約定的加密方法,使用前面的三個隨機數,生成"對話金鑰"(
session key
),對話金鑰被切片生成兩個對稱金鑰和MAC
金鑰,用來加密接下來的整個對話過程。
注意這個例子只是傳統的 RSA
模式金鑰交換的 SSL
握手流程。
詳細流程
以下內容參考文獻:
客戶端發出請求(ClientHello
)
握手第一步是客戶端向服務端傳送 Client Hello
訊息,這個訊息裡包含了
- 一個客戶端生成的隨機數
Client random
、 - 客戶端支援的加密套件(
Support Ciphers
) - 支援的協議版本
SSL Version
,比如TLS 1.0版。 - 支援的壓縮方法
-
Session id
用於會話複用 -
Extension
拓展欄位的存在,是因為SSL
協議起草之初有些功能沒有考慮到,後續這些功能被加進RFC
,而為了相容SSL
,把這些功能的描述放到Extension
中。-
Server_name(SNI)
: 客戶端在client hello
中帶上server name
拓展(如果使用ip
地址進行訪問,那麼就不會有server name
拓展),它會捎帶上域名地址,伺服器解析到server name
後,就會根據server name
中的域名,選擇合適的證書。 -
Elliptic_curves/ec_point_formats
: 使用橢圓曲線金鑰交換演算法的時候用到,裡面列舉了自己支援的橢圓曲線演算法,供伺服器選擇。 -
SessionTicket TLS
: 會話複用時使用。 -
status_request
: 請求OCSP
,伺服器可以傳送cettificate status
到客戶端,裡面帶上ocsp
的資訊。 -
signature_algorithms
: 表示自己支援的簽名演算法,伺服器收到這個拓展,在進行例如server key exchange
簽名等操作時,需要參考客戶端這個拓展。 -
application_layer_negotiation(ALPN)
: 用以描述支援的上層協議,h2、http/1.1、spdy等。可以把他想象成IP頭中的protocol,描述了上層是TCP還是UDP還是ICMP。目前主流瀏覽器,若要使用HTTP2,則必須使用這個拓展。 -
Renegotiation info
: 如果是重新協商(即加密的hello
),那麼會帶上上次協商的 12 位元組的finished
,如果是新hello
請求,那麼裡面欄位為0。
-
切記及時伺服器端不支援renegotiation,在server hello響應時也需要帶上這個拓展
這裡需要注意的是,客戶端傳送的資訊之中不包括伺服器的域名。也就是說,理論上伺服器只能包含一個網站,否則會分不清應該向客戶端提供哪一個網站的數字證書。這就是為什麼通常一臺伺服器只能有一張數字證書的原因。
對於虛擬主機的使用者來說,這當然很不方便。2006年,TLS協議加入了一個 Server Name Indication
擴充套件,允許客戶端向伺服器提供它所請求的域名。
伺服器迴應(SeverHello
)
伺服器收到客戶端請求後,向客戶端發出迴應,這叫做SeverHello。伺服器的迴應包含以下內容。
- 確認使用的加密通訊協議版本,比如
TLS 1.0
版本。如果瀏覽器與伺服器支援的版本不一致,伺服器關閉加密通訊。 - 一個伺服器生成的隨機數,稍後用於生成"對話金鑰"。注意,至此客戶端和服務端都擁有了兩個隨機數
- 從
Client Hello
傳過來的Support Ciphers
裡確定一份加密套件,這個套件決定了後續加密和生成摘要時具體使用哪些演算法,比如RSA
公鑰加密。 -
session id
(sessionid
會話複用需要帶上,當然命中session ticket
時也需要帶上) - 拓展。一般會帶上空的
renegotiation info
(前提是客戶端提供了EMPTY_SCSV
加密套件或者有renegotiation info
拓展),以表示自己支援secure renegotiation
。
除了上面這些資訊,如果伺服器需要確認客戶端的身份,就會再包含一項請求,要求客戶端提供"客戶端證書"
伺服器證書傳送 (Server Certificate
)
傳送伺服器證書,將伺服器配置的證書(鏈)傳送到客戶端。
注意證書鏈的順序,最下層證書在前(使用者證書在前,上級證書在後)。傳送的證書是二進位制格式,並非 base64
之後的格式。
伺服器端祕鑰交換 (server key exchange
)
對於使用DHE/ECDHE非對稱金鑰協商演算法的SSL握手,將傳送該型別握手。
RSA
演算法不會繼續該握手流程(DH
、ECDH
也不會發送 server key exchange
)。
ECDHE
下主要有幾點重要的資訊:
- 指明自己使用的橢圓曲線(一般根據客戶端的拓展中
supported_groups
中的選擇橢圓曲線演算法)。 - 公鑰。伺服器本地計算一個大數(
BIGNUM
),乘上曲線的base point
,得到一個新的point
,這個point
就是公鑰,用04+x+y
的格式組織起來。04
表示unconpressed point
,和客戶端的ec_point_formats
有關。 - 簽名。和
RSA
握手不同,RSA
情況下,只要能值正常協商金鑰,那麼必然伺服器端有證書對應的私鑰,也間接表明了伺服器擁有該證書。DHE/ECDHE
不同,證書對應的私鑰並不參與金鑰協商,如果要證明伺服器擁有證書,則必然有簽名的操作(就像雙向認證的情況下,客戶端需要傳送certificate verify
)。被簽名資料從curve type
起,至point
的y
為止。對於TLS1.2
,簽名前使用client hello
拓展中提供的雜湊演算法。TLS1.0
和TLS1.1
,如果本地證書是ECC
證書,即若要使用ECDSA
簽名,這種雜湊演算法為SHA1
,其他的情況摘要演算法為md5+sha1
。 -
計算雜湊之後就呼叫
RSA
或者ECDSA
進行簽名。注意的是,TLS1.2
時要帶上 2 位元組的 “Signature Hash Algorithm
”。- 橢圓曲線數字簽名演算法(
ECDSA
)是使用橢圓曲線密碼(ECC
)對數字簽名演算法(DSA
)的模擬。 -
DSA
演算法是RSA
演算法的反向演算法,服務端利用私鑰加密,客戶端用公鑰進行驗證,速度更快,但是不能加密,只能用於簽名驗證,因為公鑰是公開的。
- 橢圓曲線數字簽名演算法(
DHE
下主要有幾點重要的資訊:
- 指明自己使用的
DH
引數,p
和q
。 - 伺服器端計算私鑰
Xb
,計算q^Xb mod p
,將結果Pb
發給客戶端,自己僅且自己儲存Xb
。 - 簽名流程與上述類似,不再贅述。
服務端證書請求 (certificate request
)
雙向認證時,伺服器會發送 certificate request
,表明自己想要收到客戶端的證書。
這個型別的握手主要包含了 ca
證書的 subject
,用以告訴客戶端自己需要哪些證書,不是由這些 ca
簽發的證書“我”不要。
客戶端,例如瀏覽器在收到這個請求時,如果不存在對應的證書,則傳送一個空的 certificate
至伺服器,如果存在一個證書,則傳送該 certificate
至伺服器。如果存在多個對應的證書,則會彈出一個彈出框讓你選擇。
Certificate request
還包含了想要證書的簽名的型別,RSA
還是 ECDSA
,對於 TLS1.2
還會包括摘要資訊。
服務端結束 (Server hello done
)
伺服器告訴客戶完成它的初始化流通訊息。
客戶端證書傳送 (client certificate
)
如果伺服器端請求了客戶端的證書,客戶端即使沒有證書,也需要傳送該型別的握手報文,只是這種情況下,裡面的內容為0。
如果瀏覽器有對應的證書,則會發送證書,當然,也有可能傳送上級證書(即傳送證書鏈),這個完全取決於瀏覽器。
客戶端金鑰交換 (client key exchange
)
-
ECDH/ECDHE
下比較簡單了,和server key exchange
處理一樣,客戶端隨機生成一個大數,然後乘上base point
,得到的結果就是public key
。 -
DHE
下客戶端計算隨機數Xa
,然後該報文中的Pubkey
就是q^Xa mop p
-
RSA
下客戶端隨機生成 48 位元組的預主金鑰,然後使用pkcs1
規則填充至公鑰一樣的長度,隨後呼叫RSA
進行運算,得到Encrypted PreMaster
。填充規則如下:00 + 02 + non_zero + 0 + pre_master
。
"不管是客戶端還是伺服器,都需要隨機數,這樣生成的金鑰才不會每次都一樣。由於SSL協議中證書是靜態的,因此十分有必要引入一種隨機因素來保證協商出來的金鑰的隨機性。
對於RSA金鑰交換演算法來說,pre-master-key本身就是一個隨機數,再加上hello訊息中的隨機,三個隨機數通過一個金鑰匯出器最終匯出一個對稱金鑰。
pre master的存在在於SSL協議不信任每個主機都能產生完全隨機的隨機數,如果隨機數不隨機,那麼pre master secret就有可能被猜出來,那麼僅適用pre master secret作為金鑰就不合適了,因此必須引入新的隨機因素,那麼客戶端和伺服器加上pre master secret三個隨機數一同生成的金鑰就不容易被猜出了,一個偽隨機可能完全不隨機,可是是三個偽隨機就十分接近隨機了,每增加一個自由度,隨機性增加的可不是一。"
服務端證書驗證 (Certificate verify
)
傳送這個型別的握手需要2個前提條件
- 伺服器端請求了客戶端證書
- 客戶端傳送了非0長的證書
此時,客戶端想要證明自己擁有該證書,必然需要私鑰簽名一段資料發給伺服器驗證。
簽名的資料是客戶端傳送 certificate verify
前,所有收到和傳送的握手資訊(不包括5位元組的 record
)。其實這個流程和簽名 server key exchange
基本一樣。計算摘要,然後簽名運算。
加密標識 (Change cipher
)
指示 Server
從現在開始傳送的訊息都是加密過的。
伺服器握手結束通知 (Encrypted handshake message
)
其實這個報文的目的就是告訴對端自己在整個握手過程中收到了什麼資料,傳送了什麼資料。來保證中間沒人篡改報文。
其次,這個報文作用就是確認祕鑰的正確性。因為 Encrypted handshake message
是使用對稱祕鑰進行加密的第一個報文,如果這個報文加解密校驗成功,那麼就說明對稱祕鑰是正確的。
計算方法也比較簡單,將之前所有的握手資料(包括接受、傳送),計算 md
雜湊運算,然後計算 prf
,然後就是使用協商好的對稱金鑰進行加密了。
MD運算:
- 對於
TLS1.2
,摘要演算法是sha256
,即md_result = sha256(all_handshake)
; - 對於
TLS1.0 1.1
,摘要演算法是md5
和sha1
結果的拼接,即md_result = md5(all_handshake) + sha1(all_handshake)
。 - 特殊情況:如果加密套件中指定了
sha384
演算法,例如RSA_WITH_AES256_CBC_SHA384
加密套件,則無論協商使用tls
哪個版本,都用sha384
,即md_result = sha384(all_handshake)
。
PRF運算:
計算完雜湊後,客戶端按這種格式:“client finished”+ md_result
,作為 prf
的輸入。PRF
的輸出指定為 12位元組。12 位元組的資料前填充 4 位元組 message
頭部資訊,就可以送入對稱加密流程進行加密了。
PRF
運算其實就是 P_HASH
運算,P_HASH
就是不斷 hmac
運算,直到計算出預定指定長度的值為止。
祕鑰交換演算法
HTTPS
通過 TLS
層和證書機制提供了內容加密、身份認證和資料完整性三大功能,可以有效防止資料被監聽或篡改,還能抵禦 MITM
(中間人)攻擊。TLS
在實施加密過程中,需要用到非對稱金鑰交換和對稱內容加密兩大演算法。
對稱內容加密強度非常高,加解密速度也很快,只是無法安全地生成和保管金鑰。在 TLS
協議中,應用資料都是經過對稱加密後傳輸的,傳輸中所使用的對稱金鑰,則是在握手階段通過非對稱金鑰交換而來。常見的 AES-GCM
、ChaCha20-Poly1305
,都是對稱加密演算法。
非對稱金鑰交換能在不安全的資料通道中,產生只有通訊雙方才知道的對稱加密金鑰。目前最常用的金鑰交換演算法有 RSA
和 ECDHE
:RSA
歷史悠久,支援度好,但不支援 PFS
(Perfect Forward Secrecy
);而 ECDHE
是使用了 ECC
(橢圓曲線)的 DH
(Diffie-Hellman
)演算法,計算速度快,支援 PFS
。
只有非對稱金鑰交換,依然無法抵禦 MITM
攻擊,還得引入身份認證機制。對於大部分 HTTPS
網站來說,服務端一般通過 HTTP
應用層的帳號體系就能完成客戶端身份認證;而瀏覽器想要驗證服務端身份,需要用到服務端提供的證書。
在 RSA
金鑰交換中,瀏覽器使用證書提供的 RSA
公鑰加密相關資訊,如果服務端能解密,意味著服務端擁有證書對應的私鑰,同時也能算出對稱加密所需金鑰。金鑰交換和服務端認證合併在一起。
在 ECDHE
金鑰交換中,服務端使用證書私鑰對相關資訊進行簽名,如果瀏覽器能用證書公鑰驗證簽名,就說明服務端確實擁有對應私鑰,從而完成了服務端認證。金鑰交換和服務端認證是完全分開的。
可用於數字簽名的演算法主要有 RSA
和 ECDSA
,也就是目前金鑰交換 + 簽名主流選擇:
-
RSA
金鑰交換(無需簽名); -
ECDHE
金鑰交換、RSA
簽名; -
ECDHE
金鑰交換、ECDSA
簽名; -
ECDH
金鑰交換、RSA
簽名; -
ECDH
金鑰交換、ECDSA
簽名;
內建 ECDSA
公鑰的證書一般被稱之為 ECC
證書,內建 RSA
公鑰的證書就是 RSA
證書。由於 256 位 ECC Key
在安全性上等同於 3072 位 RSA Key
,加上 ECC
運算速度更快,ECDHE
金鑰交換 + ECDSA
數字簽名無疑是最好的選擇。由於同等安全條件下,ECC
演算法所需的 Key
更短,所以 ECC
證書檔案體積比 RSA
證書要小一些。
RSA
證書可以用於 RSA
金鑰交換(RSA
非對稱加密)或 ECDHE
金鑰交換(RSA
非對稱簽名);而 ECC
證書只能用於 ECDHE
金鑰交換(ECDSA
非對稱簽名)。
並不是所有瀏覽器都支援 ECDHE
金鑰交換,也就是說 ECC
證書的相容性要差一些。
完全正向保密
"Forward Secrecy
" 或 "Perfect Forward Secrecy
-完全正向保密" 協議描述了祕鑰協商(比如祕鑰交換)方法的特點。實際上這意味著及時你的伺服器的祕鑰有危險,通訊僅有可能被一類人竊聽,他們必須設法獲的每次會話都會生成的祕鑰對。
完全正向保密是通過每次握手時為祕鑰協商隨機生成金鑰對來完成(和所有會話一個 key
相反)。實現這個技術(提供完全正向保密-Perfect Forward Secrecy
)的方法被稱為 "ephemeral
"。
通常目前有2個方法用於完成完全正向保密(Perfect Forward Secrecy
):
DHE
- 一個迪菲-赫爾曼金鑰交換金鑰協議(Diffie Hellman key-agreement protocol
)短暫(ephemeral
)版本。
ECDHE
- 一個橢圓曲線金鑰交換金鑰協議( Elliptic Curve Diffie Hellman key-agreement protocol
)短暫(ephemeral
)版本。
短暫(ephemeral
)方法有效能缺點,因為生成 key
非常耗費資源。
RSA
金鑰交換演算法
我們先來看看傳統的祕鑰交換演算法—— RSA
祕鑰交換演算法。它是不符合完全正向保密的,但是是我們上面講的經典祕鑰交換。
RSA的核心涉及公鑰私鑰的概念
- 使用公鑰加密的資料只有私鑰能解密
- 使用私鑰加密的資料只有公鑰能解密
我們構建這麼一種場景,伺服器配置有公鑰+私鑰,客戶端是離散的。
RSA演算法流程文字描述如下:
- 任意客戶端對伺服器發起帶有隨機碼的請求,伺服器發回另一個隨機碼和自己的公鑰到客戶端(公鑰明文傳輸)。
- 客戶端生成隨機碼,和前兩個隨機碼合併,使用隨機數演算法生成一個金鑰
S
,使用收到的公鑰進行加密,生成C
,把C
傳送到伺服器。 - 伺服器收到
C
,使用公鑰對應的私鑰進行解密,得到S
。 - 上述交換步驟後,客戶端和伺服器都得到了
S
,S
為金鑰(預主金鑰)。
我們來看看上述過程中,為何第三方無法得到 S
。首先第一步後,客戶端有公鑰,伺服器有公鑰和私鑰。由於公鑰是明文傳輸的,所以可以假設第三方也有公鑰。
第二步後,客戶端傳送 C
,伺服器能夠使用自己的私鑰進行解密,而第三方只有公鑰,無法解密。即第三方無法計算得到 S
。
上述中,伺服器傳送的公鑰在 SSL
中是通過 certificate
報文傳送的,certificate
中的包含了公鑰。C
是通過 Client key exchange
報文傳送的。
其實,在實際 SSL
實際設計中,S
其實並沒有直接被當成金鑰加密,這裡為了描述原理,省去了對 S
後續進行 KDF
雜湊等操作,並不影響實際理解 RSA
。
RSA
有一個問題,就是如果私鑰洩漏,即私鑰被第三方知道,那麼第三方就能從 C
中解密得到 S
,即只要儲存所有的 A
和 B
的報文,等到私鑰被洩漏的那一天,或者有辦法快從 C
中計算 S
的方法出現(量子計算機分解大素數),那麼 A
和 B
就沒有什麼私密性可言了。
這就是所謂的前向不安全,私鑰參與了金鑰交換,安全性取決於私鑰是否安全儲存。
DHE
金鑰交換演算法
DHE演算法流程文字描述如下:
- 客戶端計算一個隨機值
Xa
,使用Xa
作為指數,即計算Pa = q^Xa mod p
,其中q
和p
是全世界公認的一對值。客戶端把Pa
傳送至伺服器,Xa
作為自己私鑰,僅且自己知道。 - 伺服器和客戶端計算流程一樣,生成一個隨機值
Xb
,使用Xb
作為指數,計算Pb = q^Xb mod p
,將結果Pb
傳送至客戶端,Xb
僅自己儲存。 - 客戶端收到
Pb
後計算Sa = Pb ^Xa mod p
;伺服器收到Pa
後計算Sb = Pa^Xb mod p
- 演算法保證了
Sa = Sb = S
,故金鑰交換成功,S
為金鑰(預主金鑰)。
上述途中,Sa
和 Sb
得到的結果是相同的,即記為 S
。
上述金鑰交換流程中,和 RSA
金鑰交換有較大不同,DHE
金鑰交換時,伺服器私鑰沒有參與進來。也就是說,私鑰即使洩漏,也不會導致會話加密金鑰 S
被第三方解密。
實際使用過程中,私鑰的功能被削弱到用來身份認證。
DHE
引數和 Pb
都是通過 server key exchange
傳送給客戶端,Pa
通過 client key exchange
傳送給伺服器。server key exchange
的結尾處需要使用伺服器私鑰對該報文字身進行簽名,以表明自己擁有私鑰。
ECDHE
金鑰交換演算法
只要理解 DHE
金鑰交換原理,那麼理解 ECDHE
金鑰交換原理其實並不難(如果不想深究的話)。
ECDHE
的運算是把 DHE
中模冪運算替換成了點乘運算,速度更快,可逆更難。
ECDHE
演算法流程文字描述如下:
- 客戶端隨機生成隨機值
Ra
,計算Pa(x, y) = Ra * Q(x, y)
,Q(x, y)
為全世界公認的某個橢圓曲線演算法的基點。將Pa(x, y)
傳送至伺服器。 - 伺服器隨機生成隨機值
Pb
,計算Pb(x,y) = Rb * Q(x, y)
。將Pb(x, y)
傳送至客戶端。 - 客戶端計算
Sa(x, y) = Ra * Pb(x, y)
;伺服器計算Sb(x, y) = Rb *Pa(x, y)
- 演算法保證了
Sa = Sb = S
,提取其中的S
的x
向量作為金鑰(預主金鑰)。
SSL
協議中,上圖中橢圓曲線名和 Pb
通過 server key exchange
報文傳送;Pa
通過 client key exchange
報文傳送。
ECDHE
與 ECDH
演算法的區別
字面少了一個 E
,E
代表了“臨時”,即在握手流程中,作為伺服器端,ECDH
少了一步計算 Pb
的過程,Pb
用證書中的公鑰代替,而證書對應的私鑰就是 Xb
。由此可見,使用 ECDH
金鑰交換演算法,伺服器必須採用 ECC
證書;伺服器不傳送 server key exchange
報文,因為傳送 certificate
報文時,證書本身就包含了 Pb
資訊。
ECDHE
與 RSA
的區別
ECDHE(DHE)
演算法屬於 DH
類金鑰交換演算法, 私鑰不參與金鑰的協商,故即使私鑰洩漏,客戶端和伺服器之間加密的報文都無法被解密。由於 ECDHE
每條會話都重新計算一個金鑰(Ra
、Rb
),故一條會話被解密後,其他會話仍舊安全。
然而,ECDH
演算法伺服器端的私鑰是固定的,即證書的私鑰作為 Rb
,故 ECDH
不被認為前向安全,因為私鑰洩漏相當於 Rb
洩漏,Rb
洩漏,導致會話金鑰可被第三方計算。
加密套件
加密套件(CipherList
)是指在 ssl
通訊中,伺服器和客戶端所使用的加密演算法的組合。在 ssl
握手初期,客戶端將自身支援的加密套件列表傳送給伺服器;在握手階段,伺服器根據自己的配置從中儘可能的選出一個套件,作為之後所要使用的加密方式。這些演算法包括:認證演算法、金鑰交換演算法、對稱演算法和摘要演算法等。
每種加密套件的名字裡包含了四部分資訊,分別是:
- 第一部分是金鑰交換,用於決定客戶端與伺服器之間在握手的過程中如何認證。使用非對稱加密演算法來生成會話金鑰,因為非對稱演算法不會將重要資料在通訊中傳輸。用到的演算法包括
RSA
,Diffie-Hellman
,ECDH
,PSK
等 - 第二部分是加密演算法,主要是對傳輸的資料進行加密傳輸用的。一般有對稱加和非對稱加密,但是非對稱加密演算法太耗效能,再者有些非對稱加密演算法有內容長度的限制,所以真正要傳輸的資料會使用對稱加密來進行加密。演算法名稱後通常會帶有兩個數字,分別表示金鑰的長度和初始向量的長度,比如
DES 56/56
,RC2 56/128
,RC4 128/128
,AES 128/128
,AES 256/256
- 第三部分是會話校驗(
MAC
)演算法,為了防止握手本身被竄改(這裡極容易和證書籤名演算法混淆)。演算法包括MD5
,SHA
等。 - 第四部分是
PRF
(偽隨機數函式),用於生成“master secret
”。
例如 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
從其名字可知,它是:
- 基於
TLS
協議的; - 使用
ECDHE
、RSA
作為金鑰交換演算法與證書籤名型別; - 加密演算法是
AES
(金鑰和初始向量的長度都是 256); -
MAC
演算法(這裡就是雜湊演算法)是SHA
。
在客戶端和伺服器端建立安全連線之前,雙方都必須指定適合自己的加密套件。加密套件的選擇可以通過組合的字串來控制。伺服器在選擇演算法時,會有優先順序,是以客戶端提供的的為最優,還是伺服器端配置的為最優。所謂的客戶端最優,就是根據客戶端提供的加密套件,從上到下,看是否有本地支援的,有的話則使用。所謂伺服器端最優,就是伺服器端根據自身配置的加密套件順序,一個個在 client hello
中找,找到了就使用。
其次,當伺服器配置 ECC
證書時,加密套件只能選擇 XXX_ECDSA_XXX
或者 ECDH_XXX
。當伺服器配置 RSA
證書時,只能選擇 RSA_XXX
或者 ECDHE_RSA_XXX
形式的加密套件。
需要注意的是,如果加密套件選擇 ECDH_RSA
或者 ECDH_ECDSA
時,由於 ECDH
加密套件預設表明了握手需要 ECC
公鑰(即 ECC
證書的公鑰充當握手中 server key exchange
中的公鑰,證書的私鑰同樣也是握手過程中的私鑰,握手過程不需要 server key exchange
),所以第二部分 _RSA
和 _ECDSA
表明的是想要的伺服器證書籤名型別。
換句話說,CA
頒發的證書可以是用 CA
自己的 RSA
私鑰進行簽名的 RSA
證書,也可以用 CA
的 ECC
私鑰進行簽名的 ECC
證書。具體是哪個,取決於 CA
部門和申請證書的類別。
證書內部儲存的 CSR
的公鑰是具體申請證書人員生成的,可以是 RSA
演算法的公鑰,也可以是 ECC
演算法的公鑰。取決於提交給 CA
之前生成的 CSR
方法。
比如說伺服器選擇了 ECDH_RSA
加密套件,但是傳送的證書卻是 ECDSA
簽名的證書,雖然說證書籤名型別不影響整個握手,但是對於校驗嚴格的客戶端,這種情況可能會導致客戶端斷開連結。
加密套件也可以用字串的形式,舉例:ALL:!ADH:RC4+RSA:+SSLv2:@STRENGTH
Openssl
定義了 4 中選擇符號:“+”,“-”,“!”,“@”。其中,“+”表示取交集;“-”表示臨時刪除一個演算法;“!”表示永久刪除一個演算法;“@“表示了排序方法。
多個描述之間可以用“:”、“,”、“ ”、“;”來分開。選擇加密套件的時候按照從左到的順序構成雙向連結串列,存放與記憶體中。
ALL:!ADH:RC4+RSA:+SSLv2:@STRENGTH
表示的意義是:
首先選擇所有的加密套件(不包含eNULL,即空對稱加密演算法),然後在得到的雙向連結串列之中去掉身份驗證採用 DH
的加密套件;加入包含 RC4
演算法並將包含 RSA
的加密套件放在雙向連結串列的尾部;再將支援 SSLV2
的加密套件放在尾部;最後得到的結果按照安全強度進行排序。
可以使用命令 : openssl ciphers -V 'ALL:!ADH:RC4+RSA:+SSLv2:@STRENGTH' | column -t
來檢視具體加密套件。