安全套接層(Secure Sockets Layer,SSL)是一種安全協定,在網景公司(Netscape)1994年推出首版Web浏覽器的同時提出,目的是為網絡通信提供安全及資料完整性保障,SSL在傳輸層中對網絡通信進行加密。
SSL協定的優勢在于它是與應用層協定獨立無關的。高層的應用層協定(例如:HTTP、FTP、Telnet等等)能透明的建立于SSL協定之上。SSL協定在應用層協定通信之前就已經完成加密算法、通信密鑰的協商以及伺服器認證工作。在此之後應用層協定所傳送的資料都會被加密,進而保證通信的私密性。
現在SSL3.0得到了普遍的使用,它的改進版TLS(傳輸層安全)已經成為網際網路标準。SSL本身和TCP套接字連接配接是很相似的,在協定棧中,SSL可以被簡單的看作是安全的TCP連接配接,但是某些TCP連接配接的特性它是不支援的,比如帶外資料(out-of-bound)。
在建構基于Socket的C/S程式時,通過添加對SSL的支援來保障資料安全和完整是不錯的方法。完善的Java為我們提供了簡單的實作方法:JSSE(Java安全套接字擴充)。JSSE是一個純Java實作的SSL和TLS協定架構,抽象了SSL和TLS複雜的算法,使安全問題變得簡單。JSSE已經成為J2SE1.4版本中的标準元件,支援SSL 3.0和TLS 1.0。我們将通過一個具體的例子示範JSSE的一些基本應用。例子中的伺服器端将打開一個SSL Socket,隻有持有指定證書的用戶端可以與它連接配接,所有的資料傳遞都是加密的。
構造一個SSLSocket是非常簡單的:
SSLServerSocketFactory factory=(SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
SSLServerSocket server = (SSLServerSocket) factory.createServerSocket(portNumber);
SSLSocket socket = (SSLSocket);
但是執行這樣的程式會産生一個異常,報告找不到可信任的證書。SSLSocket和普通的Socket是不一樣的,它需要一個證書來進行安全認證。
一、 證書
生成一個CA憑證,在指令行下執行:
keytool –genkey –keystore SSLKey –keyalg rsa –alias SSL
黑體部分是使用者可以自己指定的參數,第一個參數是要生成的證書的名字,第二個參數是證書的别名。rsa指明了我們使用的加密方法。
系統生成的檔案命将會和證書名相同。證書可以送出給權威CA認證組織稽核,如果通過稽核,組織會提供信任擔保,向客戶擔保你的連接配接是安全的。當然這不是必須的。在我們的例子中會把證書直接打包到用戶端程式中,保證用戶端是授權使用者,避免僞造客戶,是以不需要送出稽核。
二、 伺服器端
現在可以編寫伺服器端的代碼,與普通的Socket代碼不同,我們需要在程式中導入證書,并使用該證書構造SSLSocket。需要的說明的是:
●KeyStore ks=KeyStore.getInstance("JKS");
通路Java密鑰庫,JKS是keytool建立的Java密鑰庫,儲存密鑰。
● KeyManagerFactory kmf=KeyManagerFactory.getInstance("SunX509");
建立用于管理JKS密鑰庫的X.509密鑰管理器。
● SSLContext sslContext=SSLContext.getInstance("SSLv3");
構造SSL環境,指定SSL版本為3.0,也可以使用TLSv1,但是SSLv3更加常用。
●sslContext.init(kmf.getKeyManagers(),null,null);
初始化SSL環境。第二個參數是告訴JSSE使用的可信任證書的來源,設定為null是從javax.net.ssl.trustStore中獲得證書。第三個參數是JSSE生成的随機數,這個參數将影響系統的安全性,設定為null是個好選擇,可以保證JSSE的安全性。
完整代碼如下:
/*
*SSL Socket的伺服器端
*@Author Bromon
*/
package org.ec107.ssl;
import java.net.*;
import javax.net.ssl.*;
import java.io.*;
import java.security.*;
public class SSLServer
{
static int port=8266; //系統将要監聽的端口号,82.6.6是偶以前女朋友的生日^_^
static SSLServerSocket server;
/*
*構造函數
*/
public SSLServer()
{
}
/*
*@param port 監聽的端口号
*@return 傳回一個SSLServerSocket對象
*/
private static SSLServerSocket getServerSocket(int thePort)
{
SSLServerSocket s=null;
try
{
String key="SSLKey"; //要使用的證書名
char keyStorePass[]="12345678".toCharArray(); //證書密碼
char keyPassword[]="12345678".toCharArray(); //證書别稱所使用的主要密碼
KeyStore ks=KeyStore.getInstance("JKS"); //建立JKS密鑰庫
ks.load(new FileInputStream(key),keyStorePass);
//建立管理JKS密鑰庫的X.509密鑰管理器
KeyManagerFactory kmf=KeyManagerFactory.getInstance("SunX509");
kmf.init(ks,keyPassword);
SSLContext sslContext=SSLContext.getInstance("SSLv3");
sslContext.init(kmf.getKeyManagers(),null,null);
//根據上面配置的SSL上下文來産生SSLServerSocketFactory,與通常的産生方法不同
SSLServerSocketFactory factory=sslContext.getServerSocketFactory();
s=(SSLServerSocket)factory.createServerSocket(thePort);
}catch(Exception e)
{
System.out.println(e);
}
return(s);
}
public static void main(String args[])
{
try
{
server=getServerSocket(port);
System.out.println("在”+port+”端口等待連接配接...");
while(true)
{
SSLSocket socket=(SSLSocket)server.accept();
//将得到的socket交給CreateThread對象處理,主線程繼續監聽
new CreateThread(socket);
}
}catch(Exception e)
{
System.out.println("main方法錯誤80:"+e);
}
}
}
/*
*内部類,獲得主線程的socket連接配接,生成子線程來處理
*/
class CreateThread extends Thread
{
static BufferedReader in;
static PrintWriter out;
static Socket s;
/*
*構造函數,獲得socket連接配接,初始化in和out對象
*/
public CreateThread(Socket socket)
{
try
{
s=socket;
in=new BufferedReader(new InputStreamReader(s.getInputStream(),"gb2312"));
out=new PrintWriter(s.getOutputStream(),true);
start(); //開新線程執行run方法
}catch(Exception e)
{
System.out.println(e);
}
}
/*
*線程方法,處理socket傳遞過來的資料
*/
public void run()
{
try
{
String msg=in.readLine();
System.out.println(msg);
s.close();
}catch(Exception e)
{
System.out.println(e);
}
}
}
将我們剛才生成的證書放到程式所在的目錄下,上面的代碼就可以在編譯之後執行:
java org.ec107.ssl.SSLServer
在8266端口等待連接配接…
三、 用戶端
用戶端的代碼相對簡單,我們可以不在程式中指定SSL環境,而是在執行用戶端程式時指定。需要注意的是用戶端并沒有導入證書,而是采用了預設的工廠方法構造SSLSocket:
● SSLSocketFactory factory=(SSLSocketFactory)SSLSocketFactory.getDefault();
構造預設的工廠方法
●Socket s=factory.createSocket("localhost",port);
打開一個SSLSocket連接配接
/*
*SSL Socket 的用戶端
*@Author Bromon
*/
package org.ec107.ssl;
import java.net.*;
import javax.net.ssl.*;
import javax.net.*;
import java.io.*;
public class SSLClient
{
static int port=8266;
public static void main(String args[])
{
try
{
SSLSocketFactory factory=(SSLSocketFactory)SSLSocketFactory.getDefault();
Socket s=factory.createSocket("localhost",port);
PrintWriter out=new PrintWriter(s.getOutputStream(),true);
out.println("安全的說你好");
out.close();
s.close();
}catch(Exception e)
{
System.out.println(e);
}
}
}
把伺服器産生的證書(SSLKey)拷貝到程式所在的目錄,執行這個程式的時候需要向javax.net.ssl.trustStore環境變量傳入證書名:
java –Djavax.net.ssl.trustStore=SSLKey org.ec107.ssl.SSLClient
可以在伺服器的控制台看到用戶端發送過來的資料。
執行用戶端可以有另一種方法,把證書拷貝到java home/lib/security目錄下,名字改為jssecacerts,然後可以直接執行用戶端:
java org.ec107.ssl.SSLClient
程式會自動的到上述目錄下去尋找jssecacerts檔案作為預設的證書。需要注意的是這裡的java home并不是我們在安裝J2SE時指定的那個JAVA_HOME。可以執行一個程式來得到java home的位置:
public class GetJavaHome
{
public static void main(String args[])
{
System.out.println(System.getProperty(“java.home”));
}
}
一般情況下(windows 2K)hava home的位置是在C:Program FilesJavaj2re1.4.0_02,相對的,證書就應該拷貝到C:Program FilesJavaj2re1.4.0_02libsecurity下,如果安裝了自帶JDK的Java IDE,比如JBuilder,情況可能會有不同。
如果程式客戶在不持有證書的情況下直接進行連接配接,伺服器端會産生運作時異常,不允許進行連接配接。
運作環境:windows 2K server,j2sdk1.4.1