Java网络编程——第十章 安全Sokcet

对称加密,加密解密使用相同的秘钥,收发双发都必须知道这个秘钥,秘钥不可公开;加密解密速度很快
非对称加密,加密解密使用不同的秘钥,公钥用于加密,可以公开,私钥用于解密,不可公开;保证机密性。、完整性、真实性;CPU密集型,速度较慢
一般用法:用非对称加密算法加密对称加密秘钥,最后用对称加密算法加密实际数据

Java安全Socket扩展(JSSE)
javax.net.ssl,定义Java安全网络通信API的抽象类
javax.net 替代构造函数创建安全Socket的抽象Socket工厂类
java.security.cert 处理ssl所需公开秘钥证书的类
com.sun.net.ssl Sun的JSSE参考实现中实现加密算法和协议的具体类,注意该类不是JSSE标准,可以用其他实现替换该包

创建安全Socket

1、使用javax.net.ssl中的SSLSocketFctory,获取SocketFactory实例factory
2、使用factory创建Socket对象,返回的都是javax.net.ssl.SSLSocket
     创建并返回一个连接到指定主机和端口的Socket
     1、public abstract Socket Socket creatSoket(String host, int port) throws IOException, UnknownHostException
     2、public abstract Socket Socket creatSoket(InetAddress host, int port) throws IOException
     从指定本地网络接口和端口连接到指定主机和端口
     3、public abstract Socket Socket creatSoket(String host, int port, InetAddress interface, int localPort) throws IOException, UnknownHostException
     4、public abstract Socket Socket creatSoket(InetAddress host, int port, InetAddress interfacr, int localPort) throws IOException, UnknownHostException
     以代理服务器现有的Socket为起点,经该代理服务器连接到指定的主机和端口,一种autoClose指明该Socket关闭后,底层的proxy Socket是否关闭
     5、public abstract Socket Socket creatSoket(Socket proxy, String host, int port, boolean autoClose) throws IOException
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

public class HTTPSClient {
    public static void main(String[] args) {
        // https 默认端口
        int port = 443;
        String host = "www.baidu.com";

        SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
        SSLSocket socket = null;
        try {
            socket = (SSLSocket) factory.createSocket(host, port);

            // 启用所有密码组
            String[] supported = socket.getSupportedCipherSuites();
            socket.setEnabledCipherSuites(supported);

            Writer out = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
            // https requires the full URL in the GET line
            out.write("GET http://" + host + "/ HTTP/1.1\r\n");
            out.write("Host: " + host + "\r\n");
            out.write("\r\n");
            out.flush();

            // read response
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            // 读取响应头部
            String s;
            while (!(s = in.readLine()).equals("")) {
                System.out.println(s);
            }
            System.out.println();

            // read the length
            String contentLength = in.readLine();
            int length = Integer.MAX_VALUE;
            try {
                length = Integer.parseInt(contentLength.trim(), 16);
            } catch (NumberFormatException ex) {
                // 服务器发送响应的第一行没有发送content-length
            }
            System.out.println(contentLength);


            int c;
            int i = 0;
            while ((c = in.read()) != -1 && i++ < length) {
                System.out.write(c);
            }

            System.out.println();
        } catch (IOException ex) {
            System.err.println(ex);
        } finally {
            try {
                if (socket != null)
                    socket.close();
            } catch (IOException e) {
            }
        }
    }
}

选择密码组
JSSE支持不同认证和加密算法组合,使用SSLSocketFactory 的getSupportedCipherSuites获取支持的组合,处于某些组合强度太弱而被禁用的缘故,可以使用SSLSocket的getEnabledCipherSuites指明可以使用的组合;需要注意的是,实际使用的组合需要客户端和服务端的协商,有一方不同意或者没有相关证书则抛出IOException的子类SSLException,可以通过SSLSocket的setEnabledCipherSuites修改需要使用的密码组;一般使用TLE_ECDHE开头并以SHA256或者SHA384结尾的组合最强;
DES,块加密方式,固定64位一块,不足使用额外位填充
AES,块加密方式,128、192、256位的块,不足位需要额外位填充;
RC4,流加密方式,一次加密一个字节,适用于可能需要一次发送一个字节的协议

事件处理器
由于认证网络通信速度很慢,因此一般需要异步处理连接,JSSE采用标准Java事件模型通知程序,即

得到握手结束事件通知,实现
public interface HandshakeCompletedListener extends java.util.EventListener
     public void handshakeCompleted(HandshakeCompletedEvent event)
其中参数HandshakeCompletedEvent
public class HandshakeCompletedEvent extends java.util.EventObject,该类提供以下方法
     public SSLSession getSession()
     public String getCipherSuite()
     public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifedException
     public SSLSocket getSocket()
通过HandshakeCompletedListener的
     public abstract void addHandshakeCompletedListener(HandshakeCompletedListener listener)
     public abstract void removeHandshakeCompletedListener(HandshakeCompletedListener listener) throws IllegleArguementException
实现对SSLSocket对象的握手结束事件的注册

