如何一步步構(gòu)建安全的 HTTPS 站點(diǎn)
通常一個(gè) web 站點(diǎn)開(kāi)啟 HTTPS ,以 nginx 為例,我們可以這樣進(jìn)行配置:
- server {
- listen 443 ssl http2;
- server_name www.example.com;
- index index.html index.htm;
- root /www/www;
- ssl on;
- ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
- ssl_certificate /usr/local/nginx/ssl/example.com.rsa.cer;
- ssl_certificate_key /usr/local/nginx/ssl/example.com.rsa.key;
- ssl_ciphers AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5;;
- }
上述 nginx 配置中包含了配置監(jiān)聽(tīng)端口、開(kāi)啟ssl、配置證書(shū)、以及支持的加密算法。一般來(lái)說(shuō)用戶(hù)訪問(wèn)域名不可能直接在瀏覽器的地址欄中輸入 https://www.example.com 來(lái)進(jìn)行訪問(wèn),而是輸入域名,默認(rèn)情況下是通過(guò) HTTP 協(xié)議來(lái)進(jìn)行訪問(wèn)的,即 http://www.example.com,因此,在nginx 的配置中我們還需要定義一個(gè)server 段來(lái)處理 HTTP 的訪問(wèn)。
- server {
- listen 80 default_server;
- server_name _ www.example.com;
- location / {
- return 302 https://$host$request_uri;
- }
- }
上述配置中監(jiān)聽(tīng)了 80端口,并且定義了一個(gè) location,將 HTTP 請(qǐng)求 302 跳轉(zhuǎn)到 HTTPS 的Host 去。這樣就實(shí)現(xiàn)了用戶(hù)不管怎么訪問(wèn)都可以跳轉(zhuǎn)到 HTTPS 。
但是問(wèn)題來(lái)了,這樣的配置其實(shí)是有缺陷的,如果用戶(hù)端從瀏覽器手動(dòng)輸入的是 HTTP 地址,或者從其它地方點(diǎn)擊了網(wǎng)站的 HTTP 鏈接,那么瀏覽器會(huì)依賴(lài)于服務(wù)端 301/302 跳轉(zhuǎn)才能使用 HTTPS 服務(wù)。而第一次的 HTTP 請(qǐng)求就有可能被劫持,因?yàn)橹虚g的數(shù)據(jù)傳輸是明文的,就有可能會(huì)導(dǎo)致請(qǐng)求無(wú)法到達(dá)服務(wù)器,從而構(gòu)成 HTTPS 降級(jí)劫持。
要解決降級(jí)劫持,我們可以使用HSTS。
什么是 HSTS?
HSTS(HTTP Strict Transport Security,HTTP 嚴(yán)格傳輸安全),是一套由互聯(lián)網(wǎng)工程任務(wù)組發(fā)布的互聯(lián)網(wǎng)安全策略機(jī)制。網(wǎng)站可以通過(guò)配置 HSTS,來(lái)強(qiáng)制瀏覽器使用 HTTPS 與網(wǎng)站通信,保障網(wǎng)站更加安全。
HSTS的作用是強(qiáng)制客戶(hù)端(如瀏覽器)使用HTTPS與服務(wù)器創(chuàng)建連接。服務(wù)器開(kāi)啟HSTS的方法是,當(dāng)客戶(hù)端通過(guò)HTTPS發(fā)出請(qǐng)求時(shí),在服務(wù)器返回的超文本傳輸協(xié)議響應(yīng)頭中包含 `Strict-Transport-Security` 字段。非加密傳輸時(shí)設(shè)置的`HSTS`字段無(wú)效。
比如,`https://example.com/`的響應(yīng)頭含有`Strict-Transport-Security: max-age=31536000; includeSubDomains`。這意味著兩點(diǎn):
在接下來(lái)的一年(即31536000秒)中,瀏覽器只要向`example.com`或其子域名發(fā)送HTTP請(qǐng)求時(shí),必須采用`HTTPS`來(lái)發(fā)起連接。比如,用戶(hù)點(diǎn)擊超鏈接或在地址欄輸入 `http://www.example.com/` ,瀏覽器應(yīng)當(dāng)自動(dòng)將 http 轉(zhuǎn)寫(xiě)成 `https`,然后直接向 `https://www.example.com/` 發(fā)送請(qǐng)求。
在接下來(lái)的一年中,如果 `example.com` 服務(wù)器發(fā)送的`TLS`證書(shū)無(wú)效,用戶(hù)不能忽略瀏覽器警告繼續(xù)訪問(wèn)網(wǎng)站。
如何進(jìn)行配置?
以 nginx 為例,我們?cè)趯?duì)應(yīng)域名的 vhost 中增加響應(yīng)頭:
- server {
- ....
- add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
- ...
- }
參數(shù)解釋?zhuān)?/p>
- max-age,單位是秒,用來(lái)告訴瀏覽器在指定時(shí)間內(nèi),這個(gè)網(wǎng)站必須通過(guò) HTTPS 協(xié)議來(lái)訪問(wèn)。也就是對(duì)于這個(gè)網(wǎng)站的 HTTP 地址,瀏覽器需要先在本地替換為 HTTPS 之后再發(fā)送請(qǐng)求。
- includeSubDomains,可選參數(shù),如果指定這個(gè)參數(shù),表明這個(gè)網(wǎng)站所有子域名也必須通過(guò) HTTPS 協(xié)議來(lái)訪問(wèn)。
- preload,可選參數(shù),HSTS 這個(gè)響應(yīng)頭只能用于 HTTPS 響應(yīng);網(wǎng)站必須使用默認(rèn)的 443 端口;必須使用域名,不能是 IP。而且啟用 HSTS 之后,一旦網(wǎng)站證書(shū)錯(cuò)誤,用戶(hù)無(wú)法選擇忽略。
瀏覽器請(qǐng)求后響應(yīng)頭中會(huì)顯示:
- strict-transport-security:max-age=31536000
如圖所示:

