zoukankan      html  css  js  c++  java
  • MINA、Netty、Twisted一起学(十一):SSL/TLS

    什么是SSL/TLS

    不使用SSL/TLS的网络通信,一般都是明文传输,网络传输内容在传输过程中很容易被窃听甚至篡改,非常不安全。SSL/TLS协议就是为了解决这些安全问题而设计的。SSL/TLS协议位于TCP/IP协议之上,各个应用层协议之下,使网络传输的内容通过加密算法加密,并且只有服务器和客户端可以加密解密,中间人即使抓到数据包也无法解密获取传输的内容,从而避免安全问题。例如广泛使用的HTTPS协议即是在TCP协议和HTTP协议之间加了一层SSL/TLS协议。

    相关术语

    在学习SSL/TLS协议之前,首先要了解一些相关概念: 
    - 对称加密:加密和解密都采用同一个密钥,常用的算法有DES、3DES、AES,相对于非对称加密算法更简单速度更快。 
    - 非对称加密:和对称加密算法不同,非对称加密算法会有两个密钥:公钥(可以公开的)和私钥(私有的),例如客户端如果使用公钥加密,那么即时其他人有公钥也无法解密,只能通过服务器私有的私钥解密。RSA算法即是典型的非对称加密算法。 
    - 数字证书:数字证书是一个包含公钥并且通过权威机构发行的一串数据,数字证书很多需要付费购买,也有免费的,另外也可以自己生成数字证书,本文中将会采用自签名的方式生成数字证书。

    SSL/TLS流程

    使用SSL/TLS协议的服务器和客户端开始通信之前,会先进行一个握手阶段:

    1. 客户端发出请求:这一步客户端会生成一个随机数传给服务器;
    2. 服务器回应:这一步服务器会返回给客户端一个服务器数字证书(证书中包含用于加密的公钥),另外服务器也会生成一个随机数给客户端;
    3. 客户端回应:这一步客户端首先会校验数字证书的合法性,然后会再生成一个随机数,这个随机数会使用第2步中的公钥采用非对称加密算法(例如RSA算法)进行加密后传给服务器,密文只能通过服务器的私钥来解密。
    4. 服务器最后回应:握手结束。

    握手结束后,客户端和服务器都有上面握手阶段的三个随机数。客户端和服务器都通过这三个随机生成一个密钥,接下来所有的通信内容都使用这个密钥通过对称加密算法加密传输,服务器和客户端才开始进行安全的通信。

    如果看到这里还是一脸懵逼,可以参考SSL/TLS协议运行机制的概述更深入地了解SSL/TLS流程,本文不再过多介绍。

    生成私钥和证书

    使用openssl来生成私钥和证书:

    openssl req -x509 -newkey rsa:2048 -nodes -days 365 -keyout private.pem -out cert.crt

    运行以上命令后,会在当前目录下生成一个私钥文件(private.pem)和一个证书文件(cert.crt)。

    生成的私钥和证书Twisted、Netty可以直接使用,然而MINA对私钥文件的格式的要求,要将pem格式转换成der格式,实际上就是将文本文件私钥转成二进制文件私钥。openssl将private.pem转成private.der私钥文件:

    openssl pkcs8 -topk8 -inform PEM -in private.pem -outform DER -nocrypt -out private.der

    SSL/TLS服务器

    接下来在http://xxgblog.com/2014/08/21/mina-netty-twisted-2/一文的基础上,加上SSL/TLS层。

    MINA

    MINA可以通过SslFilter来实现SSL/TLS,初始化SslFilter的代码比较繁琐:

    public class MinaServer {
    
        public static void main(String[] args) throws Exception {
    
    
            String certPath = "/Users/wucao/Desktop/ssl/cert.crt";  // 证书
            String privateKeyPath = "/Users/wucao/Desktop/ssl/private.der";  // 私钥
    
            // 证书
            // https://docs.oracle.com/javase/7/docs/api/java/security/cert/X509Certificate.html
            InputStream inStream = null;
            Certificate certificate = null;
            try {
                inStream = new FileInputStream(certPath);
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                certificate = cf.generateCertificate(inStream);
            } finally {
                if (inStream != null) {
                    inStream.close();
                }
            }
    
            // 私钥
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Files.readAllBytes(new File(privateKeyPath).toPath()));
            PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
    
            KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
            ks.load(null, null);
            Certificate[] certificates = {certificate};
            ks.setKeyEntry("key", privateKey, "".toCharArray(), certificates);
    
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(ks, "".toCharArray());
    
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(kmf.getKeyManagers(), null, null);
    
            IoAcceptor acceptor = new NioSocketAcceptor();
            DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
            chain.addLast("ssl", new SslFilter(sslContext));  // SslFilter需要放在最前面
            chain.addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"), "
    ", "
    ")));
            acceptor.setHandler(new TcpServerHandle());
            acceptor.bind(new InetSocketAddress(8080));
        }
    }
    
    
    class TcpServerHandle extends IoHandlerAdapter {
    
        @Override
        public void exceptionCaught(IoSession session, Throwable cause)
                throws Exception {
            cause.printStackTrace();
        }
    
        @Override
        public void messageReceived(IoSession session, Object message)
                throws Exception {
            String line = (String) message;
            System.out.println("messageReceived:" + line);
        }
    
        @Override
        public void sessionCreated(IoSession session) throws Exception {
            System.out.println("sessionCreated");
        }
    
        @Override
        public void sessionClosed(IoSession session) throws Exception {
            System.out.println("sessionClosed");
        }
    }

    Netty

    Netty通过添加一个SslHandler来实现SSL/TLS,相对MINA来说代码就比较简洁:

    public class NettyServer {
    
        public static void main(String[] args) throws InterruptedException, SSLException {
    
            File certificate = new File("/Users/wucao/Desktop/ssl/cert.crt");  // 证书
            File privateKey = new File("/Users/wucao/Desktop/ssl/private.pem");  // 私钥
            final SslContext sslContext = SslContextBuilder.forServer(certificate, privateKey).build();
    
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            try {
                ServerBootstrap b = new ServerBootstrap();
                b.group(bossGroup, workerGroup)
                        .channel(NioServerSocketChannel.class)
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            public void initChannel(SocketChannel ch)
                                    throws Exception {
                                ChannelPipeline pipeline = ch.pipeline();
    
                                // SslHandler要放在最前面
                                SslHandler sslHandler = sslContext.newHandler(ch.alloc());
                                pipeline.addLast(sslHandler);
    
                                pipeline.addLast(new LineBasedFrameDecoder(80));
                                pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
    
                                pipeline.addLast(new TcpServerHandler());
                            }
                        });
                ChannelFuture f = b.bind(8080).sync();
                f.channel().closeFuture().sync();
            } finally {
                workerGroup.shutdownGracefully();
                bossGroup.shutdownGracefully();
            }
        }
    
    }
    
    class TcpServerHandler extends ChannelInboundHandlerAdapter {
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            String line = (String) msg;
            System.out.println("channelRead:" + line);
        }
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            System.out.println("channelActive");
        }
    
        @Override
        public void channelInactive(ChannelHandlerContext ctx) {
            System.out.println("channelInactive");
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }

    Twisted

    Twisted实现SSL/TLS也是非常简单的,将reactor.listenTCP替换为reactor.listenSSL即可

    # -*- coding:utf-8 –*-
    
    from twisted.protocols.basic import LineOnlyReceiver
    from twisted.internet.protocol import Factory
    from twisted.internet import reactor, ssl
    
    sslContext = ssl.DefaultOpenSSLContextFactory(
        '/Users/wucao/Desktop/ssl/private.pem',  # 私钥
        '/Users/wucao/Desktop/ssl/cert.crt',  # 公钥
    )
    
    class TcpServerHandle(LineOnlyReceiver):
    
        def connectionMade(self):
            print 'connectionMade'
    
        def connectionLost(self, reason):
            print 'connectionLost'
    
        def lineReceived(self, data):
            print 'lineReceived:' + data
    
    factory = Factory()
    factory.protocol = TcpServerHandle
    reactor.listenSSL(8080, factory, sslContext)
    reactor.run()

    SSL/TLS客户端

    这里还是使用Java来写一个SSL/TLS客户端,用来测试以上三个服务器程序。需要注意的是,在上面SSL/TLS流程的介绍中,SSL/TLS握手阶段的第2步服务器会将证书传给客户端,第3步客户端会校验证书的合法性,所以下面的代码首先会让客户端信任openssl生成的证书,才能正确的完成SSL/TLS握手。

    public class SSLClient {
    
        public static void main(String args[]) throws Exception {
    
            // 客户端信任改证书,将用于校验服务器传过来的证书的合法性
            String certPath = "/Users/wucao/Desktop/ssl/cert.crt";
            InputStream inStream = null;
            Certificate certificate = null;
            try {
                inStream = new FileInputStream(certPath);
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                certificate = cf.generateCertificate(inStream);
            } finally {
                if (inStream != null) {
                    inStream.close();
                }
            }
    
            KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
            ks.load(null, null);
            ks.setCertificateEntry("cert", certificate);
    
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("sunx509");
            tmf.init(ks);
    
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, tmf.getTrustManagers(), null);
    
            SSLSocketFactory socketFactory = sslContext.getSocketFactory();
    
            Socket socket = null;
            OutputStream out = null;
    
            try {
    
                socket = socketFactory.createSocket("localhost", 8080);
                out = socket.getOutputStream();
    
                // 请求服务器
                String lines = "床前明月光
    疑是地上霜
    举头望明月
    低头思故乡
    ";
                byte[] outputBytes = lines.getBytes("UTF-8");
                out.write(outputBytes);
                out.flush();
    
            } finally {
                // 关闭连接
                out.close();
                socket.close();
            }
    
        }
    }

     

    MINA、Netty、Twisted一起学系列

    MINA、Netty、Twisted一起学(一):实现简单的TCP服务器

    MINA、Netty、Twisted一起学(二):TCP消息边界问题及按行分割消息

    MINA、Netty、Twisted一起学(三):TCP消息固定大小的前缀(Header)

    MINA、Netty、Twisted一起学(四):定制自己的协议

    MINA、Netty、Twisted一起学(五):整合protobuf

    MINA、Netty、Twisted一起学(六):session

    MINA、Netty、Twisted一起学(七):发布/订阅(Publish/Subscribe)

    MINA、Netty、Twisted一起学(八):HTTP服务器

    MINA、Netty、Twisted一起学(九):异步IO和回调函数

    MINA、Netty、Twisted一起学(十):线程模型

    MINA、Netty、Twisted一起学(十一):SSL/TLS

    MINA、Netty、Twisted一起学(十二):HTTPS

    源码

    https://github.com/wucao/mina-netty-twisted

  • 相关阅读:
    2014 ACM/ICPC Asia Regional Guangzhou Online HDU 5024 Wang Xifeng's Little Plot
    HDU 5015 2014 ACM/ICPC Asia Regional Xi'an Online 233 Matrix
    POJ 1269 Intersecting Lines(直线相交的判断)
    POJ 3304 Segments
    POJ 2318 TOYS
    The 2014 ACM-ICPC Asia Mudanjiang Regional First Round C ZOj 3811
    unique函数(先记下来)
    计算几何常用算法(数学知识)
    HUD 3461 codelock 纯题意解释,不含思想
    POJ 1182 食物链
  • 原文地址:https://www.cnblogs.com/wucao/p/6474711.html
Copyright © 2011-2022 走看看