開始使用 ECC 證書
文章目錄
提醒:本文最後更新於 829 天前,文中所描述的資訊可能已發生改變,請謹慎使用。
我之前的文章多次提到 ECC 證書,但一直沒有專門介紹 ECC 證書的文章,今天補上。本文包含三部分內容:1)簡單介紹 ECC 證書是什麼;2)介紹如何申請 ECC 證書;3)以 Nginx 為例介紹如何使用 ECC 證書。順便說下,本站已經用上了 ECC 證書。要檢視本站主要支援哪些技術特性,可以點這裡。
簡單介紹
HTTPS 通過 TLS 層和證書機制提供了內容加密、身份認證和資料完整性三大功能,可以有效防止資料被監聽或篡改,還能抵禦 MITM(中間人)攻擊。TLS 在實施加密過程中,需要用到非對稱金鑰交換和對稱內容加密兩大演算法。
對稱內容加密強度非常高,加解密速度也很快,只是無法安全地生成和保管金鑰。在 TLS 協議中,應用資料都是經過對稱加密後傳輸的,傳輸中所使用的對稱金鑰,則是在握手階段通過非對稱金鑰交換而來。常見的 AES-GCM、ChaCha20-Poly1305,都是對稱加密演算法。
非對稱金鑰交換能在不安全的資料通道中,產生只有通訊雙方才知道的對稱加密金鑰。目前最常用的金鑰交換演算法有 RSA 和 ECDHE:RSA 歷史悠久,支援度好,但不支援 PFS(Perfect Forward Secrecy);而 ECDHE 是使用了 ECC(橢圓曲線)的 DH(Diffie-Hellman)演算法,計算速度快,支援 PFS。要了解更多 RSA 和 ECDHE 金鑰交換的細節,可以閱讀 Cloudflare 的
只有非對稱金鑰交換,依然無法抵禦 MITM 攻擊,還得引入身份認證機制。對於大部分 HTTPS 網站來說,服務端一般通過 HTTP 應用層的帳號體系就能完成客戶端身份認證;而瀏覽器想要驗證服務端身份,需要用到服務端提供的證書。
瀏覽器會在兩個步驟中用到證書:1)證書合法性校驗。確保證書由合法 CA 簽署,且適用於當前網站;2)使用證書提供的非對稱加密公鑰,完成金鑰交換和服務端認證。
證書合法性校驗的原理,簡單總結如下:
- 根據版本號、序列號、簽名演算法標識、發行者名稱、有效期、證書主體名、證書主體公鑰資訊、發行商唯一標識、主體唯一標識、擴充套件等資訊,生成 TBSCertificate(To Be Signed Certificate)資訊;
- 簽發數字簽名:使用 HASH 函式對 TBSCertificate 計算得到訊息摘要,再用 CA 的私鑰進行加密,得到簽名;
- 校驗數字簽名:使用相同的 HASH 函式對 TBSCertificate 計算得到訊息摘要,與使用 CA 公鑰解密簽名得到內容相比較;
可以看到校驗證書需要同時用到簽名和非對稱加密演算法:目前必須使用 SHA-2 做為證書籤名函式(沒有打 XP SP3 補丁的 IE6 不支援);目前一般使用 RSA 演算法對 TBSCertificate 進行非對稱加密。可以通過 openssl 工具來檢視證書籤名演算法:
$ openssl x509 -in chained.pem -noout -text | grep 'Signature Algorithm'
Signature Algorithm: sha256WithRSAEncryption
大部分 CA 都有證書鏈,瀏覽器對於收到的多級證書,需要從站點證書開始逐級驗證,直至出現作業系統或瀏覽器內建的受信任 CA 根證書。
瀏覽器還需要校驗當前訪問的域名是否存在於證書 TBSCertificate 的 Common Name
或 Subject Alternative Name
欄位之中。
在 RSA 金鑰交換中,瀏覽器使用證書提供的 RSA 公鑰加密相關資訊,如果服務端能解密,意味著服務端擁有證書對應的私鑰,同時也能算出對稱加密所需金鑰。金鑰交換和服務端認證合併在一起。
在 ECDHE 金鑰交換中,服務端使用證書私鑰對相關資訊進行簽名,如果瀏覽器能用證書公鑰驗證簽名,就說明服務端確實擁有對應私鑰,從而完成了服務端認證。金鑰交換和服務端認證是完全分開的。
可用於 ECDHE 數字簽名的演算法主要有 RSA 和 ECDSA,也就是目前金鑰交換 + 簽名有三種主流選擇:
- RSA 金鑰交換(無需簽名);
- ECDHE 金鑰交換、RSA 簽名;
- ECDHE 金鑰交換、ECDSA 簽名;
以下是 Chrome 中這三種金鑰交換方式的截圖(截圖來自於早期 Chrome,新版 Chrome 檢視位置有了變化):
內建 ECDSA 公鑰的證書一般被稱之為 ECC 證書,內建 RSA 公鑰的證書就是 RSA 證書。由於 256 位 ECC Key 在安全性上等同於 3072 位 RSA Key,加上 ECC 運算速度更快,ECDHE 金鑰交換 + ECDSA 數字簽名無疑是最好的選擇。由於同等安全條件下,ECC 演算法所需的 Key 更短,所以 ECC 證書檔案體積比 RSA 證書要小一些。以下是本站的對比,可以看到左側的 ECC 證書要小 1/3:
RSA 證書可以用於 RSA 金鑰交換(RSA 非對稱加密)或 ECDHE 金鑰交換(RSA 非對稱簽名);而 ECC 證書只能用於 ECDHE 金鑰交換(ECDSA 非對稱簽名)。
並不是所有瀏覽器都支援 ECDHE 金鑰交換,也就是說 ECC 證書的相容性要差一些。例如在 Windows XP 中,使用 ECC 證書的網站只有 Firefox 能訪問(Firefox 的 TLS 自己實現,不依賴作業系統);Android 平臺中,也需要 Android 4+ 才支援 ECC 證書。
好訊息是,Nginx 1.11.0 開始提供了對 RSA/ECC 雙證書的支援。它的實現原理是:分析在 TLS 握手中雙方協商得到的 Cipher Suite,如果支援 ECDSA 就返回 ECC 證書,否則返回 RSA 證書。
也就是說,配合最新的 Nginx,我們可以使用 ECC 證書為現代瀏覽器提供更好的體驗,同時老舊瀏覽器依然會得到 RSA 證書,從而保證了相容性。這一次,魚與熊掌可以兼得。
如何申請
如果你的 CA 支援簽發 ECC 證書,使用以下命令生成 CSR(Certificate Signing Request,證書籤名請求)檔案並提交給提供商,就可以獲得 ECC 證書:
openssl ecparam -genkey -name secp256r1 | openssl ec -out ecc.key
openssl req -new -key ecc.key -out ecc.csr
以上命令中可供選擇的演算法有 secp256r1 和 secp384r1,secp521r1 已被 Chrome 和 Firefox 廢棄。
我目前在用的 Let's Encrypt,也支援簽發 ECC 證書。我使用了 acme.sh 這個小巧的工具來簽發證書,指定 -k ec-256
就可以將證書型別改為 ECC:
"/root/.acme.sh"/acme.sh --issue --dns dns_cx -d imququ.com -d www.imququ.com -k ec-256
目前 Let's Encrypt 只提供 RSA 中間證書,官方預計會在 2017 年 3 月底提供 ECC 中間證書(via)。
如何使用
有了 RSA/ECC 雙證書之後,還需要安裝 Nginx 1.11.x。這部分內容我之前詳細寫過,請點選檢視。
一切準備妥當後,將證書配置改為雙份即可:
ssl_certificate example.com.rsa.crt;
ssl_certificate_key example.com.rsa.key;
ssl_certificate example.com.ecdsa.crt;
ssl_certificate_key example.com.ecdsa.key;
問題來了!本站使用 Cloudflare 提供的 Cipher Suites 配置,在 Nginx 中配置了雙證書並重啟,用 Chrome 測試發現仍然沒有采用 ECC 證書。這是為什麼呢?
# https://github.com/cloudflare/sslconfig/blob/master/conf
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
研究發現,Chrome 與服務端協商到的 Cipher Suites 是 ECDHE-RSA-AES128-GCM-SHA256
,來自於 ssl_ciphers
配置中的 EECDH+AES128
這部分。我們通過 openssl 工具看一下 EECDH+AES128
具體包含哪些 Cipher Suites:
openssl ciphers -V 'EECDH+AES128' | column -t
0xC0,0x2F - ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD
0xC0,0x2B - ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(128) Mac=AEAD
0xC0,0x27 - ECDHE-RSA-AES128-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AES(128) Mac=SHA256
0xC0,0x23 - ECDHE-ECDSA-AES128-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(128) Mac=SHA256
0xC0,0x13 - ECDHE-RSA-AES128-SHA SSLv3 Kx=ECDH Au=RSA Enc=AES(128) Mac=SHA1
0xC0,0x09 - ECDHE-ECDSA-AES128-SHA SSLv3 Kx=ECDH Au=ECDSA Enc=AES(128) Mac=SHA1
可以看到,使用 RSA 做為簽名認證演算法(Au=RSA)的加密套件排到了前面,導致 Nginx 作出了錯誤判斷。
知道原因就好辦了,將這段配置改為 EECDH+ECDSA+AES128:EECDH+aRSA+AES128
,再看一下:
openssl ciphers -V 'EECDH+ECDSA+AES128:EECDH+aRSA+AES128' | column -t
0xC0,0x2B - ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(128) Mac=AEAD
0xC0,0x23 - ECDHE-ECDSA-AES128-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(128) Mac=SHA256
0xC0,0x09 - ECDHE-ECDSA-AES128-SHA SSLv3 Kx=ECDH Au=ECDSA Enc=AES(128) Mac=SHA1
0xC0,0x2F - ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD
0xC0,0x27 - ECDHE-RSA-AES128-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AES(128) Mac=SHA256
0xC0,0x13 - ECDHE-RSA-AES128-SHA SSLv3 Kx=ECDH Au=RSA Enc=AES(128) Mac=SHA1
這下就沒問題了。
並不是所有加密套件都需要把 ECDSA 和 aRSA 分開寫,例如 EECDH+CHACHA20
就不需要,ECDSA 預設就在前面:
openssl ciphers -V 'EECDH+CHACHA20' | column -t
0xCC,0xA9 - ECDHE-ECDSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH Au=ECDSA Enc=ChaCha20-Poly1305 Mac=AEAD
0xCC,0xA8 - ECDHE-RSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH Au=RSA Enc=ChaCha20-Poly1305 Mac=AEAD
最終,我的 Cipher Suites 配置如下,供參考:
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5;
--EOF--
提醒:本文最後更新於 829 天前,文中所描述的資訊可能已發生改變,請謹慎使用。