HSTS 可以很好地解決 HTTPS 降級(jí)攻擊,但是對(duì)于 HSTS 生效前的首次 HTTP 請(qǐng)求,依然無(wú)法避免被劫持。瀏覽器廠商們?yōu)榱私鉀Q這個(gè)問(wèn)題,提出了 HSTS Preload List 方案:內(nèi)置一份可以定期更新的列表,對(duì)于列表中的域名,即使用戶(hù)之前沒(méi)有訪問(wèn)過(guò),也會(huì)使用 HTTPS 協(xié)議。
目前這個(gè) Preload List 由 Google Chrome 維護(hù),Chrome、Firefox、Safari、IE 11 和 Microsoft Edge 都在使用。如果要想把自己的域名加進(jìn)這個(gè)列表,首先需要滿足以下條件:
- 擁有合法的證書(shū)(如果使用 SHA-1 證書(shū),過(guò)期時(shí)間必須早于 2016 年);
- 將所有 HTTP 流量重定向到 HTTPS;
- 確保所有子域名都啟用了 HTTPS;
- 輸出 HSTS 響應(yīng)頭:
- max-age 不能低于 18 周(10886400 秒);
- 必須指定 includeSubdomains 參數(shù);
- 必須指定 preload 參數(shù);
但是,即便滿足了上述所有條件,也不一定能進(jìn)入 HSTS Preload List,更多信息可以看這里(https://hstspreload.org/)。通過(guò) Chrome 的 chrome://net-internals/#hsts 工具,可以查詢(xún)某個(gè)網(wǎng)站是否在 Preload List 之中,還可以手動(dòng)把某個(gè)域名加到本機(jī) Preload List。
對(duì)于 HSTS 以及 HSTS Preload List,我的建議是只要你不能確保永遠(yuǎn)提供 HTTPS 服務(wù),就不要啟用。因?yàn)橐坏?HSTS 生效,你再想把網(wǎng)站重定向?yàn)?HTTP,之前的老用戶(hù)會(huì)被無(wú)限重定向,唯一的辦法是換新域名。
如果確定要開(kāi)啟,點(diǎn)擊https://hstspreload.org,輸入你的域名,勾選協(xié)議,提交即可。

確認(rèn)后,你就可以將你的域名提交給 HSTS 預(yù)加載列表了:

提交成功后會(huì)給你返回成功的信息,不過(guò)你要保證你的配置比如是一直開(kāi)啟了,否則也會(huì)從列表中刪除。

再次訪問(wèn),查看瀏覽器響應(yīng)頭:

此外,我們要做到讓HTTPS 網(wǎng)站更安全更快速,還應(yīng)當(dāng)做到以下幾點(diǎn):
第一,密鑰要足夠的復(fù)雜,以rsa 密鑰對(duì)為例,最好超過(guò)2048位;
第二,ssl_ciphers 的合理配置,盡量拋棄那些已經(jīng)被證明不安全的加密算法,使用較新的被證明無(wú)安全威脅的算法,例如可以這樣配置:
- 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:TLS-CHACHA20-POLY1305-SHA256:TLS-AES-256-GCM-SHA384:TLS-AES-128-GCM-SHA256:EECDH+CHACHA20:EECDH+AESGCM:EECDH+AES:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!KRB5:!aECDH:!EDH+3DES;
第三,避免使用已經(jīng)被證明不安全的加密協(xié)議,例如 SSLV2和SSLV3 ,而使用 TLSv1.2 TLSv1.3;
- ssl_protocols TLSv1.2 TLSv1.3;
一般來(lái)說(shuō)較新的協(xié)議都是針對(duì)上一個(gè)版本進(jìn)行了很多的優(yōu)化,比如TLS1.2和TLS1.3協(xié)議,可以看下這2個(gè)協(xié)議的加密過(guò)程,首先,我們看下 TLS1.2的加密過(guò)程:

