Netty基于SSL实现信息传输过程中双向加密验证
SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密。
在实际通信过程中,如果不使用SSL那么信息就是明文传输,从而给非法分子一些可乘之机;
-
窃听风险[eavesdropping]:第三方可以获知通信内容。
-
篡改风险[tampering]:第三方可以修改通信内容。
-
冒充风险[pretending]:第三方可以冒充他人身份参与通信。
SSL/TLS协议就是为了解决这三大风险而设计的;
-
保密:在握手协议中定义了会话密钥后,所有的消息都被加密。
-
鉴别:可选的客户端认证,和强制的服务器端认证。
-
完整性:传送的消息包括消息完整性检查(使用MAC)。
那么本章节我们通过在netty的channHandler中添加SSL安全模块{sslContext.newHandler(channel.alloc())},来实现加密传输的效果。
测试注释掉客户端SSL安全模块:
1io.netty.handler.ssl.NotSslRecordException: not an SSL/TLS record: cea2d0c5b9abd6dabac5a3ba627567737461636bb3e6b6b4d5bb207c20cda8d6aab7fecef1b6cbc1b4bdd3bda8c1a2b3c9b9a6204d6f6e205365702032332031333a35303a3535204353542032303139203132372e302e302e310d0a
测试篡改服务端时间:
1javax.net.ssl.SSLHandshakeException: General SSLEngine problem
开发环境
1、jdk1.8【jdk1.7以下只能部分支持netty】
2、Netty4.1.36.Final【netty3.x 4.x 5每次的变化较大,接口类名也随着变化】
3、OpenSSL-Win64 可以按照自己的需要进行下载;
http://slproweb.com/products/Win32OpenSSL.html
生成证书
1、安装OpenSSL
安装完成后D:\Program Files\OpenSSL-Win64\bin目录下,cnf文件复制到bin目录里,否则在操作工程中如果未指定路径,会报错;
https://stackoverflow.com/questions/22906927/openssl-windows-error-in-req/27918971
2.生成ca.crt证书
1)准备ca.conf配置文件
[ req ] default_bits = 4096 distinguished_name = req_distinguished_name [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = CN stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = guangzhou localityName = Locality Name (eg, city) localityName_default = shenzhen organizationName = Organization Name (eg, company) organizationName_default = bsoft commonName = Common Name (e.g. server FQDN or YOUR name) commonName_max = 64 commonName_default = CA Test
2)生成ca.key
openssl genrsa -out ca.key 4096
3)生成ca证书签发请求ca.csr
openssl req -new -sha256 -out ca.csr -key ca.key -config ca.conf
4)生成ca.crt证书
openssl x509 -req -days 3650 -in ca.csr -signkey ca.key -out ca.crt
3、生成服务端和客户端私钥 | 命令中需要输入密码测试可以都输入123456
openssl genrsa -des3 -out server.key 1024
openssl genrsa -des3 -out client.key 1024
4、根据key生成csr文件 | -config openssl.cnf 默认在cnf文件夹,如果未复制出来,需要指定路径“D://..cnf//openssl.cnf”
openssl req -new -key server.key -out server.csr -config openssl.cnf openssl req -new -key client.key -out client.csr -config openssl.cnf
5、根据ca证书server.csr、client.csr生成x509证书
openssl x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
openssl x509 -req -days 3650 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt
6、将key文件进行PKCS#8编码
openssl pkcs8 -topk8 -in server.key -out pkcs8_server.key -nocrypt
openssl pkcs8 -topk8 -in client.key -out pkcs8_client.key -nocrypt
7、最终将bin文件夹下,如下文件复制出来;
server端:ca.crt、server.crt、pkcs8_server.key
client端:ca.crt、client.crt、pkcs8_client.key
结构:

