引子 前幾天看到微信後臺團隊分享了TLS相關文章,正好gRPC里TLS數據加密是很重要的一塊,於是整理出了這篇文章。 在gRPC里,如果僅僅是用來做後端微服務,可以考慮不加密。本文太長,先給個大綱。 1. HTTPS,HTTP/2介紹 2. TLS加密原理、實現庫 3. HTTP/2協議協商機制 4 ...
引子
前幾天看到微信後臺團隊分享了TLS相關文章,正好gRPC里TLS數據加密是很重要的一塊,於是整理出了這篇文章。
在gRPC里,如果僅僅是用來做後端微服務,可以考慮不加密。本文太長,先給個大綱。
1. HTTPS,HTTP/2介紹
2. TLS加密原理、實現庫
3. HTTP/2協議協商機制
4. 自建數字證書(CA)
5. gRPC使用TLS
1. HTTP/1.x
目前絕大多數網站和APP都是建立在HTTP之上的,所有的數據都是明文傳輸,沒有任何安全可言。
網圖
2. HTTPS
HTTPS(Hypertext Transfer Protocol over Secure Socket Layer)是以安全為目標的HTTP通道,即HTTP下加入SSL層,HTTPS的安全基礎是SSL。用來保護用戶隱私,防止流量劫持。
(網圖,懶得畫了)
2.1 HTTPS的作用(來自百度)
認證用戶和伺服器,確保數據發送到正確的客戶機和伺服器;(驗證證書)
加密數據以防止數據中途被竊取;(加密)
維護數據的完整性,確保數據在傳輸過程中不被改變。(摘要演算法)
HTTPS之所以安全,就是HTTP建立在SSL/TLS之上的。
(網圖)
SSL/TLS協議的基本思路是採用公鑰加密法,也就是說,客戶端先向伺服器端索要公鑰,然後用公鑰加密信息,伺服器收到密文後,用自己的私鑰解密。
(1)如何保證公鑰不被篡改?
將公鑰放在數字證書中。只要證書是可信的,公鑰就是可信的。
(2)公鑰加密計算量太大,如何減少耗用的時間?
每一次對話,客戶端和伺服器端都生成一個”對話密鑰”,用它來加密信息。由於”對話密鑰”是對稱加密,所以運算速度非常快,而伺服器公鑰只用於加密”對話密鑰”本身,這樣就減少了加密運算的消耗時間。
也就是說,對於HTTPS,由於成本問題
-
握手階段(handshake)用的非對稱加密
-
數據通信用的是對稱加密
2.2 加密演算法
我們大致的講一下加密相關術語。由於密碼學太過複雜,我們不去深究,也千萬別問我為什麼公鑰加密後,能夠用私鑰解密。
(主要是數學太難,門檻太高,我也不懂,逃。。。)
對稱密鑰
又稱為共用密鑰加密,對稱密鑰在加密和解密的過程中使用的密鑰是相同的,常見的對稱加密演算法有DES、3DES、AES、RC5、RC6。對稱密鑰的優點是計算速度快,但是密鑰需要在通訊的兩端共用,讓彼此知道密鑰是什麼對方纔能正確解密,如果所有客戶端都共用同一個密鑰,那麼這個密鑰就像萬能鑰匙一樣,可以憑藉一個密鑰破解所有人的密文了。
非對稱密鑰
服務端會生成一對密鑰,一個私鑰保存在服務端,僅自己知道,另一個是公鑰,公鑰可以自由發佈供任何人使用。客戶端的明文通過公鑰加密後的密文需要用私鑰解密。非對稱密鑰在加密和解密的過程的使用的密鑰是不同的密鑰,加密和解密是不對稱的,所以稱之為非對稱加密。與對稱密鑰加密相比,非對稱加密無需在客戶端和服務端之間共用密鑰,只要私鑰不發給任何用戶,即使公鑰在網上被截獲,也無法被解密,僅有被竊取的公鑰是沒有任何用處的。常見的非對稱加密有RSA。
數字簽名
數字簽名就如同日常生活中的簽名一樣,這是任何人都沒法仿造的。在電腦中的數字簽名就是用於驗證傳輸的內容是不是真實伺服器發送的數據,發送的數據有沒有被篡改過。
數字證書
數字證書簡稱CA,它由權威機構給某網站頒發的一種認可憑證,這個憑證是被大家(瀏覽器)所認可的。
3. HTTP/2
HTTP/2,主要是基於Google的SPDY協議,是自HTTP/1.1從1999年發佈16年後的首次更新。Servlet4.0將完全支持HTTP/2。
3.1 HTTP/1.1的問題
-
假設一個網站需要載入幾十個資源(css、js、jpg、等等),等到html文件載入成功後,瀏覽器會一個一個請求這些資源,並等待伺服器按順序一個一個返回。
- 一個請求,一個應答
- http header
3.2 HTTP/2主要特性:
-
request/response多路復用(multiplexing)
-
二進位幀傳輸(binary framing)
-
數據流優先順序(stream prioritization)
-
伺服器推送(server push)
-
頭信息壓縮(header compression)
HTTP/2是站在HTTP/1.1肩膀上的一個改進而已,跟HTTP/1.1相比:
-
相同的request/response模式
-
沒有新的method
-
沒有新的header
-
在應用層沒有引入新的花樣
-
沒有修改URL規範、沒有修改其他底層規範
HTTP/2僅是一個協議而已,它可以建立在TLS之上,也可以不。但是,根據 http://caniuse.com/,網站的統計,瀏覽器幾乎只支持安全的HTTP/2,也就是說如果是網站的話,想要升到HTTP/2就必須支持HTTPS。當然如gRPC這種內部的服務開發,可以不用支持TLS。
3.3 HTTP/2 的協議協商機制
一個網站支不支持HTTP/2,對於瀏覽器來說是不知道的,只能通過兩者的協商來確定是否使用HTTP/2協議,還是HTTP/1.1。我們分2種來講。
a. HTTP(without TLS)
為了更方便地部署新協議,HTTP/1.1 引入了 Upgrade 機制,它使得客戶端和服務端之間可以藉助已有的 HTTP 語法升級到其它協議。
如果大家之前使用過 WebSocket,應該已經對 HTTP Upgrade 機制有所瞭解。下麵是建立 WebSocket 連接的 HTTP 請求
GET ws://example.com/ HTTP/1.1
Connection: Upgrade
Upgrade: websocket
Origin: http://example.com
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: d4egt7snxxxxxx2WcaMQlA==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
這是服務端同意升級的 HTTP 響應:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: gczJQPmQ4Ixxxxxx6pZO8U7UbZs=
在這之後,客戶端和服務端之間就可以使用 WebSocket 協議進行雙向數據通訊,跟 HTTP/1.1 沒關係了。可以看到,WebSocket 連接的建立就是典型的 HTTP Upgrade 機制。顯然,這個機制也可以用做 HTTP/1.1 到 HTTP/2 的協議升級。
b. HTTPS(with TLS)
多了 TLS 之後,雙方必須等到成功建立 TLS 連接之後才能發送應用數據。而要建立 TLS 連接,本來就要進行 CipherSuite 等參數的。引入 HTTP/2 之後,需要做的只是在原本的協商機制中把對 HTTP 協議的協商加進去。Google 在 SPDY 協議中開發了一個名為 NPN(Next Protocol Negotiation,下一代協議協商)的 TLS 擴展。隨著 SPDY 被 HTTP/2 取代,NPN 也被官方修訂為 ALPN(Application Layer Protocol Negotiation,應用層協議協商)。
下圖,是caniuse.com網站統計的支持HTTP/2的瀏覽器版本,以及支持的協商協議。可以看到chrome到41版本才支持,IE根本不支持。
4. SSL/TLS
互聯網加密通信協議的歷史,幾乎與互聯網一樣長。
1994年,NetScape公司設計了SSL協議(Secure Sockets Layer)的1.0版,但是未發佈。
1995年,NetScape公司發佈SSL 2.0版,很快發現有嚴重漏洞。
1996年,SSL 3.0版問世,得到大規模應用。
1999年,互聯網標準化組織ISOC接替NetScape公司,發佈了SSL的升級版TLS 1.0版。
2006年和2008年,TLS進行了兩次升級,分別為TLS 1.1版和TLS 1.2版。最新的變動是2011年TLS 1.2的修訂版。
目前常用的 HTTP 協議是 HTTP1.1,常用的 TLS 協議版本有如下幾個:TLS1.2, TLS1.1, TLS1.0 和 SSL3.0。
-
其中 SSL3.0 由於 POODLE 攻擊已經被證明不安全
-
TLS1.0 也存在部分安全漏洞,比如 RC4 和 BEAST 攻擊
-
TLS1.2 和 TLS1.1 暫時沒有已知的安全漏洞,比較安全,同時有大量擴展提升速度和性能,推薦
那麼如何建立TLS鏈接的呢?大概步驟如下:
(網圖)
客戶端將自己支持的一套加密演算法、HASH演算法發送給服務端
服務端從中選出一組加密演算法與HASH演算法,並將自己的身份信息以證書的形式發回給客戶端。證書裡面包含了服務端的地址(功能變數名稱),加密公鑰,以及證書的頒發機構等信息
客戶端獲得證書之後,開始驗證證書的合法性,如果證書信任,則生成一串隨機數字作為通訊過程中對稱加密的秘鑰。然後取出證書中的公鑰,將這串數字以及HASH的結果進行加密,然後發給服務端
服務端接收客戶端發來的數據之後,通過私鑰進行解密,然後HASH校驗,如果一致,則使用客戶端發來的數字串加密一段握手消息發給客戶端
客戶端解密,並HASH校驗,沒有問題,則握手結束。接下來的傳輸過程將由之前客戶端生成的隨機密碼並利用對稱加密演算法進行加密通信
4.1 實現庫
TLS協議的設計目標是構建一個安全傳輸層(Transport Layer Security ),在基於連接的傳輸層(如tcp)之上提供。
TLS是用來做加密數據傳輸的,因此它的主體當然是一個對稱加密傳輸組件。為了給這個組件生成雙方共用的密鑰,因此就需要先搞一個認證密鑰協商組件,TLS協議自然分為:
做對稱加密傳輸的record協議 ,the record protocol
做認證密鑰協商的handshake協議,the handshake protocol
還有3個很簡單的輔助協議:
changecipher spec 協議,the changecipher spec protocol, 用來通知對端從handshake切換到record協議(有點冗餘,在TLS1.3裡面已經被刪掉了)
alert協議,the alert protocol, 用來通知各種返回碼,
application data協議, The application data protocol,就是把http,smtp等的數據流傳入record層做處理並傳輸。
(網圖)
如上看到,要實現TLS協議是很複雜的,目前他的實現也已經有很多了,當然最著名的當屬 openssl 。在wikipedia里已經列的很詳細了。gRPC里由於是基於netty的,netty里的TLS實現庫主要是BoringSSL、OpenSSL
大家可以參考
https://en.wikipedia.org/wiki/Comparison_of_TLS_implementations
5. CA(證書)
它的作用就是提供證書(即伺服器證書,由功能變數名稱、公司信息、序列號和簽名信息組成)加強服務端和客戶端之間信息交互的安全性,以及證書運維相關服務。任何個體/組織都可以扮演 CA 的角色,只不過難以得到客戶端的信任,能夠受瀏覽器預設信任的 CA 大廠商有很多,其中 TOP5 是 Symantec、Comodo、Godaddy、GolbalSign 和 Digicert。
證書也挺貴的,對於個人來說,還是算了。就是我們偉大的12306用的也是自建證書。
5.1 證書標準
X.509 - 這是一種證書標準,主要定義了證書中應該包含哪些內容.其詳情可以參考RFC5280,SSL使用的就是這種證書標準.
5.2 編碼格式
同樣的X.509證書,可能有不同的編碼格式
PEM - Privacy Enhanced Mail,打開看文本格式,以"-----BEGIN..."開頭, "-----END..."結尾,內容是BASE64編碼.
查看PEM格式證書的信息:openssl x509 -in certificate.pem -text -noout
Apache和*NIX伺服器偏向於使用這種編碼格式.DER - Distinguished Encoding Rules,打開看是二進位格式,不可讀.
查看DER格式證書的信息:openssl x509 -in certificate.der -inform der -text -noout
Java和Windows伺服器偏向於使用這種編碼格式.
5.3 相關的文件擴展名
雖然我們已經知道有PEM和DER這兩種編碼格式,但文件擴展名並不一定就叫"PEM"或者"DER",常見的擴展名除了PEM和DER還有以下這些,它們除了編碼格式可能不同之外,內容也有差別,但大多數都能相互轉換編碼格式.
CRT - CRT應該是certificate的三個字母,其實還是證書的意思,常見於*NIX系統,有可能是PEM編碼,也有可能是DER編碼,大多數應該是PEM編碼,相信你已經知道怎麼辨別.
CER - 還是certificate,還是證書,常見於Windows系統,同樣的,可能是PEM編碼,也可能是DER編碼,大多數應該是DER編碼.
KEY - 通常用來存放一個公鑰或者私鑰,並非X.509證書,編碼同樣的,可能是PEM,也可能是DER.
查看KEY的辦法:openssl rsa -in mykey.key -text -noout
如果是DER格式的話,同理應該這樣了:openssl rsa -in mykey.key -text -noout -inform derCSR - Certificate Signing Request,即證書簽名請求,這個並不是證書,而是向權威證書頒發機構獲得簽名證書的申請,其核心內容是一個公鑰(當然還附帶了一些別的信息),在生成這個申請的時候,同時也會生成一個私鑰,私鑰要自己保管好。
查看的辦法:openssl req -noout -text -in my.csrPFX/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 -in certificate.crt -inkey privateKey.key -out certificate.pfx -certfile CACert.crt其中CACert.crt是CA(權威證書頒發機構)的根證書,有的話也通過-certfile參數一起帶進去.這麼看來,PFX其實是個證書密鑰庫.JKS - 即Java Key Storage,這是Java的專利,跟OpenSSL關係不大,利用Java的一個叫"keytool"的工具,可以將PFX轉為JKS
5.4 證書編碼的轉換
PEM轉為DER openssl x509 -in cert.crt -outform der -out cert.der
DER轉為PEM openssl x509 -in cert.crt -inform der -outform pem -out cert.pem
6. 自建證書
OpenSSL 是一個免費開源的庫,它提供了構建數字證書的命令行工具。一般都是三級證書,為了簡單我就只做2級了,大家可以自己簽發三級。
6.1 創建 Root CA
a) 生成根證書私鑰
$ openssl genrsa -aes256 -out cakey.pem 2048 (生成私鑰)
$ openssl pkcs8 -topk8 -in cakey.pem -out ca.key -nocrypt (grpc格式)
b)生成根證書簽發申請文件
$openssl req -new -key ca.key -out ca.csr -subj "/C=CN/ST=myprovince/L=mycity/O=myorganization/OU=mygroup/CN=wwc" (/CN代表的就是功能變數名稱)
c)自簽髮根證書(cer文件)
$ openssl x509 -req -days 365 -sha1 -extensions v3_ca -signkey ca.key -in ca.csr -out ca.cer
6.2 簽發自建證書
a) 生成證書私鑰
$ openssl genrsa -aes256 -out server.pem 2048 (生成私鑰)
$ openssl pkcs8 -topk8 -in server.pem -out server.key -nocrypt
b)生成證書簽發申請文件
$openssl req -new -key server.key -out server.csr -subj "/C=CN/ST=myprovince/L=mycity/O=myorganization/OU=mygroup/CN=wancai"
c)使用根證書簽發服務端證書
$ openssl x509 -req -days 365 -sha1 -extensions v3_req -CA ca.cer -CAkey ca.key -CAserial ca.srl -CAcreateserial -in server.csr -out server.cer
7. gRPC使用自建證書
將server.cer(證書)和server.key(私鑰)拷貝到工作目錄
gRPC的通信組件是netty、okhttp,netty帶了ssl實現,有動態和靜態兩種方式來提供TLS的實現庫,為了開發方便,我這裡使用了boringssl實現庫。okhttp也實現了TLS,但okhttp使用在移動端,故在此不表。
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-all</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
<version>1.1.33.Fork23</version>
</dependency>
gRPC Server端
使用TLS,添加證書和私鑰
/* The port on which the server should run */
int port = 8443;
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl())
.useTransportSecurity(loadCert("server.cer"),loadCert("server.key"))
.build()
.start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
HelloWorldServer.this.stop();
System.err.println("*** server shut down");
}
});
gRPC Client端
添加信任的證書,同時註意剛纔我們建立證書的時候,功能變數名稱是wancai,所以在這裡需要添加功能變數名稱,否則鏈接失敗。
SslContext sslContext = null;
try {
sslContext = GrpcSslContexts.forClient().trustManager(
loadCert("server.cer")).build();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
InetAddress address;
try {
address = InetAddress.getByName(host);
address = InetAddress.getByAddress("wancai", address.getAddress());
} catch (UnknownHostException ex) {
throw new RuntimeException(ex);
}
channel = NettyChannelBuilder.forAddress(new InetSocketAddress(address, port))
.flowControlWindow(65 * 1024)
.negotiationType(NegotiationType.TLS)
.sslContext(sslContext)
.build();
blockingStub = GreeterGrpc.newBlockingStub(channel);
最後,我們通過 wireshark,抓包看看使用TLS加密和不加密通信的信息。
當沒有加密時,通信如下
參考資料
-
https://blog.helong.info/blog/2015/09/07/tls-protocol-analysis-and-crypto-protocol-design/
-
http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html
-
http://www.barretlee.com/blog/2016/04/24/detail-about-ca-and-certs/
-
http://www.cnblogs.com/guogangj/p/4118605.html
-
https://my.oschina.net/itblog/blog/651434
-
http://blog.csdn.net/clementad/article/details/50620067
-
https://imququ.com/post/protocol-negotiation-in-http2.html