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();
		}
	}
	
}



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