案例;
1,NettyServer.class
package com.forezp.util.netty.test17ssl.server; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslContext; import javax.net.ssl.SSLException; import java.io.File; public class NettyServer { public static void main(String[] args) throws SSLException{ new NettyServer().bind(6000); } private void bind(int port) throws SSLException { //引入SSL安全验证 File certChainFile=new File("D:\\study\\SpringBootLearning-master" + "\\springboot-mybatis\\src\\main\\java\\com\\forezp\\util" + "\\netty\\test17ssl\\ssl\\server\\server.crt"); File keyFile=new File("D:\\study\\SpringBootLearning-master" + "\\springboot-mybatis\\src\\main\\java\\com\\forezp\\util" + "\\netty\\test17ssl\\ssl\\server\\pkcs8_server.key"); File rootFile=new File("D:\\study\\SpringBootLearning-master" + "\\springboot-mybatis\\src\\main\\java\\com\\forezp\\util" + "\\netty\\test17ssl\\ssl\\server\\ca.crt"); SslContext sslContext= SslContextBuilder .forServer(certChainFile,keyFile) .trustManager(rootFile) .clientAuth(ClientAuth.REQUIRE) .build(); EventLoopGroup parentGroup=new NioEventLoopGroup(1); EventLoopGroup childGroup=new NioEventLoopGroup(); try { ServerBootstrap bootstrap=new ServerBootstrap(); bootstrap.group(parentGroup,childGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG,128) .childHandler(new MyServerChannelInitializer(sslContext)); ChannelFuture future=bootstrap.bind(port).sync(); System.out.println("netty server start done..."); future.channel().closeFuture().sync(); }catch (Exception e){ System.out.println("netty server start error..."); }finally { parentGroup.shutdownGracefully(); childGroup.shutdownGracefully(); } } }
2,MyServerChannelInitializer.class
package com.forezp.util.netty.test17ssl.server; import io.netty.channel.ChannelInitializer; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.LineBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.ssl.SslContext; import java.nio.charset.Charset; public class MyServerChannelInitializer extends ChannelInitializer<SocketChannel> { private SslContext sslContext; public MyServerChannelInitializer(SslContext sslContext) { this.sslContext = sslContext; } @Override protected void initChannel(SocketChannel ch) throws Exception { // 添加SSL安装验证 ch.pipeline().addLast(sslContext.newHandler(ch.alloc())); ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); ch.pipeline().addLast(new StringDecoder(Charset.forName("UTF8"))); ch.pipeline().addLast(new StringEncoder(Charset.forName("UTF8"))); ch.pipeline().addLast(new MyServerHandler()); } }
3,MyServerHandler.class
package com.forezp.util.netty.test17ssl.server; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.socket.SocketChannel; import java.text.SimpleDateFormat; import java.util.Date; public class MyServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { SocketChannel channel=(SocketChannel) ctx.channel(); System.out.println("链接报告开始"); System.out.println("链接报告信息:有一客户端链接到本服务端"); System.out.println("链接报告IP:" + channel.localAddress().getHostString()); System.out.println("链接报告Port:" + channel.localAddress().getPort()); System.out.println("链接报告完毕"); //通知客户端链接建立成功 String str = "通知客户端链接建立成功" + " " + new Date() + " " + channel.localAddress().getHostString() + "\r\n"; ctx.writeAndFlush(str); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("客户端断开链接" + ctx.channel().localAddress().toString()); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //接收msg消息 System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") .format(new Date()) + " 接收到消息:" + msg); //通知客户端链消息发送成功 ctx.writeAndFlush("[SSL]服务端发送,客户端我在。\r\n"); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("异常信息:" + cause.getMessage()); ctx.close(); } }
4,NettyClient.class
package com.forezp.util.netty.test17ssl.client; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import javax.net.ssl.SSLException; import java.io.File; public class NettyClient { public static void main(String[] args) throws SSLException{ new NettyClient().connect("127.0.0.1",6000); } private void connect(String address, int port) throws SSLException { //引入SSL安全验证 File certChainFile=new File("D:\\study\\SpringBootLearning-master" + "\\springboot-mybatis\\src\\main\\java\\com\\forezp\\util" + "\\netty\\test17ssl\\ssl\\client\\client.crt"); File keyFile=new File("D:\\study\\SpringBootLearning-master" + "\\springboot-mybatis\\src\\main\\java\\com\\forezp\\util" + "\\netty\\test17ssl\\ssl\\client\\pkcs8_client.key"); File rootFile=new File("D:\\study\\SpringBootLearning-master" + "\\springboot-mybatis\\src\\main\\java\\com\\forezp\\util" + "\\netty\\test17ssl\\ssl\\client\\ca.crt"); SslContext sslContext= SslContextBuilder.forClient() .keyManager(certChainFile,keyFile) .trustManager(rootFile) .build(); EventLoopGroup workGroup=new NioEventLoopGroup(); try { Bootstrap bootstrap=new Bootstrap(); bootstrap.group(workGroup) .channel(NioSocketChannel.class) .option(ChannelOption.AUTO_READ,true) .handler(new MyClientChannelInitializer(sslContext)); ChannelFuture future=bootstrap.connect(address,port).sync(); System.out.println("netty client start done ...."); future.channel().closeFuture().sync(); }catch (Exception e){ System.out.println("netty client start error ...."); }finally { workGroup.shutdownGracefully(); } } }
5,MyClientChannelInitializer.class
package com.forezp.util.netty.test17ssl.client; import io.netty.channel.ChannelInitializer; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.LineBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.ssl.SslContext; import java.nio.charset.Charset; public class MyClientChannelInitializer extends ChannelInitializer<SocketChannel> { private SslContext sslContext; public MyClientChannelInitializer(SslContext sslContext) { this.sslContext = sslContext; } @Override protected void initChannel(SocketChannel ch) throws Exception { // 添加SSL安装验证 ch.pipeline().addLast(sslContext.newHandler(ch.alloc())); ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); ch.pipeline().addLast(new StringDecoder(Charset.forName("UTF8"))); ch.pipeline().addLast(new StringEncoder(Charset.forName("UTF8"))); ch.pipeline().addLast(new MyClientHandler()); } }
6,MyClientHandler.class
package com.forezp.util.netty.test17ssl.client; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.socket.SocketChannel; import java.text.SimpleDateFormat; import java.util.Date; public class MyClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { SocketChannel channel=(SocketChannel) ctx.channel(); System.out.println("链接报告开始"); System.out.println("链接报告信息:本客户端链接到服务端。channelId:" + channel.id()); System.out.println("链接报告IP:" + channel.localAddress().getHostString()); System.out.println("链接报告Port:" + channel.localAddress().getPort()); System.out.println("链接报告完毕"); //通知客户端链接建立成功 String str = "通知服务端链接建立成功" + " " + new Date() + " " + channel.localAddress().getHostString() + "\r\n"; ctx.writeAndFlush(str); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("断开链接" + ctx.channel().localAddress().toString()); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //接收msg消息 System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 接收到消息:" + msg); //通知客户端链消息发送成功 ctx.writeAndFlush("[SSL]客户端发送,服务端你在吗?\\r\\n"); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); System.out.println("异常信息:"+cause.getMessage()); } }
参考文档:
https://blog.csdn.net/LetsStudy/article/details/123506619
https://mp.weixin.qq.com/s?__biz=MzIxMDAwMDAxMw==&mid=2650724977&idx=1&sn=cab1a02be74a62d8184c18abc5eba6b5&chksm=8f613b93b816b2850b17a05b74d7debec11eb3dfaa8d9763f1af63825b3e24685bcddd10a38e&token=1898926011&lang=zh_CN#rd
浙公网安备 33010602011771号