1. 程式人生 > >MINA、Netty、Twisted一起學(十一):SSL/TLS

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伺服器

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

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 = "床前明月光\r\n疑是地上霜\r\n舉頭望明月\r\n低頭思故鄉\r\n";
            byte[] outputBytes = lines.getBytes("UTF-8");
            out.write(outputBytes);
            out.flush();

        } finally {
            // 關閉連線
            out.close();
            socket.close();
        }

    }
}

MINA、Netty、Twisted一起學系列

原始碼

相關推薦

MINANettyTwisted一起SSL/TLS

什麼是SSL/TLS 不使用SSL/TLS的網路通訊,一般都是明文傳輸,網路傳輸內容在傳輸過程中很容易被竊聽甚至篡改,非常不安全。SSL/TLS協議就是為了解決這些安全問題而設計的。SSL/TLS協議位於TCP/IP協議之上,各個應用層協議之下,使網路傳輸的內

MINANettyTwisted一起HTTPS

由於HTTPS協議是由HTTP協議加上SSL/TLS協議組合而成,在閱讀本文前可以先閱讀一下HTTP伺服器和SSL/TLS兩篇博文,本文中的程式碼也是由這兩篇博文中的程式碼組合而成。 HTTPS介紹 上一篇博文中介紹了SSL/TLS協議,我們平時接觸最多的S

MinaNettyTwisted一起執行緒模型

要想開發一個高效能的TCP伺服器,熟悉所使用框架的執行緒模型非常重要。MINA、Netty、Twisted本身都是高效能的網路框架,如果再搭配上高效率的程式碼,才能實現一個高大上的伺服器。但是如果不瞭解它們的執行緒模型,就很難寫出高效能的程式碼。框架本身效率再高,程式寫的太差

Android開發系列對手機通訊錄的讀取新增刪除查詢

一、通訊錄介紹 通訊錄是Android手機自帶的一個應用,它是一個ContentProvider應用,其它應用可以對通訊錄進行訪問,進行對聯絡人的CRUD操作。 二、通訊錄資料庫結構的介紹 首先,我們可以在File Explorer檢視下找到contacts2.db檔案,

大學生程式設計如何建立程式設計邏輯思維

編者按:誰沒有年輕過,誰沒有二過? 作為一個軟體工程的學生,雖然專業課(C,資料結構之類的)學得算是過得去吧,但是始終覺得沒有建立起程式設計所需要的邏輯思維。也許跟我從小數學不好有關。打個比方吧,簡單的問題可以很快搞定,但是遇到較為複雜的問題,就很難將其抽象出來,總感覺腦中一團混沌。為此

EF學習筆記實施繼承

long cannot oid data- turn cati com list pac 學習總目錄:ASP.NET MVC5 及 EF6 學習筆記 - (目錄整理) 上篇鏈接:EF學習筆記(十) 處理並發 本篇原文鏈接:Implementing Inheritance 面

DockerDocker實戰部署HTTPS的Tomcat站點

pos 容器 image avi tor 基礎 oot stat ask 1、選擇基礎鏡像    docker pull tomcat:7.0-jre8 2、生成HTTPS證書   keytool -genkey -alias tomcat -keyalg RSA -key

R語言學習筆記廣義線性模型

學習筆記 Education 5.0 1.3 style only 可能性 div erro #Logistic 回歸 install.packages("AER") data(Affairs,package="AER") summary(Affairs) a

Python+Selenium筆記配置selenium Grid

find arch 自己 {} get 說明 前言 mon AC (一) 前言 Selenium Grid可以將測試分布在若幹個物理或虛擬機器上,從而實現分布方式或並行方式執行測試。 這個鏈接是官方的相關說明。 https://github.com/SeleniumHQ/s

Python筆記多線程

st2 pv操作 出現 end 談話 col 隊列大小 == done (二)和(三)不感興趣的可以跳過,這裏參考了《深入理解計算機系統》第一章和《Python核心編程》第四章 (一) 多線程編程 一個程序包含多個子任務,並且子任務之間相互獨立,讓這些子任務同時運

hadoop學習筆記MapReduce數據類型

筆記 ash all 記錄 write 一個 操作 png bool 一、序列化 1 hadoop自定義了數據類型,在hadoop中,所有的key/value類型必須實現Writable接口。有兩個方法,一個是write,一個是readFileds。分別用於讀(反序列化操

Android項目實戰moveTaskToBack(boolean ) 方法的使用

android項目 androi ID cti 項目實戰 htm www style 順序 原文:Android項目實戰(十一):moveTaskToBack(boolean ) 方法的使用當你開發的程序被按後退鍵退出的時候, 你肯定不想讓他就這麽被finish()吧,那麽

IntelliJ IDEA Debug的使用

答案 停止 mar val 圖標設計 art style 打印 top Debug模式是開發人員必會的一項調試程序的技能,用來追蹤代碼的運行流程,線上問題追蹤,程序運行異常定位調試,以及在運行過程中參數的變化。IDEA的debug對於新手如何快速上手呢? 一、基本介紹

java基礎學習總結自動裝箱和自動拆箱

自動拆箱和自動裝箱 Java為每種基本資料型別都提供了對應的包裝器型別。舉個例子: public class TestMain { public static void main(String[] args) { Integer i = 10; } }

javaweb學習筆記JSP1

目錄 jsp(1) 1.基本概念 1 .1Jsp的執行過程 1.2 JSP與servlet 2 JSP語法 2.1 Jsp模板 2.2 Jsp表示式 2.3 Jsp指令碼 2.4 Jsp宣告 2.5 Jsp註釋 jsp(1) 1.基本

學習筆記使用K-Means演算法檢測DGA域名

1.資料收集與清洗:同(十) 2.特徵化:同(十) 3.訓練樣本 model = KMeans(n_clusters = 2, random_state=random_state) y_pred = model.fit_predict(x)  4.效果驗證:使用TSNE將

linux系列nl命令

調整 寫到 實例 空行 格式 指定格式 指定 所有 tab 1、命令格式:   nl [選項] [文件] 2、命令功能:   nl(Number of Lines) 將指定的文件添加行號標註後寫到標準輸出。如果不指定文件或指定文件為"-" ,程序將從標準輸入讀取數據。

搭建自己的部落格新增根據日期篩選

之前有通過分類來篩選文章的功能,這次新增根據建立日期來篩選文章。 1、變化的部分 2、上程式碼 ul.blog-types,ul.blog-dates { list-style-type: none; } div.blog:not(:last-child) {

機器學習筆記 TensorFlow實戰三MNIST數字識別問題

1 - MNIST數字識別問題 前面介紹了這樣用TensorFlow訓練一個神經網路模型和主要考慮的問題及解決這些問題的常用方法。下面我們用一個實際的問題來驗證之前的解決方法。 我們使用的是MNIST手寫數字識別資料集。在很多深度學習教程中,這個資料集都會被當做一個案例。 1.1

Cris 的Python筆記面向物件三大特徵之多型

''' Python 的多型:一個物件可以以不同的形態去呈現,實現方式相比 Java更加簡單 因為動態語言的特點,Python 無法實現在程式碼指向前對方法引數進行檢查,所以只要滿足指定方法體的要求,任何資料型別的物件都可以作為該方法的實參 ''' class A: def _