什么是SSL/TLS

不使用SSL/TLS的網(wǎng)絡(luò)通信,一般都是明文傳輸,網(wǎng)絡(luò)傳輸內(nèi)容在傳輸過程中很容易被竊聽甚至篡改,非常不安全。SSL/TLS協(xié)議就是為了解決這些安全問題而設(shè)計(jì)的。SSL/TLS協(xié)議位于TCP/IP協(xié)議之上,各個(gè)應(yīng)用層協(xié)議之下,使網(wǎng)絡(luò)傳輸?shù)膬?nèi)容通過加密算法加密,并且只有服務(wù)器和客戶端可以加密解密,中間人即使抓到數(shù)據(jù)包也無法解密獲取傳輸?shù)膬?nèi)容,從而避免安全問題。例如廣泛使用的HTTPS協(xié)議即是在TCP協(xié)議和HTTP協(xié)議之間加了一層SSL/TLS協(xié)議。

相關(guān)術(shù)語

在學(xué)習(xí)SSL/TLS協(xié)議之前,首先要了解一些相關(guān)概念: 
- 對(duì)稱加密:加密和解密都采用同一個(gè)密鑰,常用的算法有DES、3DES、AES,相對(duì)于非對(duì)稱加密算法更簡(jiǎn)單速度更快。 
- 非對(duì)稱加密:和對(duì)稱加密算法不同,非對(duì)稱加密算法會(huì)有兩個(gè)密鑰:公鑰(可以公開的)和私鑰(私有的),例如客戶端如果使用公鑰加密,那么即時(shí)其他人有公鑰也無法解密,只能通過服務(wù)器私有的私鑰解密。RSA算法即是典型的非對(duì)稱加密算法。 
- 數(shù)字證書:數(shù)字證書是一個(gè)包含公鑰并且通過權(quán)威機(jī)構(gòu)發(fā)行的一串?dāng)?shù)據(jù),數(shù)字證書很多需要付費(fèi)購買,也有免費(fèi)的,另外也可以自己生成數(shù)字證書,本文中將會(huì)采用自簽名的方式生成數(shù)字證書。

SSL/TLS流程

使用SSL/TLS協(xié)議的服務(wù)器和客戶端開始通信之前,會(huì)先進(jìn)行一個(gè)握手階段:

  1. 客戶端發(fā)出請(qǐng)求:這一步客戶端會(huì)生成一個(gè)隨機(jī)數(shù)傳給服務(wù)器;

  2. 服務(wù)器回應(yīng):這一步服務(wù)器會(huì)返回給客戶端一個(gè)服務(wù)器數(shù)字證書(證書中包含用于加密的公鑰),另外服務(wù)器也會(huì)生成一個(gè)隨機(jī)數(shù)給客戶端;

  3. 客戶端回應(yīng):這一步客戶端首先會(huì)校驗(yàn)數(shù)字證書的合法性,然后會(huì)再生成一個(gè)隨機(jī)數(shù),這個(gè)隨機(jī)數(shù)會(huì)使用第2步中的公鑰采用非對(duì)稱加密算法(例如RSA算法)進(jìn)行加密后傳給服務(wù)器,密文只能通過服務(wù)器的私鑰來解密。

  4. 服務(wù)器最后回應(yīng):握手結(jié)束。

握手結(jié)束后,客戶端和服務(wù)器都有上面握手階段的三個(gè)隨機(jī)數(shù)。客戶端和服務(wù)器都通過這三個(gè)隨機(jī)生成一個(gè)密鑰,接下來所有的通信內(nèi)容都使用這個(gè)密鑰通過對(duì)稱加密算法加密傳輸,服務(wù)器和客戶端才開始進(jìn)行安全的通信。

如果看到這里還是一臉懵逼,可以參考SSL/TLS協(xié)議運(yùn)行機(jī)制的概述更深入地了解SSL/TLS流程,本文不再過多介紹。

生成私鑰和證書

使用openssl來生成私鑰和證書:

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

運(yùn)行以上命令后,會(huì)在當(dāng)前目錄下生成一個(gè)私鑰文件(private.pem)和一個(gè)證書文件(cert.crt)。

生成的私鑰和證書Twisted、Netty可以直接使用,然而MINA對(duì)私鑰文件的格式的要求,要將pem格式轉(zhuǎn)換成der格式,實(shí)際上就是將文本文件私鑰轉(zhuǎn)成二進(jìn)制文件私鑰。openssl將private.pem轉(zhuǎn)成private.der私鑰文件:

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

SSL/TLS服務(wù)器

接下來在http://xxgblog.com/2014/08/21/mina-netty-twisted-2/一文的基礎(chǔ)上,加上SSL/TLS層。

MINA

MINA可以通過SslFilter來實(shí)現(xiàn)SSL/TLS,初始化SslFilter的代碼比較繁瑣:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

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"), "\r\n", "\r\n")));
        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");
    }
}

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

Netty

Netty通過添加一個(gè)SslHandler來實(shí)現(xiàn)SSL/TLS,相對(duì)MINA來說代碼就比較簡(jiǎn)潔:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

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();
    }
}

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

Twisted

Twisted實(shí)現(xiàn)SSL/TLS也是非常簡(jiǎn)單的,將reactor.listenTCP替換為reactor.listenSSL即可

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

# -*- coding:utf-8 –*-from twisted.protocols.basic import LineOnlyReceiverfrom twisted.internet.protocol import Factoryfrom 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()

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

SSL/TLS客戶端

這里還是使用Java來寫一個(gè)SSL/TLS客戶端,用來測(cè)試以上三個(gè)服務(wù)器程序。需要注意的是,在上面SSL/TLS流程的介紹中,SSL/TLS握手階段的第2步服務(wù)器會(huì)將證書傳給客戶端,第3步客戶端會(huì)校驗(yàn)證書的合法性,所以下面的代碼首先會(huì)讓客戶端信任openssl生成的證書,才能正確的完成SSL/TLS握手。

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

public class SSLClient {    public static void main(String args[]) throws Exception {        // 客戶端信任改證書,將用于校驗(yàn)服務(wù)器傳過來的證書的合法性
        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();            // 請(qǐng)求服務(wù)器
            String lines = "床前明月光\r\n疑是地上霜\r\n舉頭望明月\r\n低頭思故鄉(xiāng)\r\n";            byte[] outputBytes = lines.getBytes("UTF-8");
            out.write(outputBytes);
            out.flush();

        } finally {            // 關(guān)閉連接            out.close();
            socket.close();
        }

    }
}

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

MINA、Netty、Twisted一起學(xué)系列

MINA、Netty、Twisted一起學(xué)(一):實(shí)現(xiàn)簡(jiǎn)單的TCP服務(wù)器

MINA、Netty、Twisted一起學(xué)(二):TCP消息邊界問題及按行分割消息

MINA、Netty、Twisted一起學(xué)(三):TCP消息固定大小的前綴(Header)

MINA、Netty、Twisted一起學(xué)(四):定制自己的協(xié)議

MINA、Netty、Twisted一起學(xué)(五):整合protobuf

MINA、Netty、Twisted一起學(xué)(六):session

MINA、Netty、Twisted一起學(xué)(七):發(fā)布/訂閱(Publish/Subscribe)

MINA、Netty、Twisted一起學(xué)(八):HTTP服務(wù)器

MINA、Netty、Twisted一起學(xué)(九):異步IO和回調(diào)函數(shù)

MINA、Netty、Twisted一起學(xué)(十):線程模型

MINA、Netty、Twisted一起學(xué)(十一):SSL/TLS