標籤:
課程:Java程式設計實驗 班級:1353 姓名:符餘佳源 學號:20135321
成績: 指導教師:婁嘉鵬 實驗日期:2015.6.9
實驗密級:無 預習程度: 實驗時間:15:30~18:00
儀器組次: 21 必修/選修: 選修 實驗序號:5
實驗名稱:TCP傳輸及加解密
產品託管地址:http://git.shiyanlou.com/20135321/shiyanlou_cs212
實驗內容:
1.運行教材上TCP代碼,結對進行,一人伺服器,一人用戶端;
2.利用加解密程式碼封裝,編譯運行代碼,一人加密,一人解密;
3.整合代碼,一人加密後通過TCP發送;
註:加密使用AES或者DES/AES或者DES加密金鑰key並發送,使用伺服器的公開金鑰加密/公開金鑰演算法使用RSA或DH/檢驗發送資訊的完整性使用MD5或者SHA3;
4.完成Blog。
實驗儀器:
名稱 |
型號 |
數量 |
PC |
ACER |
1 |
虛擬機器 |
實驗樓 |
1 |
一.實驗步驟
(一)在進行實驗之前,我花出一定的時間瞭解了AES及DES演算法的相關內容,並再將JAVA的一些常用的語句進行了一定的回顧,為本次實驗奠定一定的基礎。
我(20135321)在本次實驗中主要負責伺服器端,我的搭檔符運錦(20135323)負責用戶端,他的部落格園地址為:http://www.cnblogs.com/20135323fuyunjin
(二)回顧TCP
伺服器端代碼:
package chapter9;
import java.io.*;
import java.net.*;
public class ServerTest {
public static final int PORT=8080;
public static void main(String[] args)throws IOException{
ServerSocket s=new ServerSocket(PORT);
System.out.println("Started:"+s);
try{
Socket socket=s.accept();
try{
System.out.println("Connection accepted:"+socket);
BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out=new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
while(true){
String str=in.readLine();
if(str.equals("END"))
break;
System.out.println("Echoing:"+str);
out.println(str);
}
}finally{
System.out.println("closing...");
socket.close();
}
}finally{
s.close();
}
}
}
用戶端代碼:
package chapter9;
import java.io.*;
import java.net.*;
public class ClientTest {
public static void main(String[] args)throws IOException{
InetAddress addr=InetAddress.getByName(null);
System.out.println("addr="+addr);
Socket socket=new Socket(addr,ServerTest.PORT);
try{
System.out.println("socket="+socket);
BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out=new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
for(int i=0;i<10;i++){
out.println("howdy"+i);
String str=in.readLine();
System.out.println(str);
}
out.println("END");
}finally{
System.out.println("closing...");
socket.close();
}
}
}
注意:要先運行伺服器端代碼再運行用戶端代碼
運行結果如下:
(三)前期知識準備
什麼是用戶端?
用戶端(Client)或稱為使用者端,是指與伺服器相對應,為客戶提供本地服務的程式。除了一些只在本地啟動並執行應用程式之外,一般安裝在普通的客戶機上,需要與服務端互相配合運行。網際網路發展以後,較常用的使用者端包括了如全球資訊網使用的網頁瀏覽器,收寄電子郵件時的電子郵件用戶端,以及即時通訊的用戶端軟體等。對於這一類應用程式,需要網路中有相應的伺服器和服務程式來提供相應的服務,如資料庫服務,電子郵件服務等等,這樣在客戶機和伺服器端,需要建立特定的通訊串連,來保證應用程式的正常運行。
原理:
用戶端及伺服端的關係不見得一定建立在兩台分開的機器上,同一台機器中也有這種主從關係的存在。提供服務的伺服端及接受服務的用戶端也有可能都在同一台機器上,例如我們在提供網頁的伺服器上執行瀏覽器瀏覽本機所提供的網頁,這樣在同一台機器上就同時扮演伺服端及用戶端。
什麼是伺服器端?
伺服器端(Server)是指在網路編程中被動等待串連的程式,伺服器端一般實現程式的核心邏輯以及資料存放區等核心功能。伺服器端的編程步驟和用戶端不同,是由四個步驟實現,依次是:
①監聽連接埠: 伺服器端屬於被動等待串連,所以伺服器端啟動以後,不需要發起串連,而只需要監聽本機電腦的某個固定連接埠即可。這個連接埠就是伺服器端開放給用戶端的連接埠,伺服器端程式啟動並執行本機電腦的IP地址就是伺服器端程式的IP地址。
②獲得串連:當用戶端串連到伺服器端時,伺服器端就可以獲得一個串連,這個串連包含用戶端的資訊,例如用戶端IP地址等等,伺服器端和用戶端也通過該串連進行資料交換。一般在伺服器端編程中,當獲得串連時,需要開啟專門的線程處理該串連,每個串連都由獨立的線程實現。
③交換資料:伺服器端通過獲得的串連進行資料交換。伺服器端的資料交換步驟是首先接收用戶端發送過來的資料,然後進行邏輯處理,再把處理以後的結果資料發送給用戶端。簡單來說,就是先接收再發送,這個和用戶端的資料交換數序不同。其實,伺服器端獲得的串連和用戶端串連是一樣的,只是資料交換的步驟不同。當然,伺服器端的資料交換也是可以多次進行的。
④關閉串連:當伺服器程式關閉時,需要關閉伺服器端,通過關閉伺服器端使得伺服器監聽的連接埠以及佔用的記憶體可以釋放出來,實現了串連的關閉。
什麼是TCP?
TCP(Transmission Control Protocol 傳輸控制通訊協定)是一種連線導向的、可靠的、基於位元組流的傳輸層通訊協定,由IETF的RFC 793定義。在簡化的電腦網路OSI模型中,它完成第四層傳輸層所指定的功能,使用者資料包通訊協定(UDP)是同一層內[1] 另一個重要的傳輸協議。在網際網路協議族(Internet protocol suite)中,TCP層是位於IP層之上,應用程式層之下的中介層。不同主機的應用程式層之間經常需要可靠的、像管道一樣的串連,但是IP層不提供這樣的流機制,而是提供不可靠的包交換。[1]
應用程式層向TCP層發送用於網間傳輸的、用8位位元組表示的資料流,然後TCP把資料流分區成適當長度的報文段(通常受該電腦串連的網路的資料連結層的傳輸單元最大值([1] MTU)的限制)。之後TCP把結果包傳給IP層,由它來通過網路將包傳送給接收端實體[1] 的TCP層。TCP為了保證不發生丟包,就給每個包一個序號,同時序號也保證了傳送到接收端實體的包的按序接收。然後接收端實體對已成功收到的包發回一個相應的確認(ACK);如果發送端實體在合理的往返時延(RTT)內未收到確認,那麼對應的資料包就被假設為已丟失將會被進行重傳。TCP用一個校正和函數來檢驗資料是否有錯誤;在發送和接收時都要計算校正和。
在資料正確性與合法性上,TCP用一個校正和函數來檢驗資料是否有錯誤,在發送和接收時都要計算校正和;同時可以使用md5認證對資料進行加密。
(四)具體操作
1.運行DES加密代碼
import java.io.*;
import javax.crypto.*;
public class Skey_DES{
public static void main(String args[]) throws Exception{
KeyGenerator kg=KeyGenerator.getInstance("DESede");
kg.init(168);
SecretKey k=kg.generateKey( );
FileOutputStream f=new FileOutputStream("key1.dat");
ObjectOutputStream b=new ObjectOutputStream(f);
b.writeObject(k);
}
}
設定加解密檔案
import java.io.*;
import java.security.*;
public class Skey_kb{
public static void main(String args[]) throws Exception{
FileInputStream f=new FileInputStream("key1.dat");
ObjectInputStream b=new ObjectInputStream(f);
Key k=(Key)b.readObject( );
byte[ ] kb=k.getEncoded( );
FileOutputStream f2=new FileOutputStream("keykb1.dat");
f2.write(kb);
// 列印密鑰編碼中的內容
for(int i=0;i<kb.length;i++){
System.out.print(kb[i]+",");
}
}
}
運行:
2.原理解釋:
(1)throws Exception {}//表示該方法可能產生異常,並使用throws聲明異常,//則該方法產生異常也不必捕獲
(2)例如想用“hello”作為密鑰利用DES進行加密和解密:
String keyString=”hello”;
byte[]keyData=keyString.getBytes();
secretKey myDeskey=new SecretKeySpec(keyData,”DES”);
(3) 擷取金鑰產生器
KeyGenerator kg=KeyGenerator.getInstance("DESede");
Java中KeyGenerator類中提供了建立對稱金鑰的方法。Java中的類一般使用new操作符通過構造器建立對象,但KeyGenerator類不是這樣,它預定義了一個靜態方法getInstance(),通過它獲得KeyGenerator類型的對象。這種類成為工廠類或工廠。
方法getInstance( )的參數為字串類型,指定密碼編譯演算法的名稱。
(4)初始化金鑰產生器
kg.init(168);
該步驟一般指定密鑰的長度。如果該步驟省略的話,會根據演算法自動使用預設的密鑰長度。指定長度時,若第一步金鑰產生器使用的是“DES”演算法,則密鑰長度必須是56位;若是“DESede”,則可以是112或168位,其中112位有效;若是“AES”,可以是128, 192或256位。
(5)產生密鑰SecretKey k=kg.generateKey( );
使用第一步獲得的KeyGenerator類型的對象中generateKey( )方法可以獲得密鑰。其類型為SecretKey類型,可用於以後的加密和解密。
(6)通過對象序列化方式將密鑰儲存在檔案中
FileOutputStream f=new FileOutputStream("key1.dat");
ObjectOutputStream b=new ObjectOutputStream(f); b.writeObject(k);
ObjectOutputStream類中提供的writeObject方法可以將對象序列化,以流的方式進行處理。這裡將檔案輸出資料流作為參數傳遞給ObjectOutputStream類的構造器,這樣建立好的密鑰將儲存在檔案key1.dat中。
(五)代碼實現
在本次實驗中,我們採取了一台電腦進行結對實驗。當運用一台電腦進行本次實驗室,只需要開出兩個CMD視窗即可。
伺服器代碼(20135321餘佳源):
import java.net.*;
import java.io.*;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
import java.security.interfaces.*;
import java.math.*;
public class ComputeTCPServer{
public static void main(String srgs[]) throws Exception {
ServerSocket sc = null;//空代表可以使用預設值
Socket socket=null;
try {
sc= new ServerSocket(2123);//建立伺服器通訊端
System.out.println("連接埠號碼:" + sc.getLocalPort());
System.out.println("伺服器已經啟動...");
socket = sc.accept(); //等待用戶端串連
System.out.println("已經建立串連");
//獲得網路輸入資料流對象的引用
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
////獲得網路輸出資料流對象的引用
PrintWriter out=new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
String aline2=in.readLine();//讀取用戶端傳送來的資料
BigInteger c=new BigInteger(aline2);
FileInputStream f=new FileInputStream("Skey_RSA_priv.dat");//從Skey_RSA_priv.dat中獲得輸入位元組
ObjectInputStream b=new ObjectInputStream(f);
RSAPrivateKey prk=(RSAPrivateKey)b.readObject( );//RSA 專用密鑰的介面
BigInteger d=prk.getPrivateExponent();//進行大數運算
BigInteger n=prk.getModulus();
BigInteger m=c.modPow(d,n);
byte[] keykb=m.toByteArray();
String aline=in.readLine();//讀取用戶端傳送來的資料
byte[] ctext=parseHexStr2Byte(aline);
Key k=new SecretKeySpec(keykb,"DESede");//使用SecretKeySpec類來根據一個位元組數組構造一個 SecretKey.
//僅對能表示為一個位元組數組並且沒有任何與之相關聯的鑰參數的原始密鑰有用,如,DES
Cipher cp=Cipher.getInstance("DESede");//加密和解密
cp.init(Cipher.DECRYPT_MODE, k);
byte []ptext=cp.doFinal(ctext);
String p=new String(ptext,"UTF8");
System.out.println("從用戶端接收到資訊為:"+p); //通過網路輸出資料流返回結果給用戶端
String aline3=in.readLine();
String x=p;
MessageDigest m2=MessageDigest.getInstance("MD5");//為應用程式提供資訊摘要演算法的功能,如 MD5演算法
//是單向雜湊函數,它接收任意大小的資料,並輸出固定長度的雜湊值。
m2.update(x.getBytes( ));
byte a[ ]=m2.digest( );
String result="";
for (int i=0; i<a.length; i++){
result+=Integer.toHexString((0x000000ff & a[i]) |
0xffffff00).substring(6);
}
System.out.println(result);
if(aline3.equals(result)){
System.out.println("匹配成功");
}
out.println("匹配成功");
out.close();
in.close();
sc.close();
} catch (Exception e) {
System.out.println(e);
}
}
public static String parseByte2HexStr(byte buf[]) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < buf.length; i++) {
String hex = Integer.toHexString(buf[i] & 0xFF);
if (hex.length() == 1) {
hex = ‘0‘ + hex;
}
sb.append(hex.toUpperCase());
}
return sb.toString();
}
public static byte[] parseHexStr2Byte(String hexStr) {
if (hexStr.length() < 1)
return null;
byte[] result = new byte[hexStr.length()/2];
for (int i = 0;i< hexStr.length()/2; i++) {
int high = Integer.parseInt(hexStr.substring(i*2, i*2+1 ), 16); //將String字元類型資料轉換為Integer整型資料
int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16);
result[i] = (byte) (high * 16 + low);
}
return result;
}
}
用戶端代碼(20135323符運錦):
import java.net.*;
import java.io.*;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.spec.*;
import javax.crypto.interfaces.*;
import java.security.interfaces.*;
import java.math.*;
public class ComputeTCPClient {
public static void main(String srgs[]) throws Exception{
try {
KeyGenerator kg=KeyGenerator.getInstance("DESede");//KeyGenerator類提供(對稱)金鑰產生器的功能
//金鑰產生器是使用此類的某個 getInstance 類方法構造的
//方法getInstance( )的參數為字串類型,指定密碼編譯演算法的名稱
kg.init(168); //初始化指定密鑰的長度
SecretKey k=kg.generateKey( );//秘密(對稱)密鑰。實現此介面的密鑰以其編碼格式返回字串,並返回作為 getEncoded 方法調用結果的原始密鑰位元組
byte[] ptext2=k.getEncoded();//設定一個位元組數組承載原始密鑰位元組
//建立串連特定伺服器的指定連接埠的Socket對象
Socket socket = new Socket("192.168.1.115", 2123);//該IP地址是寢室網IP
//獲得從伺服器端來的網路輸入資料流
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//獲得從用戶端向伺服器端輸出資料的網路輸出資料流
PrintWriter out=new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
//建立鍵盤輸入資料流,以便用戶端從鍵盤上輸入資訊
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
FileInputStream f3=new FileInputStream("Skey_RSA_pub.dat");
ObjectInputStream b2=new ObjectInputStream(f3);
RSAPublicKey pbk=(RSAPublicKey)b2.readObject( );
BigInteger e=pbk.getPublicExponent();
BigInteger n=pbk.getModulus();
BigInteger m=new BigInteger(ptext2);
BigInteger c=m.modPow(e,n);
String cs=c.toString( );
out.println(cs); //通過網路傳送到伺服器
System.out.print("請輸入待發送的資料:");
String s=stdin.readLine(); //從鍵盤讀入待發送的資料
Cipher cp=Cipher.getInstance("DESede");//DES演算法的解碼過程
cp.init(Cipher.ENCRYPT_MODE, k);//DES演算法的解碼過程
byte ptext[]=s.getBytes("UTF8");//DES演算法的解碼過程
byte ctext[]=cp.doFinal(ptext);//DES演算法的解碼過程
String str=parseByte2HexStr(ctext);//DES演算法的解碼過程
out.println(str); //通過網路傳送到伺服器
String x=s;
MessageDigest m2=MessageDigest.getInstance("MD5");//MessageDigest類為應用程式提供資訊摘要演算法的功能如MD5演算法
m2.update(x.getBytes( ));
byte a[ ]=m2.digest( );
String result="";
for (int i=0; i<a.length; i++){
result+=Integer.toHexString((0x000000ff & a[i]) |
0xffffff00).substring(6);////以十六進位(基數 16)不帶正負號的整數形式返回一個整數參數的字串表示形式。
}
System.out.println(result);
out.println(result);
str=in.readLine();//從網路輸入資料流讀取結果
System.out.println( "從伺服器接收到的結果為:"+str); //輸出伺服器返回的結果
}
catch (Exception e) {
System.out.println(e);
}
finally{
}
}
public static String parseByte2HexStr(byte buf[]) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < buf.length; i++) {
String hex = Integer.toHexString(buf[i] & 0xFF); //返回的字串表示的不帶正負號的整數參數所表示的值以十六進位(基數為16)
if (hex.length() == 1) {
hex = ‘0‘ + hex;
}
sb.append(hex.toUpperCase());
}
return sb.toString();
}
public static byte[] parseHexStr2Byte(String hexStr) {
if (hexStr.length() < 1)
return null;
byte[] result = new byte[hexStr.length()/2];
for (int i = 0;i< hexStr.length()/2; i++) {
int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16); //將String字元類型資料轉換為Integer整型資料
int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16); //將String字元類型資料轉換為Integer整型資料
result[i] = (byte) (high * 16 + low);
}
return result;
}
}
(六)整合
1.寢室網IP地址:
整合操作:
(七)PSP時間
步驟 |
耗時 |
百分比 |
需求分析 |
50min |
16.1% |
設計 |
60min |
19.3% |
代碼實現 |
120mn |
38.7% |
測試 |
20min |
6.5% |
分析總結 |
60min |
19.3% |
(八)遇到的問題
1.一開始並沒有想著建立兩台laptop之間的互用區域網路導致不能夠實現伺服器端和用戶端的互聯
解決方案:用電腦放出wifi或者手機開出熱點或者使用寢室裡的路由器進行局域傳輸。
2.在單獨一台laptop上進行類比實驗的時候,一開始沒有想到使用兩個CMD視窗來操作,單獨一個eclipse也不能實現得了伺服器端和用戶端的聯絡。
解決方案:經過符運錦的提醒點撥就想到了使用兩個CMD視窗,成功實現了類比互聯。
(九)實驗體會及感想
這是第四次java實驗,也是本學期最後一次的java實驗了。相對比於前三次的實驗,這次實驗更著重的是對代碼的整合和搭配,還有在一定程度上考察了我們對密碼學知識的理解。同樣的這是一次結對程式設計實驗,領航員和駕駛員都很重要,兩者也可以互換角色,這一點我深有體會,因為一開始我是覺得我可以勝任很大部分的代碼工作,到後來卻是符運錦同學把握點撥出了實驗的重點。不管如何,我們還是在實驗中收穫頗豐,組合代碼也是一個java程式員應該具備的技能,所以我感到很幸運。
Java程式設計實驗 實驗五