1. 程式人生 > >開始使用 ECC 證書

開始使用 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 NameSubject Alternative Name 欄位之中。

在 RSA 金鑰交換中,瀏覽器使用證書提供的 RSA 公鑰加密相關資訊,如果服務端能解密,意味著服務端擁有證書對應的私鑰,同時也能算出對稱加密所需金鑰。金鑰交換和服務端認證合併在一起。

在 ECDHE 金鑰交換中,服務端使用證書私鑰對相關資訊進行簽名,如果瀏覽器能用證書公鑰驗證簽名,就說明服務端確實擁有對應私鑰,從而完成了服務端認證。金鑰交換和服務端認證是完全分開的。

可用於 ECDHE 數字簽名的演算法主要有 RSA 和 ECDSA,也就是目前金鑰交換 + 簽名有三種主流選擇:

  • RSA 金鑰交換(無需簽名);
  • ECDHE 金鑰交換、RSA 簽名;
  • ECDHE 金鑰交換、ECDSA 簽名;

以下是 Chrome 中這三種金鑰交換方式的截圖(截圖來自於早期 Chrome,新版 Chrome 檢視位置有了變化):

key exchange

內建 ECDSA 公鑰的證書一般被稱之為 ECC 證書,內建 RSA 公鑰的證書就是 RSA 證書。由於 256 位 ECC Key 在安全性上等同於 3072 位 RSA Key,加上 ECC 運算速度更快,ECDHE 金鑰交換 + ECDSA 數字簽名無疑是最好的選擇。由於同等安全條件下,ECC 演算法所需的 Key 更短,所以 ECC 證書檔案體積比 RSA 證書要小一些。以下是本站的對比,可以看到左側的 ECC 證書要小 1/3:

ecc certificate vs rsa certificate

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 天前,文中所描述的資訊可能已發生改變,請謹慎使用。