bromon原創(chuàng)?版權(quán)所有
?SSL(安全套接層)是Netscape公司在1994年開發(fā)的,最初用于WEB瀏覽器,為瀏覽器與服務(wù)器間的數(shù)據(jù)傳遞提供安全保障,提供了加密、來源認(rèn)證和數(shù)據(jù)完整性的功能。現(xiàn)在SSL3.0得到了普遍的使用,它的改進(jìn)版TLS(傳輸層安全)已經(jīng)成為互聯(lián)網(wǎng)標(biāo)準(zhǔn)。SSL本身和TCP套接字連接是很相似的,在協(xié)議棧中,SSL可以被簡單的看作是安全的TCP連接,但是某些TCP連接的特性它是不支持的,比如帶外數(shù)據(jù)(out-of-bound)。
?在構(gòu)建基于Socket的C/S程序時(shí),通過添加對(duì)SSL的支持來保障數(shù)據(jù)安全和完整是不錯(cuò)的方法。完善的Java為我們提供了簡單的實(shí)現(xiàn)方法:JSSE(Java安全套接字?jǐn)U展)。JSSE是一個(gè)純Java實(shí)現(xiàn)的SSL和TLS協(xié)議框架,抽象了SSL和TLS復(fù)雜的算法,使安全問題變得簡單。JSSE已經(jīng)成為J2SE1.4版本中的標(biāo)準(zhǔn)組件,支持SSL?3.0和TLS?1.0。我們將通過一個(gè)具體的例子演示JSSE的一些基本應(yīng)用。例子中的服務(wù)器端將打開一個(gè)SSL?Socket,只有持有指定證書的客戶端可以與它連接,所有的數(shù)據(jù)傳遞都是加密的。
?構(gòu)造一個(gè)SSLSocket是非常簡單的:
?SSLServerSocketFactory?factory=(SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
?SSLServerSocket?server?=?(SSLServerSocket)?factory.createServerSocket(portNumber);
?SSLSocket?socket?=?(SSLSocket);
?但是執(zhí)行這樣的程序會(huì)產(chǎn)生一個(gè)異常,報(bào)告找不到可信任的證書。SSLSocket和普通的Socket是不一樣的,它需要一個(gè)證書來進(jìn)行安全認(rèn)證。
?一、?證書
?生成一個(gè)CA證書,在命令行下執(zhí)行:
?keytool?–genkey?–keystore?SSLKey?–keyalg?rsa?–alias?SSL
?黑體部分是用戶可以自己指定的參數(shù),第一個(gè)參數(shù)是要生成的證書的名字,第二個(gè)參數(shù)是證書的別名。rsa指明了我們使用的加密方法。
?系統(tǒng)會(huì)要求輸入證書發(fā)放者的信息,逐項(xiàng)輸入即可,如下圖:
??
???系統(tǒng)生成的文件命將會(huì)和證書名相同。證書可以提交給權(quán)威CA認(rèn)證組織審核,如果通過審核,組織會(huì)提供信任擔(dān)保,向客戶擔(dān)保你的連接是安全的。當(dāng)然這不是必須的。在我們的例子中會(huì)把證書直接打包到客戶端程序中,保證客戶端是授權(quán)用戶,避免偽造客戶,所以不需要提交審核。
?二、?服務(wù)器端
?現(xiàn)在可以編寫服務(wù)器端的代碼,與普通的Socket代碼不同,我們需要在程序中導(dǎo)入證書,并使用該證書構(gòu)造SSLSocket。需要的說明的是:
?●KeyStore?ks=KeyStore.getInstance("JKS");
?訪問Java密鑰庫,JKS是keytool創(chuàng)建的Java密鑰庫,保存密鑰。
?●?KeyManagerFactory?kmf=KeyManagerFactory.getInstance("SunX509");
?創(chuàng)建用于管理JKS密鑰庫的X.509密鑰管理器。
?●?SSLContext?sslContext=SSLContext.getInstance("SSLv3");
?構(gòu)造SSL環(huán)境,指定SSL版本為3.0,也可以使用TLSv1,但是SSLv3更加常用。
?●sslContext.init(kmf.getKeyManagers(),null,null);
?初始化SSL環(huán)境。第二個(gè)參數(shù)是告訴JSSE使用的可信任證書的來源,設(shè)置為null是從javax.net.ssl.trustStore中獲得證書。第三個(gè)參數(shù)是JSSE生成的隨機(jī)數(shù),這個(gè)參數(shù)將影響系統(tǒng)的安全性,設(shè)置為null是個(gè)好選擇,可以保證JSSE的安全性。
?完整代碼如下:
??1
/**//*
??2
?*SSL?Socket的服務(wù)器端
??3
?*@Author?Bromon
??4
?*/
??5
??6
?package?org.ec107.ssl;
??7
??8
?import?java.net.*;
??9
?import?javax.net.ssl.*;
?10
?import?java.io.*;
?11
?import?java.security.*;
?12
?13
?public?class?SSLServer
?14
?
{
?15
??static?int?port=8266;??//系統(tǒng)將要監(jiān)聽的端口號(hào),82.6.6是偶以前女朋友的生日^_^
?16
??static?SSLServerSocket?server;
?17
??
?18
??/**//*
?19
??*構(gòu)造函數(shù)
?20
??*/
?21
??
?22
??public?SSLServer()
?23
??
{
?24
???
?25
??}
?26
??
?27
??
?28
??/**//*
?29
??*@param?port?監(jiān)聽的端口號(hào)
?30
??*@return?返回一個(gè)SSLServerSocket對(duì)象
?31
??*/
?32
??
?33
??private?static?SSLServerSocket?getServerSocket(int?thePort)
?34
??
{
?35
???SSLServerSocket?s=null;
?36
???try
?37
???
{
?38
????String?key="SSLKey";??//要使用的證書名
?39
?40
????char?keyStorePass[]="12345678".toCharArray();??//證書密碼
?41
?42
????char?keyPassword[]="12345678".toCharArray();??//證書別稱所使用的主要密碼
?43
?44
????KeyStore?ks=KeyStore.getInstance("JKS");??//創(chuàng)建JKS密鑰庫
?45
?46
????ks.load(new?FileInputStream(key),keyStorePass);
?47
?48
????//創(chuàng)建管理JKS密鑰庫的X.509密鑰管理器
?49
????KeyManagerFactory?kmf=KeyManagerFactory.getInstance("SunX509");
?50
?51
????kmf.init(ks,keyPassword);
?52
?53
????SSLContext?sslContext=SSLContext.getInstance("SSLv3");
?54
?55
????sslContext.init(kmf.getKeyManagers(),null,null);
?56
??
?57
????//根據(jù)上面配置的SSL上下文來產(chǎn)生SSLServerSocketFactory,與通常的產(chǎn)生方法不同
?58
????SSLServerSocketFactory?factory=sslContext.getServerSocketFactory();
?59
?60
????s=(SSLServerSocket)factory.createServerSocket(thePort);
?61
?62
???}catch(Exception?e)
?63
???
{
?64
????System.out.println(e);
?65
???}
?66
???return(s);
?67
??}
?68
??
?69
??
?70
??public?static?void?main(String?args[])
?71
??
{
?72
???try
?73
???
{
?74
????server=getServerSocket(port);
?75
????System.out.println("在”+port+”端口等待連接
");
?76
?77
????while(true)
?78
????
{
?79
?????SSLSocket?socket=(SSLSocket)server.accept();
?80
?????
?81
?????//將得到的socket交給CreateThread對(duì)象處理,主線程繼續(xù)監(jiān)聽
?82
?????new?CreateThread(socket);
?83
?????
?84
????}
?85
???}catch(Exception?e)
?86
???
{
?87
????System.out.println("main方法錯(cuò)誤80:"+e);
?88
???}
?89
??}
?90
?}
?91
?92
?/**//*
?93
?*內(nèi)部類,獲得主線程的socket連接,生成子線程來處理
?94
?*/
?95
?96
?class?CreateThread?extends?Thread
?97
?
{
?98
??static?BufferedReader?in;
?99
??static?PrintWriter?out;
100
??static?Socket?s;
101
??
102
??/**//*
103
??*構(gòu)造函數(shù),獲得socket連接,初始化in和out對(duì)象
104
??*/
105
??
106
??public?CreateThread(Socket?socket)
107
??
{
108
???try
109
???
{
110
????s=socket;
111
????in=new?BufferedReader(new?InputStreamReader(s.getInputStream(),"gb2312"));
112
113
????out=new?PrintWriter(s.getOutputStream(),true);
114
115
????start();??//開新線程執(zhí)行run方法
116
117
???}catch(Exception?e)
118
???
{
119
????System.out.println(e);
120
???}
121
???
122
??}
123
??
124
??/**//*
125
??*線程方法,處理socket傳遞過來的數(shù)據(jù)
126
??*/
127
??
128
??public?void?run()
129
??
{
130
???try
131
???
{
132
????String?msg=in.readLine();
133
????System.out.println(msg);
134
????s.close();
135
???}catch(Exception?e)
136
???
{
137
????System.out.println(e);
138
???}
139
??}
140
?}
141
142
??將我們剛才生成的證書放到程序所在的目錄下,上面的代碼就可以在編譯之后執(zhí)行:
?java?org.ec107.ssl.SSLServer
?在8266端口等待連接…
?三、?客戶端
?客戶端的代碼相對(duì)簡單,我們可以不在程序中指定SSL環(huán)境,而是在執(zhí)行客戶端程序時(shí)指定。需要注意的是客戶端并沒有導(dǎo)入證書,而是采用了默認(rèn)的工廠方法構(gòu)造SSLSocket:
?●?SSLSocketFactory?factory=(SSLSocketFactory)SSLSocketFactory.getDefault();
?構(gòu)造默認(rèn)的工廠方法
?●Socket?s=factory.createSocket("localhost",port);
?打開一個(gè)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);
???}
??}
?}