会话管理,由于安全握手时间较长,会话管理解决Session共享问题,SSL允许扩展到多个Socket的Session,相同Session的不同Sokcet可以共享同一组秘钥,这样只有会话中的第一个Socket需要生成和交换秘钥的开销;对于在一个较短的时间内对一个主机的一个端口打开多个安全Socket,JSSE会自动重用会话秘钥;安全Socket的会话有SSLSession管理
获得Session
public abstract SSLSession getSession()
是否允许创建会话
public abstract void setEnableSessionCreation(boolean allowCreation)
重新开始会话,即放弃之前的秘钥、证书等
public abstract void startHandshake() throws IOException

客户端模式,一般而言不需要验证客户端的合法性
public abstract void setUseClientMod(boolean mode) throws IlleagleArguementException,如果传入true,则使用client的模式,不需要认证;客户端服务端都可以使用该方法;注意该方法对于任何指定Socket只能设置一次,否则抛出IllegleArguementException
public abstract void setNeedClientAuth(boolean needAuthentication),该方法用于SSLServerSocket,指明与他连接的客户端是否需要认证;若Socket不用在服务端,则抛出IlleglArguementException

服务端安全Socket,可客户端SSLSocket类似,public abstract class SSLServerSosocket extends ServerSocket, 其创建方式通过 public abstract class SSLServerSocketFactory获得SSLServerSocketFactory,进而通过该SSLServerSocketFactory创建Socket,creatServerSocket();
注意,SSLServerSocketFactory.getDefault(),返回的factory只支持服务器认证,不支持加密,sun的参考实现需要com.sun.net.ssl.SSLContext对象负责创建已经充分配置的和初始化的SSLServerSocket,一般步骤
1、使用keytool生成公开秘钥和证书
2、申请可信任第三方认证生成的整数
3、创建SSLContext
4、为证书源创建TrustManagerFactory
5、位使用的秘钥类型创建KeyManagerFactory
6、为秘钥和证书数据库创建KeyStore对象,对于Oracle默认值为JKS
7、用秘钥和证书填充KeyStore对象
8、用KeyStore及其短语初始化KeyManagerFactory,
9、用keyManagerFactory的秘钥管理器(必要)、TrustManagerFactory的信任管理器(可选)和一个随机源(可选)初始化上下文;后两个若使用默认值,则为null
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Arrays;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;


public class SecureOrderTaker {
    public final static int PORT = 7000;
    public final static String algorithm = "SSL";

    public static void main(String[] args) {
        try {
            SSLContext context = SSLContext.getInstance(algorithm);
            // 参考实现只支持X.509秘钥
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
            // Oracle默认秘钥库类型
            KeyStore ks = KeyStore.getInstance("JKS");
            /* 出于安全考考虑,每个秘钥库都要使用短语加密
             * 从从磁盘读取前,都必须提供这个短语,短语以char[]形式存储
             * 这样可以快速送内存擦除,而非等待GC
             */
            //char[] password = System.console().readPassword();
            char[] password = "wyc660904".toCharArray();
            // 证书文件的创建 http://www.cnblogs.com/youxia/p/java002.html
            ks.load(new FileInputStream("C:\\Users\\Administrator\\Desktop\\程序学习\\java网络编程\\JavaNetworkProgramming\\src\\ch10\\jnp4e.keys"), password);
            kmf.init(ks, password);
            context.init(kmf.getKeyManagers(), null, null);

            // 擦除秘钥
            Arrays.fill(password, '0');

            SSLServerSocketFactory factory = context.getServerSocketFactory();
            SSLServerSocket server = (SSLServerSocket) factory.createServerSocket(PORT);

            // 增加匿名(未认证)密码组
            String[] supported = server.getSupportedCipherSuites();
            String[] anonCipherSuitesSupported = new String[supported.length];
            int numAnonCipherSuitesSupported = 0;
            for (int i = 0; i < supported.length; i++) {
                if (supported[i].indexOf("_anon_") > 0) {
                    anonCipherSuitesSupported[numAnonCipherSuitesSupported++] = supported[i];
                }
            }
            String[] oldEnabled = server.getEnabledCipherSuites();
            String[] newEnabled = new String[oldEnabled.length + numAnonCipherSuitesSupported];
            System.arraycopy(oldEnabled, 0, newEnabled, 0, oldEnabled.length);
            System.arraycopy(anonCipherSuitesSupported, 0, newEnabled, oldEnabled.length, numAnonCipherSuitesSupported);
            server.setEnabledCipherSuites(newEnabled);

            // 设置完成,可以进行实际通信
            while (true) {
                try (Socket theConnection = server.accept()) {
                    InputStream in = theConnection.getInputStream();
                    int c;
                    while ((c = in.read()) != -1) {
                        System.out.write(c);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        } catch (IOException | KeyManagementException | KeyStoreException | NoSuchAlgorithmException
                | CertificateException | UnrecoverableKeyException e) {
            e.printStackTrace();
        }
    }
}

配置SSLServerSocket和SSLSocket类似,同样可以选择密码组、会话管理、客户端模式



你可能感兴趣的:(java网络编程)