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 

 

posted on 2022-11-25 16:52  让代码飞  阅读(2324)  评论(0)    收藏  举报

导航

一款免费在线思维导图工具推荐:https://www.processon.com/i/593e9a29e4b0898669edaf7f?full_name=python