Java 网络编程 安全 Socket

参考:《Java 网络编程》,部分参考来自网络。

一、保护通信

其实就是客户端与服务器端简历SSL通道,利用对称密钥进行数据的加解密,而非对称密钥是用来身份认证和消息完整性的检查,以及对称密钥的协商和加密。安全通道就是要保证机密性、真实性和完整性。

Java网络程序中使用强加密。JSSE掩盖了如何协商算法、交换密钥、双方认证和加密数据的底层的细节。JSSE可以让你创建socket和服务器socket,透明地处理安全通信中必要的协商和加密。你所要做的就是流和socket来发送数据。

Java安全socket扩展分为四个包:

Javax.net.ssl

定义Java安全网络通信API的抽象类。

Javax.net

替代构造函数创建安全socket的抽象socket工厂类。

Javax.security.cert

Java1.1中处理SSL所需的公开密钥证书的最小类集,Java1.2及以后的版本中,应当用java.security.cert

Com.sun.net.ssl

SunJSSE参考实现中实现加密算法和协议的具体类。从理论上讲,他们不属于JSSE

java中要注册密码提供者有两种方式:

1、把所需要的jar放到jre/lib/ext目录下,然后修改jre/lib/ext/security/java.security文件找到如下行:

Security.provider.1=sun.security.Provider.Sun

Security.provider.2=com.sun.rsajca.Provider

假如如下行:

Security.provider.#(顺序下写来,可能是789)=com.sun.net.ssl.internal.ssl.Provider

2、在代码中实现如下行:

java.security.Security.addProvider(

new com.sun.net.ssl.internal.ssl.Provider());

关于java加解密可以参考《Java 加解密艺术》。

二、创建安全客户端Socket

java.net.Socket对象不是通过构造函数构造的,而是从javax.net.ssl.SSLSocketFactory通过其createSocket()方法得到的。SSLSocketFactory是一个遵循抽象工厂设计模式的抽象类:

Public abstract class SSLSocketFactory extends SocketFactory

由于SSLSocketFactory本身是抽象的,所以要通过调用SSLSocketFactory.getDefault()静态方法得到一个实例:

Public static SocketFactory getDefault() throws InstantiationException

这会返回一个SSLSocketFactory实例,或当没有找到具体子类是会抛出一个InstantiationException异常。一旦有了对工厂类的引用,就可以使用下面的五个重载createSocket()创建一个SSLSocket

Public abstact Socket createSocket(String host, int port) throws IOException, UnknownHostException hostport都是远程主机的)

public abstract Socket createSocket(InetAddress host, int port) throws IOException, UnknownHostException

Public abstract Socket createSocket(String host, int port, InetAddress interface, int localport) throws IOException , UnknownHostExceptioninterface localport都是指定的本地的网络接口和本地端口)

Public abstract Socket createSocket(InetAddress host, int port, InetAddress interface, int localPort) throws IOExcetpion, UnknownHostException

Public abstract Socket createSocket(Socket proxy, String host, int port, boolean autoClose) throws IOException 返回一个经由此代理服务器到指定主机和端口的Socket

一旦建立了安全socket,就可以像其他任何socket一样操作,获取输入输出流以及其他操作(getInputStream()getOutputStream())。

下面的代码试讲通过安全socket发送订单:

创建一个安全的Socket然后向远程“login.metalab.unc.edu”端口为7000服务器连接并发送一些订单信息。

  与非安全的Socket没有什么区别只是有三条语句不同而已。依他的几乎就是正常使用。

try {
		//下面一条语句只是在没有想java.security file 中添加
		//security.provider.3=com.sun.net.ssl.internal.ssl.Provider
		//时才需要。
		Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
		 
		SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
		Socket socket = factory.createSocket("login.metalab.unc.edu", 7000);
		Writer out = new OutputStreamWriter(socket.getOutputStream(), "ASCII");
		out.write("Name: John Smith\r\n");
		out.write("Product-ID:XXX\r\n");
		out.write("Address:XXX\r\n");
		out.write("Cardnamber:XXX\r\n");
		out.write("Expires:XXX\r\n");
		out.flush();
		out.close();
		socket.close();
	} catch (IOException e) {
		e.printStackTrace();
  	}