?把服務(wù)器產(chǎn)生的證書(SSLKey)拷貝到程序所在的目錄,執(zhí)行這個(gè)程序的時(shí)候需要向javax.net.ssl.trustStore環(huán)境變量傳入證書名:
?java?–Djavax.net.ssl.trustStore=SSLKey?org.ec107.ssl.SSLClient
?可以在服務(wù)器的控制臺(tái)看到客戶端發(fā)送過來的數(shù)據(jù)。
?執(zhí)行客戶端可以有另一種方法,把證書拷貝到j(luò)ava?home/lib/security目錄下,名字改為jssecacerts,然后可以直接執(zhí)行客戶端:
?java?org.ec107.ssl.SSLClient
?程序會(huì)自動(dòng)的到上述目錄下去尋找jssecacerts文件作為默認(rèn)的證書。需要注意的是這里的java?home并不是我們在安裝J2SE時(shí)指定的那個(gè)JAVA_HOME。可以執(zhí)行一個(gè)程序來得到j(luò)ava?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,相對(duì)的,證書就應(yīng)該拷貝到C:Program?FilesJavaj2re1.4.0_02libsecurity下,如果安裝了自帶JDK的Java?IDE,比如JBuilder,情況可能會(huì)有不同。
???如果程序客戶在不持有證書的情況下直接進(jìn)行連接,服務(wù)器端會(huì)產(chǎn)生運(yùn)行時(shí)異常,不允許進(jìn)行連接。
?運(yùn)行環(huán)境:windows?2K?server,j2sdk1.4.1