1、SSL/TLS简介
协议是Web浏览器与Web服务器之间安全交换信息的协议,提供两个基本的安全服务:鉴别与保密。
1.1、作用
不使用SSL/TLS的HTTP通信,就是不加密的通信。所有信息明文传播,带来了三大风险。
- 窃听风险(eavesdropping):第三方可以获知通信内容。
- 篡改风险(tampering):第三方可以修改通信内容。
- 冒充风险(pretending):第三方可以冒充他人身份参与通信。
SSL/TLS协议是为了解决这三大风险而设计的,希望达到:
- 保密:在握手协议中定义了会话密钥后,所有的消息都被加密。
- 鉴别:可选的客户端认证,和强制的服务器端认证。
- 完整性:传送的消息包括消息完整性检查(使用MAC)。
1.2、工作原理
1.2.1、密码学的相关概念
- 密码学(cryptography):目的是通过将信息编码使其不可读,从而达到安全性。
- 明文(plain text):发送人、接受人和任何访问消息的人都能理解的消息。
- 密文(cipher text):明文消息经过某种编码后,得到密文消息。
- 加密(encryption):将明文消息变成密文消息。
- 解密(decryption):将密文消息变成明文消息。
- 算法:取一个输入文本,产生一个输出文本。
- 加密算法:发送方进行加密的算法。
- 解密算法:接收方进行解密的算法。
- 密钥(key):只有发送方和接收方理解的消息
- 对称密钥加密(Symmetric Key Cryptography):就是需要双方使用一样的 key 来加密解密消息算法,常用密钥算法有 Data Encryption Standard(DES)、triple-strength DES(3DES)、Rivest Cipher 2 (RC2)和 Rivest Cipher 4(RC4)。因为对称算法效率相对较高,因此 SSL 会话中的敏感数据都用通过密钥算法加密。
- 非对称密钥加密(Asymmetric Key Cryptography):就是 key 的组成是公钥私钥对 (key-pair),公钥传递给对方私钥自己保留。公钥私钥算法是互逆的,一个用来加密,另一个可以解密。常用的算法有 Rivest Shamir Adleman(RSA)、Diffie-Hellman(DH)。非对称算法计算量大比较慢,因此仅适用于少量数据加密,如对密钥加密,而不适合大量数据的通讯加密。
- 公钥证书(public key certificate):公钥证书类似数字护照,由受信机构颁发。受信组织的公钥证书就是 certificate authority(CA)。多证书可以连接成证书串,第一个是发送人,下一个是给其颁发证书实体,往上到根证书是世界范围受信组织,包括 VeriSign, Entrust, 和 GTE CyberTrust。公钥证书让非对称算法的公钥传递更安全,可以避免身份伪造,比如 C 创建了公钥私钥,对并冒充 A 将公钥传递给 B,这样 C 与 B 之间进行的通讯会让 B 误认是 A 与 B 之间通讯。
- 加密哈希功能(Cryptographic Hash Functions):加密哈希功能与 checksum 功能相似。不同之处在于,checksum 用来侦测意外的数据变化而前者用来侦测故意的数据篡改。数据被哈希后产生一小串比特字符串,微小的数据改变将导致哈希串的变化。发送加密数据时,SSL 会使用加密哈希功能来确保数据一致性,用来阻止第三方破坏通讯数据完整性。SSL 常用的哈希算法有 Message Digest 5(MD5)和 Secure Hash Algorithm(SHA)。
- 消息认证码(Message Authentication Code):消息认证码与加密哈希功能相似,除了它需要基于密钥。密钥信息与加密哈希功能产生的数据结合就是哈希消息认证码(HMAC)。如果 A 要确保给 B 发的消息不被 C 篡改,他要按如下步骤做 --A 首先要计算出一个 HMAC 值,将其添加到原始消息后面。用 A 与 B 之间通讯的密钥加密消息体,然后发送给 B。B 收到消息后用密钥解密,然后重新计算出一个 HMAC,来判断消息是否在传输中被篡改。SSL 用 HMAC 来保证数据传输的安全。
- 数字签名(Digital Signature):一个消息的加密哈希被创建后,哈希值用发送者的私钥加密,加密的结果就是叫做数字签名。
1.2.2、认证类型:
- 单向认证:就是用户到服务器之间只存在单方面的认证,即客户端会认证服务器端身份,而服务器端不会去对客户端身份进行验证。首先,客户端发起握手请求,服务器收到握手请求后,会选择适合双方的协议版本和加密方式。然后,再将协商的结果和服务器端的公钥一起发送给客户端。客户端利用服务器端的公钥,对要发送的数据进行加密,并发送给服务器端。服务器端收到后,会用本地私钥对收到的客户端加密数据进行解密。然后,通讯双方都会使用这些数据来产生双方之间通讯的加密密钥。接下来,双方就可以开始安全通讯过程了。
- 双向认证:就是双方都会互相认证,也就是两者之间将会交换证书。基本的过程和单向认证完全一样,只是在协商阶段多了几个步骤。在服务器端将协商的结果和服务器端的公钥一起发送给客户端后,会请求客户端的证书,客户端则会将证书发送给服务器端。然后,在客户端给服务器端发送加密数据后,客户端会将私钥生成的数字签名发送给服务器端。而服务器端则会用客户端证书中的公钥来验证数字签名的合法性。建立握手之后过程则和单向通讯完全保持一致。
1.2.3、握手协议(Handshake protocol)
握手协议是客户机和服务器用SSL连接通信时使用的第一个子协议,握手协议包括客户机与服务器之间的一系列消息。SSL中最复杂的协议就是握手协议。该协议允许服务器和客户机相互验证,协商加密和MAC算法以及保密密钥,用来保护在SSL记录中发送的数据。握手协议是在应用程序的数据传输之前使用的。
每个握手协议包含以下3个字段
(1)Type:表示10种消息类型之一
(2)Length:表示消息长度字节数
(3)Content:与消息相关的参数
握手协议的4个阶段:
握手流程:
步骤 1:ClientHello – 客户端发送所支持的 SSL/TLS 最高协议版本号和所支持的加密算法集合及压缩方法集合等信息给服务器端。
步骤 2.:ServerHello – 服务器端收到客户端信息后,选定双方都能够支持的 SSL/TLS 协议版本和加密方法及压缩方法,返回给客户端。
步骤 3.:SendCertificate(可选) – 服务器端发送服务端证书给客户端。
步骤 4: RequestCertificate(可选) – 如果选择双向验证,服务器端向客户端请求客户端证书。
步骤 5.:ServerHelloDone – 服务器端通知客户端初始协商结束。
步骤 6.:ResponseCertificate(可选) – 如果选择双向验证,客户端向服务器端发送客户端证书。
步骤 7.:ClientKeyExchange – 客户端使用服务器端的公钥,对客户端公钥和密钥种子进行加密,再发送给服务器端。
步骤 8:CertificateVerify(可选) – 如果选择双向验证,客户端用本地私钥生成数字签名,并发送给服务器端,让其通过收到的客户端公钥进行身份验证。
步骤 9: CreateSecretKey – 通讯双方基于密钥种子等信息生成通讯密钥。
步骤 10: ChangeCipherSpec – 客户端通知服务器端已将通讯方式切换到加密模式。
步骤 11:Finished – 客户端做好加密通讯的准备。
步骤 12:ChangeCipherSpec – 服务器端通知客户端已将通讯方式切换到加密模式。
步骤 13:Finished – 服务器做好加密通讯的准备。
步骤 14:Encrypted/DecryptedData – 双方使用客户端密钥,通过对称加密算法对通讯内容进行加密。
步骤 15:ClosedConnection – 通讯结束后,任何一方发出断开 SSL 连接的消息。
1.2.4密钥生成的过程
这样握手协议完成,下面看下什么是预备主密钥,主密钥是怎么生成的。为了保证信息的完整性和机密性,SSL需要有六个加密秘密:四个密钥和两个IV。为了信息的可信性,客户端需要一个密钥(HMAC),为了加密要有一个密钥,为了分组加密要一个IV,服务也是如此。SSL需要的密钥是单向的,不同于那些在其他方向的密钥。如果在一个方向上有攻击,这种攻击在其他方向是没影响的。生成过程如下:
1.2.5、记录协议(Record protocol)
记录协议在客户机和服务器握手成功后使用,即客户机和服务器鉴别对方和确定安全信息交换使用的算法后,进入SSL记录协议,记录协议向SSL连接提供两个服务:
- 保密性:使用握手协议定义的秘密密钥实现
- 完整性:握手协议定义了MAC,用于保证消息完整性
记录协议的过程:
1.2.6、报警协议(Alert protocol)
客户机和服务器发现错误时,向对方发送一个警报消息。如果是致命错误,则算法立即关闭SSL连接,双方还会先删除相关的会话号,秘密和密钥。每个警报消息共2个字节,第1个字节表示错误类型,如果是警报,则值为1,如果是致命错误,则值为2;第2个字节制定实际错误类型。
参考:
https://www.jianshu.com/p/710f70a99cbc
https://www.cnblogs.com/zhuqil/archive/2012/10/06/ssl_detail.html
http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html
https://segmentfault.com/a/1190000002554673
2、netty支持SSL/TLS
我们也可以得到一个结论:HTTPS = HTTP + 加密 + 身份认证 + 报文正确性保障。其实也就是在HTTP层和TCP层之间新增一个步骤,通过证书交换通信秘钥并验证客户端服务端身份的合法性(SSL)。所以为了实现HTTPS,必须支持SSL。
为了支持SSL,Java提供了 javax.net.ssl 包,它的SSLContext 和SSLEngine类使得解密和加密相当简单和高效。Netty提供了OPenSSLEngine实现用于支持SSL。OPenSSLEngine工具包比JDK提供的JdkSSLEngine具有更好的性能。
使用JdksslEngine性能较差,OPenSSLEngine是其性能的10倍。netty默认会尝试加载OpenSSLEngine,如果失败再去加载JdkSSLEngine。
netty-tcnative-boringssl- 打包了openssl依赖,不用关心目标机器是否安装了openssl以及是啥版本; 如果不使用boringssl的话,就需要在目标机器上安装合适的openssl版本。
Netty 通过一个名为 SslHandler 的 ChannelHandler 实现利用了这个 API, 其中 SslHandler 在内部使用 SSLEngine 来完成实际的工作。下图描述的是 SslHandler 的数据流。
- 加密的入站数据被 SslHandler 拦截,并被解密
- 前面加密的数据被 SslHandler 解密
- 平常数据传过 SslHandler
- SslHandler 加密数据并它传递出站
Netty中,可以注册多个handler。ChannelInboundHandler按照注册的先后顺序执行;ChannelOutboundHandler按照注册的先后顺序逆序执行,如下图所示,按照注册的先后顺序对Handler进行排序,request进入Netty后的执行顺序为:
public class SslChannelInitializer extends ChannelInitializer<Channel> { private final SslContext context;
private final boolean startTls; //使用构造函数来传递 SSLContext 用于使用(startTls 是否启用) public SslChannelInitializer(SslContext context, boolean client, boolean startTls) { this.context = context; this.startTls = startTls; } @Override protected void initChannel(Channel ch) throws Exception { ByteBufAllocator byteBufAllocator = ch.alloc(); //对于每个 SslHandler 实例,都使用 Channel 的 ByteBufAllocator 从 SslContext 获取一个新的 SSLEngine SSLEngine sslEngine = context.newEngine(byteBufAllocator); //服务器端模式,客户端模式设置为true sslEngine.setUseClientMode(false); //不需要验证客户端,客户端不设置该项 sslEngine.setNeedClientAuth(false); //要将 SslHandler 设置为第一个 ChannelHandler。这确保了只有在所有其他的 ChannelHandler 将他们的逻辑应用到数据之后,才会进行加密。 //startTls 如果为true,第一个写入的消息将不会被加密(客户端应该设置为true) ch.pipeline().addFirst("ssl",new SslHandler(sslEngine, startTls)); } }
在大多数情况下,SslHandler 将成为 ChannelPipeline 中的第一个 ChannelHandler 。这将确保所有其他 ChannelHandler 应用他们的逻辑到数据后加密后才发生,从而确保他们的变化是安全的。
SslHandler 具有一些有用的方法:
//设置和获取超时时间,超时之后,握手ChannelFuture 将会被通知失败 setHandshakeTimeout (long,TimeUnit) setHandshakeTimeoutMillis (long) getHandshakeTimeoutMillis() //设置和获取超时时间,超时之后,将会触发一个关闭通知并关闭连接。这也将会导致通知该ChannelFuture 失败 setCloseNotifyTimeout (long,TimeUnit) setCloseNotifyTimeoutMillis (long) getCloseNotifyTimeoutMillis() // 返回一个在握手完成后将会得到通知的ChannelFuture。如果握手先前已经执行过了,则返回一个包含了先前的握手结果的ChannelFuture handshakeFuture() //发送close_notify 以请求关闭并销毁底层的SslEngine close() close(ChannelPromise) close(ChannelHandlerContext,ChannelPromise)
参考:
https://www.kancloud.cn/kancloud/essential-netty-in-action/52667
https://www.ahfesco.com.cn/affairs/Article.asp?id=2760
https://www.cnblogs.com/jmcui/p/9550119.html