客户端代码例子(只是看一下创建过程):

import iotest.SafeBufferedReader;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateException;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
//import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import com.sun.net.ssl.SSLContext;
import com.sun.net.ssl.TrustManagerFactory;

public class HTTPSClient {

	public static void main(String[] args) {
		
		int port = 8443;
		String host = "localhost";
		
		try {
			
			X509TrustManager sunJsseX509TrustManager = null;
			KeyStore ks = KeyStore.getInstance("JKS");
			ks.load(new FileInputStream("D://tomcat//conf//server.jks"), "123456".toCharArray());
			TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509", "SunJSSE");
			tmf.init(ks);
			com.sun.net.ssl.TrustManager[] tms = tmf.getTrustManagers();
			
			for (int i = 0; i < tms.length; i++) {
				if (tms[i] instanceof X509TrustManager) {
					sunJsseX509TrustManager = (X509TrustManager)tms[i];
				}
			}
			
			SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
			sslContext.init(null, tms, new java.security.SecureRandom());
			
			SSLSocketFactory factory = sslContext.getSocketFactory();
			
//			SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
			SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
			
			//启用所有密码组
			String[] supported = socket.getSupportedCipherSuites();
			socket.setEnabledCipherSuites(supported);
			Writer out = new OutputStreamWriter(socket.getOutputStream());
			//Https在GET行中需要完整URL
			out.write("GET http://" + host + "/ HTTP/1.1\r\n");
			out.write("Host: " + host + "\r\n");
			out.flush();
			
//			InputStream in = new DataInputStream(socket.getInputStream());
//			int c;
//			while ((c = in.read()) != -1) {
//				System.out.print((char) c);
//			}
			
			//读取服务器的响应
			BufferedReader in = new SafeBufferedReader(new InputStreamReader(socket.getInputStream()));
			//读取首部
			String s;
			while (!(s = in.readLine()).equals("")) {
				System.out.println(s);
			}
			System.out.println("======================");
			
			//读取长度
			String contentLength = in.readLine();
			int length = Integer.MAX_VALUE;
			try {
				length = Integer.parseInt(contentLength.trim(), 16);
			} catch (NumberFormatException e) {
				//此服务器未在响应体的第一行中发送content-length(内容长度)
			}
			System.out.println(contentLength);
			
			int c;
			int i = 0;
			while ((c = in.read()) != -1 && i++ < length) {
				System.out.println(c);
			}
			
			System.out.println("====================");
			out.close();
			in.close();
			socket.close();
		} catch (IOException e) {
			System.err.println(e);
		} catch (KeyStoreException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (NoSuchAlgorithmException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (CertificateException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (NoSuchProviderException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (KeyManagementException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
	}
}

二、SSLSocket 类的方法

SSLSocket继承自java.net.Socket,SSLSocket还有用于配置如何进行认证和加密,1、选择密码组

进行何种认证和加密的方法。

JSSE支持认证和加密算法的不同组合。

例如:public abstract String[] getSupportedCipherSuites();

告诉指定的socket上可用的算法组合;

    Public abstract String[] getEnabledCipherSuites();

可以查看已经启用的加密算法组合;

  Public abstract String[] setEnabledCipherSuites(String[] suites);

可以选择想用哪些加密算法组合,这样可以取消掉一些加密强度不够高的算法组合。

算法组合举例:

SSL_RSA_WITH_AES_128_CBC_SHA

分为四部分:协议、密钥交换算法、加密算法和校验和。

及:SSL是通信协议;RSA非对称密钥算法是密钥交换算法,协商出来的一个结果是对称密钥的密钥;AES_128_CBC是加密算法,AES算法,分块一块是128SHA是摘要算法,校验算法。

SSL_DH_anon_WITH_RC4_128_MD5

anon表示没有身份认证,其他部分同上。

2、事件处理

JSSE使用标准事件模式,通知客户端和服务器端之间的握手何时完成。

HandshakeCompletedListener接口:

Public interface HandshakeCompletedListener extends java.util.EventListener

这个接口声明了handshakeCompleted(HandshakecompletedEvent event)

HandshakecompletedEvent 提供了获取时间有关信息的方法:

Public SSLSession getSession();

Public String getCipherSuite();

Public X509certificate[] getPeerCertificateChain();

Public SSLSocket getSocket();

通过SSLSocketaddHandshakeCompletedListener()removeHandshakeCompletedListener()方法,某个HandshakeCompletedListener可以注册对某个SSLSocket的握手结束时间的关注。

3、会话管理

会话有SSLSession接口的实例表示,可以检查会话的创建时间和最后访问时间、将会话作废、得到会话的各种有关信息等等。

如果想抛弃以前协商好的连接,启动一个新的会话,可以用startHandshake()方法。

4、客户端模式

setUseClientMode()方法确定socket是否需要在第一次握手时进行认证自己。这个方法,试用于客户端和服务器端。这个属性只能设置一次,第二次设置会抛出IllegalArgumentException异常。

另外,服务器端的安全Socket(即由SSLServerSocketaccept()方法返回的socket)可以设置是否要求验证客户端认证:setNeedClientAuth()。此方法不能在客户端试用,否则会抛异常。

5、创建安全的服务器Socket

安全客户端socket只是一般。另一半是启用SSL的服务器socket。他们是javax.net.SSLServerSocket类的实例。

SSLServerSocketFactory.getDefault()返回的工厂一般只支持服务器认证,不支持加密。

Sun的参考视线中,要有一个com.sun.net.ssl.SSLContext对象负责创建得到完整配置和初始化的安全服务器socket。基本过程如下:

l 使用keytool生成公开密钥和证书。

l 花钱请可信任的第三方认证你的证书。

l 为使用的算法创建SSLContext

l 为正在使用的证书源创建一个TrustManagerFactory

l 为正在使用的迷药类型创建一个KeyManagerFactory

l 为密钥和证书数据库创建一个KeyStore对象。(Sun的默认值是JKS

l 用密钥和证书填充KeyStore;例如使用加密所用的口令短语从文件系统中进行加载。

l 用KeyStore及其口令短语初始化KeyManagerFactory

l 用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 javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;

import com.sun.net.ssl.KeyManagerFactory;
import com.sun.net.ssl.SSLContext;

public class SecureOrderTaker {

	public final static int DEFAULT_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");
			
			//Sun的默认密钥库类型
			KeyStore ks = KeyStore.getInstance("JSK");
			//出于安全考虑,每个米要哭都必须用口令短语加密,
			//在从磁盘加载前必须提供口令。口令短语以char[]数组的形式存储,
			//所以可以很快的从内存中除去,而不是等待垃圾回收。
			char[] password = "123456".toCharArray();
			ks.load(new FileInputStream("D://server.jks"), password);
			kmf.init(ks, password);
			context.init(kmf.getKeyManagers(), null, null);
			
			SSLServerSocketFactory factory = context.getServerSocketFactory();
			SSLServerSocket server = (SSLServerSocket) factory.createServerSocket(DEFAULT_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);
			
			//现在所有设置都已经完成,可以集中进行实际痛心了。
			
			try {
				while(true) {
					Socket theConnection = server.accept();
					InputStream in = theConnection.getInputStream();
					int c;
					while ((c = in.read()) != -1) {
						System.out.write(c);
					}
					theConnection.close();
				}//inner while end
			} catch (IOException e) {
				System.err.println(e);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (KeyManagementException e) {
			e.printStackTrace();
		} catch (KeyStoreException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (java.security.cert.CertificateException e) {
			e.printStackTrace();
		} catch (UnrecoverableKeyException e) {
			e.printStackTrace();
		}
	}
	
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章