引子css
前幾天看到微信後臺團隊分享了TLS相關文章,正好gRPC裏TLS數據加密是很重要的一塊,因而整理出了這篇文章。html
在gRPC裏,若是僅僅是用來作後端微服務,能夠考慮不加密。本文太長,先給個大綱。node
1. HTTPS,HTTP/2介紹web
2. TLS加密原理、實現庫算法
3. HTTP/2協議協商機制chrome
4. 自建數字證書(CA)後端
5. gRPC使用TLS瀏覽器
目前絕大多數網站和APP都是創建在HTTP之上的,全部的數據都是明文傳輸,沒有任何安全可言。安全
網圖 服務器
HTTPS(Hypertext Transfer Protocol over Secure Socket Layer)是以安全爲目標的HTTP通道,即HTTP下加入SSL層,HTTPS的安全基礎是SSL。用來保護用戶隱私,防止流量劫持。
(網圖,懶得畫了)
認證用戶和服務器,確保數據發送到正確的客戶機和服務器;(驗證證書)
加密數據以防止數據中途被竊取;(加密)
維護數據的完整性,確保數據在傳輸過程當中不被改變。(摘要算法)
HTTPS之因此安全,就是HTTP創建在SSL/TLS之上的。
(網圖)
SSL/TLS協議的基本思路是採用公鑰加密法,也就是說,客戶端先向服務器端索要公鑰,而後用公鑰加密信息,服務器收到密文後,用本身的私鑰解密。
(1)如何保證公鑰不被篡改?
將公鑰放在數字證書中。只要證書是可信的,公鑰就是可信的。
(2)公鑰加密計算量太大,如何減小耗用的時間?
每一次對話,客戶端和服務器端都生成一個」對話密鑰」,用它來加密信息。因爲」對話密鑰」是對稱加密,因此運算速度很是快,而服務器公鑰只用於加密」對話密鑰」自己,這樣就減小了加密運算的消耗時間。
也就是說,對於HTTPS,因爲成本問題
握手階段(handshake)用的非對稱加密
數據通訊用的是對稱加密
咱們大體的講一下加密相關術語。因爲密碼學太過複雜,咱們不去深究,也千萬別問我爲何公鑰加密後,可以用私鑰解密。
(主要是數學太難,門檻過高,我也不懂,逃。。。)
又稱爲共享密鑰加密,對稱密鑰在加密和解密的過程當中使用的密鑰是相同的,常見的對稱加密算法有DES、3DES、AES、RC五、RC6。對稱密鑰的優勢是計算速度快,可是密鑰須要在通信的兩端共享,讓彼此知道密鑰是什麼對方纔能正確解密,若是全部客戶端都共享同一個密鑰,那麼這個密鑰就像萬能鑰匙同樣,能夠憑藉一個密鑰破解全部人的密文了。
服務端會生成一對密鑰,一個私鑰保存在服務端,僅本身知道,另外一個是公鑰,公鑰能夠自由發佈供任何人使用。客戶端的明文經過公鑰加密後的密文須要用私鑰解密。非對稱密鑰在加密和解密的過程的使用的密鑰是不一樣的密鑰,加密和解密是不對稱的,因此稱之爲非對稱加密。與對稱密鑰加密相比,非對稱加密無需在客戶端和服務端之間共享密鑰,只要私鑰不發給任何用戶,即便公鑰在網上被截獲,也沒法被解密,僅有被竊取的公鑰是沒有任何用處的。常見的非對稱加密有RSA。
數字簽名就如同平常生活中的簽名同樣,這是任何人都無法仿造的。在計算機中的數字簽名就是用於驗證傳輸的內容是否是真實服務器發送的數據,發送的數據有沒有被篡改過。
數字證書簡稱CA,它由權威機構給某網站頒發的一種承認憑證,這個憑證是被你們(瀏覽器)所承認的。
HTTP/2,主要是基於Google的SPDY協議,是自HTTP/1.1從1999年發佈16年後的首次更新。Servlet4.0將徹底支持HTTP/2。
假設一個網站須要加載幾十個資源(css、js、jpg、等等),等到html文件加載成功後,瀏覽器會一個一個請求這些資源,並等待服務器按順序一個一個返回。
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。
一個網站支不支持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根本不支持。
互聯網加密通訊協議的歷史,幾乎與互聯網同樣長。
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校驗,沒有問題,則握手結束。接下來的傳輸過程將由以前客戶端生成的隨機密碼並利用對稱加密算法進行加密通訊
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
它的做用就是提供證書(即服務器證書,由域名、公司信息、序列號和簽名信息組成)增強服務端和客戶端之間信息交互的安全性,以及證書運維相關服務。任何個體/組織均可以扮演 CA 的角色,只不過難以獲得客戶端的信任,可以受瀏覽器默認信任的 CA 大廠商有不少,其中 TOP5 是 Symantec、Comodo、Godaddy、GolbalSign 和 Digicert。
證書也挺貴的,對於我的來講,仍是算了。就是咱們偉大的12306用的也是自建證書。
X.509 - 這是一種證書標準,主要定義了證書中應該包含哪些內容.其詳情能夠參考RFC5280,SSL使用的就是這種證書標準.
一樣的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服務器偏向於使用這種編碼格式.
雖然咱們已經知道有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
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
OpenSSL 是一個免費開源的庫,它提供了構建數字證書的命令行工具。通常都是三級證書,爲了簡單我就只作2級了,你們能夠本身簽發三級。
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
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
將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