安全Socket

保护通信

经过开放通道(如公共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");
        }
    }
}
原文地址:https://www.cnblogs.com/zumengjie/p/15112303.html