引子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