保护通信
经过开放通道(如公共Internet)的秘密通信绝对需要对数据加密.适合计算机实现的大多数加密机制都是基于密钥思想的.密钥是一种更加一般化的口令,并不限于文本.明文消息根据一种数学算法与密钥的各个位组合,生成加密的密文.使用的密钥位数越多,通过暴力破解的方法解密消息时就会越困难.
在传统的秘密密匙(对称加密)加密中,加密和解密使用相同的密钥.发送方和接收方都需要知道这个密钥.假设A希望给B发送一个秘密消息.A首先向B发送一个交换秘密的密钥.A和B以后的通信都是用这个密钥进行加密解密.很容易发现问题,如果C监听了A和B之间的连接,那么这个密钥C也会得知.A和B之间的通信则没有任何的秘密.
解决对称加密被监听的方法是加密和解密使用不同的密钥(非对称加密).一个密钥成为公开密钥,用于加密数据.这个密钥可以提供给任何人.另外一个密钥成为私有秘钥用来解密,这个私有秘钥必须秘密保存.假设A希望给B发送秘密消息,A需要先向B要一个公开密钥,然后使用B提供的公开密钥对消息进行加密,因为这个消息是通过B的公开密钥加密的,只有B的私有秘钥才可以解密.这种情况下C也可以获取公共密钥,但是C只能使用这个密钥进行加密而不能解密.A可以秘密的和B发送消息.
为什么是A可以秘密的和B发送消息呢?因为B要回复消息也需要使用相同的方法获取A的公共密钥,然后使用A的公共密钥加密回复给A.也就是说A向B发送消息使用B的公钥加密,B接收A的消息使用自己的私钥解密.B回复A使用A的公钥加密,A接收B的消息使用自己的私钥解密.这种情况下非对称加密的复杂性就体现出来了.实际的做法中,我们可能是这么做:
A向B获取公钥,将对称加密密码使用公钥加密发送给B,B使用自己的私钥解密.之后A,B通信使用这个对称加密手段通信.
非对称加密看似很安全但实则有一个致命的漏洞.如果A向B获取公钥时,C监听了通信,并且伪造了B的公钥,将一个假的公钥发送给A,A并不知道这个是假的,则使用这个假的公钥进行加密对称密码,C自己解密后获取到了对称加密的密码,然后又使用B的公钥把这个密码加密发送给B.B也不知道这个密码已经被看过了.A,B两人甚至都不知道C这个中间人的存在,这称为中间人攻击.
实际中解决方案是B把公钥交给可信任的第三方认证机构,A从第三方认证机构获取B的公钥,虽然C依然可以潜伏在第三方机构,但是对于A和B直接交换公钥来说,C要想进行攻击就困难的多了.
演示代码
要使用安全的Socket需要有以下几个步骤。
1 使用keytool生成公开的密钥和证书(keytool是JDK自带的工具)生成的证书在cmd目录下。
2 花钱请可信任的第三方认证你的证书(本地演示则不用).
3 为你使用的算法创建一个SSLContext.
4 为你要使用的证书源创建一个TrustManagerFactory.
5 为你要使用的密钥类型创建一个KeyManagerFactory。
6 为密钥和证书数据库创建一个KeyStore对象
7 用密钥和证书填充KeyStore对象。例如从文件系统添加。
8 使用KeyStore及其口令初始化KeyManagerFactory。
9 用KeyManagerFactory中的密钥管理器,TrustManagerFactory中的信任管理器和一个随机源来创建初始化上下文。
注:keytool工具使用方法参考 http://www.mybatis.cn/archives/1054.html
注:以下代码片段参考 https://blog.csdn.net/mn960mn/article/details/49746085
package com.datang.bingxiang.demo; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; import java.security.KeyStore; import javax.net.SocketFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManagerFactory; public class SSLClient { static SSLSocket createSocket(String host, int port) throws Exception { KeyStore ks = KeyStore.getInstance("jks"); InputStream input = new FileInputStream("C:/Users/86152/trust.jks"); ks.load(input, "123456".toCharArray()); TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(ks); SSLContext context = SSLContext.getInstance("TLSv1.2"); /** * KeyManager[] 第一个参数是授权的密钥管理器,用来授权验证。TrustManager[]第二个是被授权的证书管理器, * 用来验证服务器端的证书。第三个参数是一个随机数值,可以填写null。如果只是服务器传输数据给客户端来验证,就传入第一个参数就可以, * 客户端构建环境就传入第二个参数。双向认证的话,就同时使用两个管理器。 */ context.init(null, tmf.getTrustManagers(), null); input.close(); SocketFactory sf = context.getSocketFactory(); return (SSLSocket) sf.createSocket(host, port); } public static void main(String[] args) throws Exception { Socket s = createSocket("127.0.0.1", SSLServer.port); InputStream input = s.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(input)); String str = null; while((str=br.readLine())!=null) { System.out.println(str); } } }
package com.datang.bingxiang.demo; import java.io.FileInputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.security.KeyStore; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; public class SSLServer { public static final int port = 4488; static SSLServerSocket createServerSocket(int port) throws Exception { KeyStore ks = KeyStore.getInstance("jks"); InputStream input = new FileInputStream("C:/Users/86152/myserver.jks"); ks.load(input, "123456".toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(ks, "123456".toCharArray()); SSLContext context = SSLContext.getInstance("TLSv1.2"); /** * KeyManager[] 第一个参数是授权的密钥管理器,用来授权验证。TrustManager[]第二个是被授权的证书管理器, * 用来验证服务器端的证书。第三个参数是一个随机数值,可以填写null。如果只是服务器传输数据给客户端来验证,就传入第一个参数就可以, * 客户端构建环境就传入第二个参数。双向认证的话,就同时使用两个管理器。 */ context.init(kmf.getKeyManagers(), null, null); input.close(); SSLServerSocketFactory ssf = context.getServerSocketFactory(); return (SSLServerSocket) ssf.createServerSocket(port); } public static void main(String[] args) throws Exception { final ServerSocket ss = createServerSocket(port); System.out.println("ssl server startup at port " + port); while (true) { Socket s = ss.accept(); OutputStream output = null; output = s.getOutputStream(); output.write("hello word".getBytes()); output.flush(); s.close(); System.out.println("3333333333"); } } }