以 ECDHE 密鑰交換算法為例,TLS1.2協(xié)議完整的SSL握手過(guò)程如下:
- 第一步,首先客戶(hù)端發(fā)送ClientHello消息,該消息中主要包括客戶(hù)端支持的協(xié)議版本、加密套件列表及握手過(guò)程需要用到的ECC擴(kuò)展信息;
- 第二步,服務(wù)端回復(fù)ServerHello,包含選定的加密套件和ECC擴(kuò)展;發(fā)送證書(shū)給客戶(hù)端;選用客戶(hù)端提供的參數(shù)生成ECDH臨時(shí)公鑰,同時(shí)回復(fù)ServerKeyExchange消息;
- 第三步,客戶(hù)端接收ServerKeyExchange后,使用證書(shū)公鑰進(jìn)行簽名驗(yàn)證,獲取服務(wù)器端的ECDH臨時(shí)公鑰,生成會(huì)話所需要的共享密鑰;生成ECDH臨時(shí)公鑰和ClientKeyExchange消息發(fā)送給服務(wù)端;
- 第四步,服務(wù)器處理ClientKeyExchange消息,獲取客戶(hù)端ECDH臨時(shí)公鑰;服務(wù)器生成會(huì)話所需要的共享密鑰;發(fā)送密鑰協(xié)商完成消息給客戶(hù)端;
- 第五步,雙方使用生成的共享密鑰對(duì)消息加密傳輸,保證消息安全。
可以看到,TLS1.2 協(xié)議中需要加密套件協(xié)商、密鑰信息交換、ChangeCipherSpec 協(xié)議通告等過(guò)程,需要消耗 2-RTT 的握手時(shí)間,這也是造成 HTTPS 協(xié)議慢的一個(gè)重要原因之一。
通過(guò)抓包分析,我們可以看到他的整個(gè)加密過(guò)程:

接下來(lái),我們看下 TLS 1.3 的的交互過(guò)程,如圖所示:

抓包得后如圖所示,可以看到客戶(hù)端的整個(gè)加密過(guò)程:

在 TLS 1.3 中,客戶(hù)端首先不僅發(fā)送 ClientHello 支持的密碼列表,而且還猜測(cè)服務(wù)器將選擇哪種密鑰協(xié)商算法,并發(fā)送密鑰共享,這可以節(jié)省很大一部分的開(kāi)銷(xiāo),從而提高了速度。
TLS1.3 提供 1-RTT 的握手機(jī)制,還是以 ECDHE 密鑰交換過(guò)程為例,握手過(guò)程如下。將客戶(hù)端發(fā)送 ECDH 臨時(shí)公鑰的過(guò)程提前到 ClientHello ,同時(shí)刪除了 ChangeCipherSpec 協(xié)議簡(jiǎn)化握手過(guò)程,使第一次握手時(shí)只需要1-RTT,來(lái)看具體的流程:
- 客戶(hù)端發(fā)送 ClientHello 消息,該消息主要包括客戶(hù)端支持的協(xié)議版本、DH密鑰交換參數(shù)列表KeyShare;
- 服務(wù)端回復(fù) ServerHello,包含選定的加密套件;發(fā)送證書(shū)給客戶(hù)端;使用證書(shū)對(duì)應(yīng)的私鑰對(duì)握手消息簽名,將結(jié)果發(fā)送給客戶(hù)端;選用客戶(hù)端提供的參數(shù)生成 ECDH 臨時(shí)公鑰,結(jié)合選定的 DH 參數(shù)計(jì)算出用于加密 HTTP 消息的共享密鑰;服務(wù)端生成的臨時(shí)公鑰通過(guò) KeyShare 消息發(fā)送給客戶(hù)端;
- 客戶(hù)端接收到 KeyShare 消息后,使用證書(shū)公鑰進(jìn)行簽名驗(yàn)證,獲取服務(wù)器端的 ECDH 臨時(shí)公鑰,生成會(huì)話所需要的共享密鑰;
- 雙方使用生成的共享密鑰對(duì)消息加密傳輸,保證消息安全。
如果客戶(hù)端之前已經(jīng)連接,我們有辦法在 1.2 中進(jìn)行 1-RTT 連接,而在 TLS 1.3 中允許我們執(zhí)行 0-RTT連接,如圖所示:

當(dāng)然,具體采用 TLS1.2 還是 TLS1.3 需要根據(jù)實(shí)際的業(yè)務(wù)場(chǎng)景和用戶(hù)群體來(lái)決定,在較新版本的瀏覽器一般都支持最新的加密協(xié)議,而類(lèi)似 IE 8 以及Windows xp 這種古老的瀏覽器和操作系統(tǒng)就不支持了。如果說(shuō)你的用戶(hù)是一些政府部門(mén)的客戶(hù),那么就不適合采用這種較新的技術(shù)方案了,因?yàn)閾?jù)我所知很多政府部門(mén)的操作系統(tǒng)還是xp和 IE 8以下的版本,這會(huì)導(dǎo)致新協(xié)議無(wú)法在他們的操作系統(tǒng)中正常工作。因此你可以講加密算法和加密協(xié)議多配置幾個(gè),向下兼容不同客戶(hù)端。
第四,證書(shū)要從可靠的CA廠商申請(qǐng),因?yàn)椴豢煽康膹S商(比如不被主流瀏覽器信任的證書(shū)廠商)會(huì)亂修改證書(shū)日期,重復(fù)簽發(fā)證書(shū)。此外即使是可靠的 CA 簽發(fā)的證書(shū)也有可能是偽造的,比如賽門(mén)鐵克之前就被曝出丑聞而被火狐和Chrome 懲罰,結(jié)果就是這些主流瀏覽器不在信任這些CA 機(jī)構(gòu)簽發(fā)的一部分證書(shū)。因此一旦發(fā)現(xiàn)證書(shū)不受信任要盡快替換。

第五,使用完整的證書(shū)鏈,如果證書(shū)鏈不完整,則很有可能在一些版本的瀏覽器上訪問(wèn)異常。
第六,使用HTTP/2,使用最新的 HTTP 2 可以提升網(wǎng)站的訪問(wèn)速度以及擁有更好的性能支持。
第七,保護(hù)證書(shū)私鑰不被外泄。
第八,根據(jù)自己的業(yè)務(wù)需求選擇合適的證書(shū),證書(shū)分為自簽證書(shū)、 DV、 EV 和OV 證書(shū),一般來(lái)說(shuō)只是需要進(jìn)行簡(jiǎn)單的數(shù)據(jù)加密,采用 DV 證書(shū)即可,這類(lèi)證書(shū)通常都可以免費(fèi)申請(qǐng),只需要進(jìn)行簡(jiǎn)單的域名所有者權(quán)驗(yàn)證即可申請(qǐng),而EV和OV證書(shū)一般價(jià)格昂貴,適合金融機(jī)構(gòu)或針對(duì)數(shù)據(jù)加密有嚴(yán)格要求的單位使用,這類(lèi)證書(shū)簽發(fā)手續(xù)復(fù)雜,一般需要進(jìn)行企業(yè)身份認(rèn)證后才會(huì)簽發(fā)。自簽證書(shū)一般用戶(hù)臨時(shí)測(cè)試使用,不建議生產(chǎn)環(huán)境使用,因?yàn)樗⒉皇鞘苄湃蔚腃A 機(jī)構(gòu)簽發(fā)的,瀏覽器不會(huì)信任。

當(dāng)我們配置完后,可以通過(guò)https://www.ssllabs.com/ssltest/ ,對(duì)你的 HTTPS 站點(diǎn)進(jìn)行評(píng)分,如果是A+則說(shuō)明你的站點(diǎn)安全性特別高。如圖所示,如果評(píng)分不高,你可以查看具體的詳情來(lái)針對(duì)你的站點(diǎn)進(jìn)行更具體的優(yōu)化。

最后,附上一份nginx 的配置,作為參考:
- server
- {
- listen 443 ssl http2 default_server;
- server_name www.example.com ;
- index index.html index.htm index.php;
- root /web;
- ssl on;
- ssl_certificate /nginx/ssl/awen/fullchain.cer;
- ssl_certificate_key /nginx/ssl/example/example.com.key;
- 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:TLS-CHACHA20-POLY1305-SHA256:TLS-AES-256-GCM-SHA384:TLS-AES-128-GCM-SHA256:EECDH+CHACHA20:EECDH+AESGCM:EECDH+AES:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!KRB5:!aECDH:!EDH+3DES;
- ssl_prefer_server_ciphers on;
- ssl_protocols TLSv1.2 TLSv1.3;
- ssl_session_cache shared:SSL:50m;
- ssl_session_timeout 1d;
- ssl_session_tickets on;
- resolver 114.114.114.114 valid=300s;
- resolver_timeout 10s;
- add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
- add_header X-Frame-Options deny;
- add_header X-Content-Type-Options nosniff;
- add_header CIP $http_x_real_ip;
- add_header Accept-Ranges bytes;
- }
- server {
- listen 80;
- server_name _;
- server_name www.example.com ;
- return 302 https://$host$request_uri;
- }
好了,以上就是我給大家分享的關(guān)于 HTTPS 站點(diǎn)的優(yōu)化建議。