不使用SSL/TLS的網絡通訊,通常都是明文傳輸,網絡傳輸內容在傳輸過程當中很容易被竊聽甚至篡改,很是不安全。SSL/TLS協議就是爲了解決這些安全問題而設計的。SSL/TLS協議位於TCP/IP協議之上,各個應用層協議之下,使網絡傳輸的內容經過加密算法加密,而且只有服務器和客戶端能夠加密解密,中間人即便抓到數據包也沒法解密獲取傳輸的內容,從而避免安全問題。例如普遍使用的HTTPS協議便是在TCP協議和HTTP協議之間加了一層SSL/TLS協議。html
在學習SSL/TLS協議以前,首先要了解一些相關概念:
- 對稱加密:加密和解密都採用同一個密鑰,經常使用的算法有DES、3DES、AES,相對於非對稱加密算法更簡單速度更快。
- 非對稱加密:和對稱加密算法不一樣,非對稱加密算法會有兩個密鑰:公鑰(能夠公開的)和私鑰(私有的),例如客戶端若是使用公鑰加密,那麼即時其餘人有公鑰也沒法解密,只能經過服務器私有的私鑰解密。RSA算法便是典型的非對稱加密算法。
- 數字證書:數字證書是一個包含公鑰而且經過權威機構發行的一串數據,數字證書不少須要付費購買,也有免費的,另外也能夠本身生成數字證書,本文中將會採用自簽名的方式生成數字證書。java
使用SSL/TLS協議的服務器和客戶端開始通訊以前,會先進行一個握手階段:node
握手結束後,客戶端和服務器都有上面握手階段的三個隨機數。客戶端和服務器都經過這三個隨機生成一個密鑰,接下來全部的通訊內容都使用這個密鑰經過對稱加密算法加密傳輸,服務器和客戶端纔開始進行安全的通訊。react
若是看到這裏仍是一臉懵逼,能夠參考SSL/TLS協議運行機制的概述更深刻地瞭解SSL/TLS流程,本文再也不過多介紹。git
使用openssl來生成私鑰和證書:github
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私鑰文件:api
openssl pkcs8 -topk8 -inform PEM -in private.pem -outform DER -nocrypt -out private.der
接下來在http://xxgblog.com/2014/08/21/mina-netty-twisted-2/一文的基礎上,加上SSL/TLS層。安全
MINA能夠經過SslFilter來實現SSL/TLS,初始化SslFilter的代碼比較繁瑣:bash
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經過添加一個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實現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()
這裏仍是使用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一塊兒學(一):實現簡單的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