學號 20175313 《實驗五 網絡編程與安全》實驗報告

1、實驗內容

任務1:實現中綴表達式轉後綴表達式,後綴表達式求值java

  1. 參考http://www.cnblogs.com/rocedu/p/6766748.html#SECDSA
  2. 結對實現中綴表達式轉後綴表達式的功能 MyBC.java
  3. 結對實現從上面功能中獲取的表達式中實現後綴表達式求值的功能,調用MyDC.java
  4. 上傳測試代碼運行結果截圖和碼雲連接

任務2:經過客戶端、服務器實現任務1git

  1. 注意責任歸宿,要會經過測試證實本身沒有問題
  2. 基於Java Socket實現客戶端/服務器功能,傳輸方式用TCP
  3. 客戶端讓用戶輸入中綴表達式,而後把中綴表達式調用MyBC.java的功能轉化爲後綴表達式,把後綴表達式經過網絡發送給服務器
  4. 服務器接收到後綴表達式,調用MyDC.java的功能計算後綴表達式的值,把結果發送給客戶端
  5. 客戶端顯示服務器發送過來的結果
  6. 上傳測試結果截圖和碼雲連接

任務3:在任務2的基礎上,將客戶端產生的後綴表達式加密後傳給服務器正則表達式

  1. 注意責任歸宿,要會經過測試證實本身沒有問題
  2. 基於Java Socket實現客戶端/服務器功能,傳輸方式用TCP
  3. 客戶端讓用戶輸入中綴表達式,而後把中綴表達式調用MyBC.java的功能轉化爲後綴表達式,把後綴表達式用3DES或AES算法加密後經過網絡把密文發送給服務器
  4. 服務器接收到後綴表達式表達式後,進行解密(和客戶端協商密鑰,能夠用數組保存),而後調用MyDC.java的功能計算後綴表達式的值,把結果發送給客戶端
  5. 客戶端顯示服務器發送過來的結果
  6. 上傳測試結果截圖和碼雲連接

任務4:在任務3的基礎上,將用來加、解密的密鑰經過DH算法進行交換算法

  1. 注意責任歸宿,要會經過測試證實本身沒有問題
  2. 基於Java Socket實現客戶端/服務器功能,傳輸方式用TCP
  3. 客戶端讓用戶輸入中綴表達式,而後把中綴表達式調用MyBC.java的功能轉化爲後綴表達式,把後綴表達式用3DES或AES算法加密經過網絡把密文發送給服務器
  4. 客戶端和服務器用DH算法進行3DES或AES算法的密鑰交換
  5. 服務器接收到後綴表達式表達式後,進行解密,而後調用MyDC.java的功能計算後綴表達式的值,把結果發送給客戶端
  6. 客戶端顯示服務器發送過來的結果
  7. 上傳測試結果截圖和碼雲連接

任務5:在任務4的基礎上,將後綴表達式的摘要值傳給服務器,服務器經過解密後計算後綴表達式的MD5值,比對是否與客戶端傳來的值相同編程

  1. 注意責任歸宿,要會經過測試證實本身沒有問題
  2. 基於Java Socket實現客戶端/服務器功能,傳輸方式用TCP
  3. 客戶端讓用戶輸入中綴表達式,而後把中綴表達式調用MyBC.java的功能轉化爲後綴表達式,把後綴表達式用3DES或AES算法加密經過網絡把密文和明文的MD5値發送給服務器
  4. 客戶端和服務器用DH算法進行3DES或AES算法的密鑰交換
  5. 服務器接收到後綴表達式表達式後,進行解密,解密後計算明文的MD5值,和客戶端傳來的MD5進行比較,一致則調用MyDC.java的功能計算後綴表達式的值,把結果發送給客戶端
  6. 客戶端顯示服務器發送過來的結果
  7. 上傳測試結果截圖和碼雲連接

任務6:將上述過程用Android實現,更多詳情請參見學號 20175313 《實驗五 網絡編程與安全》實驗報告番外篇數組

2、實驗步驟

本次實驗有5個小步,每一步都是創建在前一步的基礎上進行的拓展。安全

3、關鍵代碼解析

任務1:中綴轉後綴,用後綴表達式規則進行計算服務器

  • 中綴轉後綴網絡

    • 若是遇到操做數就直接輸出。
    • 若是遇到操做符則將其放入棧中,遇到左括號也將其放入
      棧中。
    • 若是遇到右括號,則將棧元素彈出,將彈出的操做符輸出直到遇到遇到左括號爲止,注意左括號只彈出不輸出。
    • 若是遇到任何其餘操做符,如「+」,「-」,「*」,「÷」,「(」等,從棧中彈出元素直到遇到
      更低優先級的元素或棧空爲止。彈出完這些元素纔將遇到的操做符壓入棧中。
    • 若是讀到了輸入的末尾,則將棧中全部元素依次彈出。
    while (tokenizer.hasMoreTokens()){
              token=tokenizer.nextToken();
              if (isOperator(token)){
                  if (!OpStack.empty()){
                      if(judgeValue(token)>judgeValue(OpStack.peek()) && !token.equals(")") || token.equals("("))
                          OpStack.push(token);
                      else if (token.equals(")")){
                          //若是遇到一個右括號則將棧元素彈出,將彈出的操做符輸出直到遇到左括號爲止
                          while (!OpStack.peek().equals("("))
                              output=output.concat(OpStack.pop()+" ");//彈出左括號上面的全部東西
                          OpStack.pop();//彈出左括號
                      }
                      else {
                          while (!OpStack.empty() && judgeValue(token)<=judgeValue(OpStack.peek())){
                              ////若是遇到其餘任何操做符,從棧中彈出這些元素直到遇到發現更低優先級的元素或棧空爲止
                              output=output.concat(OpStack.pop()+" ");
                          }
                          OpStack.push(token);
                      }
                  }
                  else
                      OpStack.push(token);//若是棧空則直接將遇到的操做符送入棧中,第一個不可能爲右括號
              }
              else {
                  output=output.concat(token+" ");//若是遇到操做數就直接輸出
              }
          }
          while (!OpStack.empty()){
              //若是讀到了輸入分末尾,則將佔中全部元素依次彈出
              output=output.concat(OpStack.pop()+" ");
          }
  • 使用後綴表達式規則進行計算
  • 規則:從左到右遍歷表達式的每一個數字和符號,遇到是數字就進棧,遇到是符號,就將處於棧頂兩個數字出棧,進行運算,運算結果進棧,一直到最終得到結果。
while (tokenizer.hasMoreTokens()){
    token=tokenizer.nextToken();
        if(isOperator(token)){
        //遇到操做符,兩個操做數出棧
            op2=stack.pop();
            op1=stack.pop();
            result=calcSingle(op1,op2,token);//兩個操做數進行相關運算
                stack.push(new Integer(result));//運算結果進棧
            }
        else {
        //遇到數字進棧
                stack.push(new Integer(token));
            }
    }
  • 調用MyBC類的String getEquation(String s);方法將中綴表達式轉化爲後綴表達式
MyBC myBC = new MyBC();
output = myBC.getEquation(formula);
  • 調用MyDC類的String calculate(String s)方法將獲得的後綴表達式計算其結果
MyDC myDC = new MyDC();
result = myDC.calculate(formula);

任務2:客戶端輸入中綴表達式並轉化成後綴表達式發送給服務器,服務器計算後綴表達式,將結果發送給客戶端

  • 使用正則表達式判斷輸入的中綴表達式是否合法
String formula = scanner.nextLine();
    String regex = ".*[^0-9|+|\\-|*|÷|(|)|\\s|/].*";
    if(formula.matches(regex)){
        System.out.println("輸入了非法字符");
        System.exit(1);
    }

任務3:客戶端獲得的後綴表達式通過3DES或AES加密後將密文發送給服務端,服務端收到後將其進行解密,而後再計算後綴表達式的值,把結果發送給客戶端

  1. 獲取密鑰生成器:KeyGenerator kg=KeyGenerator.getInstance("AES");
  2. 初始化密鑰生成器:kg.init(128);
  3. 生成密鑰:SecretKey k=kg.generateKey( );
  4. 建立密碼器
    Cipher cp=Cipher.getInstance("AES");
  5. 初始化密碼器
    cp.init(Cipher.ENCRYPT_MODE,k);
  6. 執行加密
    byte []ctext=cp.doFinal(ptext);
  • 注意:這裏產生的密文是byte[]數組,可是傳送給服務器時所使用的參數應該是String類型,若是直接使用String s = new String(ctext);這種構造方法的話可能會出現javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher報錯。
  • 緣由:加密後的byte數組是不能強制轉換成字符串的,換言之:字符串和byte數組在這種狀況下不是互逆的;要避免這種狀況,咱們須要作一些修訂,能夠考慮將二進制數據轉換成十六進制表示。
  • 因此我這裏將ctext[]二進制轉化成十六進制傳送給服務器,服務器接收後再將十六進制轉爲二進制便可。(後面密鑰的傳送也是如此)
  • Client端代碼:
String out1 = B_H.parseByte2HexStr(ctext);
out.writeUTF(out1);
  • Server端代碼:
String cformula = in.readUTF();//讀取密文
byte cipher[] = H_B.parseHexStr2Byte(cformula);
  • 密鑰傳送過程:
    • 將SecretKey類型密鑰轉化爲byte[],後面就可採用與密文相同的方式傳送給服務器
      byte kb[] = k.getEncoded();
    • 將byte[]轉化爲SecretKeySpec,得到密鑰
      SecretKeySpec key = new SecretKeySpec(decryptKey,"AES");//得到密鑰

任務4:使用DH算法進行密鑰3DES或AES的密鑰交換

  • 建立密鑰對生成器:
    KeyPairGenerator kpg=KeyPairGenerator.getInstance("DH");
  • 初始化密鑰生成器
    kpg.initialize(1024);
  • 生成密鑰對
    KeyPair kp=kpg.genKeyPair( );
  • 獲取公鑰和私鑰
    PublicKey pbkey=kp.getPublic( );
    PrivateKey prkey=kp.getPrivate( );
  • 建立密鑰協定對象:KeyAgreement ka=KeyAgreement.getInstance("DH");
  • 初始化密鑰協定對象:ka.init(prk);
  • 執行密鑰協定:ka.doPhase(pbk,true);
  • 生成共享信息:byte[ ] sb=ka.generateSecret();
  • 建立密鑰:SecretKeySpec k=new SecretKeySpec(sb,"AES");

客戶端

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.security.Key;
import java.util.Scanner;
import java.net.*;
public class Client5_4 {
    public static void main(String[] args) {
        String mode = "AES";
        //客戶端讓用戶輸入中綴表達式,而後把中綴表達式調用MyBC.java的功能轉化爲後綴表達式,把後綴表達式經過網絡發送給服務器
        Scanner scanner = new Scanner(System.in);
        Socket mysocket;
        DataInputStream in = null;
        DataOutputStream out = null;
        try {
            mysocket = new Socket("127.0.0.1", 2010);
            in = new DataInputStream(mysocket.getInputStream());
            out = new DataOutputStream(mysocket.getOutputStream());
            System.out.println("請輸入要計算的題目:");
            String formula = scanner.nextLine();
            String regex = ".*[^0-9|+|\\-|*|÷|(|)|\\s|/].*";
            if (formula.matches(regex)) {
                System.out.println("輸入了非法字符");
                System.exit(1);
            }
            String output = "";
            MyBC myBC = new MyBC();
            try {
                //中綴轉後綴
                output = myBC.getEquation(formula);
            } catch (ExprFormatException e) {
                System.out.println(e.getMessage());
                System.exit(1);
            }
            //使用AES進行後綴表達式的加密
            KeyGenerator kg = KeyGenerator.getInstance(mode);
            kg.init(128);
            SecretKey k = kg.generateKey();//生成密鑰
            byte mkey[] = k.getEncoded();
            Cipher cp = Cipher.getInstance(mode);
            cp.init(Cipher.ENCRYPT_MODE, k);
            byte ptext[] = output.getBytes("UTF8");
            byte ctext[] = cp.doFinal(ptext);

            //將加密後的後綴表達式傳送給服務器
            String out1 = B_H.parseByte2HexStr(ctext);
            out.writeUTF(out1);

            //建立客戶端DH算法公、私鑰
            Key_DH.createPubAndPriKey("Clientpub.txt","Clientpri.txt");

            //將客戶端公鑰傳給服務器
            FileInputStream fp = new FileInputStream("Clientpub.txt");
            ObjectInputStream bp = new ObjectInputStream(fp);
            Key kp = (Key) bp.readObject();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(kp);
            byte[] kb = baos.toByteArray();
            String pop = B_H.parseByte2HexStr(kb);
            out.writeUTF(pop);
            Thread.sleep(1000);

            //接收服務器公鑰
            String push = in.readUTF();
            byte np[] = H_B.parseHexStr2Byte(push);
            ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (np));
            Key k2 = (Key)ois.readObject();;
            FileOutputStream f2 = new FileOutputStream("Serverpub.txt");
            ObjectOutputStream b2 = new ObjectOutputStream(f2);
            b2.writeObject(k2);

            //生成共享信息,並生成AES密鑰
            SecretKeySpec key = KeyAgree.createKey("Serverpub.txt", "Clientpri.txt");

            //對加密後綴表達式的密鑰進行加密,並傳給服務器
            cp.init(Cipher.ENCRYPT_MODE, key);
            byte ckey[] = cp.doFinal(mkey);
            String Key = B_H.parseByte2HexStr(ckey);
            out.writeUTF(Key);

            //接收服務器回答
            String s = in.readUTF();
            System.out.println("客戶收到服務器的回答:" + s);
        } catch (Exception e) {
            System.out.println("服務器已斷開" + e);
        }
    }
}

服務器

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.Key;

public class Server5_4 {
    public static void main(String[] args) {
        String mode = "AES";
        ServerSocket serverForClient = null;
        Socket socketOnServer = null;
        DataOutputStream out = null;
        DataInputStream in = null;
        try{
            serverForClient = new ServerSocket(2010);
        }catch (IOException e1){
            System.out.println(e1);
        }
        String result;
        try{
            System.out.println("等待客戶呼叫:");
            socketOnServer = serverForClient.accept();
            out = new DataOutputStream(socketOnServer.getOutputStream());
            in = new DataInputStream(socketOnServer.getInputStream());

            //接收加密後的後綴表達式
            String cformula = in.readUTF();
            byte cipher[] = H_B.parseHexStr2Byte(cformula);


            //接收Client端公鑰
            String push = in.readUTF();
            byte np[] = H_B.parseHexStr2Byte(push);

            //生成服務器共、私鑰
            Key_DH.createPubAndPriKey("Serverpub.txt","Serverpri.txt");

            //將服務器公鑰傳給Client端
            FileInputStream fp = new FileInputStream("Serverpub.txt");
            ObjectInputStream bp = new ObjectInputStream(fp);
            Key kp = (Key) bp.readObject();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(kp);
            byte[] kb = baos.toByteArray();
            String pop = B_H.parseByte2HexStr(kb);
            out.writeUTF(pop);
            Thread.sleep(1000);

            //生成共享信息,並生成AES密鑰
            SecretKeySpec key = KeyAgree.createKey("Serverpub.txt","Clientpri.txt");

            String k = in.readUTF();//讀取加密後密鑰
            byte[] encryptKey = H_B.parseHexStr2Byte(k);

            //對加密後密鑰進行解密
            Cipher cp = Cipher.getInstance(mode);
            cp.init(Cipher.DECRYPT_MODE,key);
            byte decryptKey [] = cp.doFinal(encryptKey);

            //對密文進行解密
            SecretKeySpec plainkey=new  SecretKeySpec(decryptKey,mode);
            cp.init(Cipher.DECRYPT_MODE, plainkey);
            byte []plain=cp.doFinal(cipher);

            //計算後綴表達式結果
            String formula = new String(plain);
            MyDC myDC = new MyDC();
            try{
                result = myDC.calculate(formula);
                //後綴表達式formula調用MyDC進行求值
            }catch (ExprFormatException e){
                result = e.getMessage();
            }catch (ArithmeticException e0){
                result = "Divide Zero Error";
            }
            //將計算結果傳給Client端
            out.writeUTF(result);
        }catch (Exception e){
            System.out.println("客戶已斷開"+e);
        }
    }
}

任務5:客戶端利用MD5算法計算其摘要值clientMD5傳送給服務器,服務器經過解密獲得的明文利用MD5算法計算其摘要值serverMD5,比較兩者是否相等

  • 生成MessageDigest對象
    MessageDigest m=MessageDigest.getInstance("MD5");
  • 傳入須要計算的字符串
    m.update(x.getBytes("UTF8" ));
    x爲須要計算的字符串
  • 計算消息摘要
    byte s[ ]=m.digest( );
  • 處理計算結果
String result="";
for (int i=0; i<s.length; i++){
       result+=Integer.toHexString((0x000000ff & s[i]) | 0xffffff00).substring(6);
  }

4、實驗結果截圖

任務1

  • MyBC類測試

  • MyDC類測試

任務2

  • 輸入非法字符'a'

  • 連續輸入三個操做數

  • 連續輸入兩個操做符

  • 缺乏右括號

  • 缺乏左括號

  • 除數爲0

  • 正常狀況

任務3

任務4

任務5

5、實驗過程當中遇到的問題及其解決方法

  • 使用byte []ctext=cp.doFinal(ptext);進行加密,產生的密文是byte[]數組,可是傳送給服務器時所使用的參數應該是String類型,若是直接使用String s = new String(ctext);這種構造方法會出現javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher報錯。

  • 緣由:加密後的byte數組是不能強制轉換成字符串的,換言之:字符串和byte數組在這種狀況下不是互逆的;要避免這種狀況,咱們須要作一些修訂,能夠考慮將二進制數據轉換成十六進制表示。

  • 解決方法:將ctext[]二進制轉化成十六進制傳送給服務器,服務器接收後再將十六進制轉爲二進制便可。
  • Client端代碼:
String out1 = B_H.parseByte2HexStr(ctext);
out.writeUTF(out1);
  • Server端代碼:
String cformula = in.readUTF();//讀取密文
byte cipher[] = H_B.parseHexStr2Byte(cformula);

6、心得體會

  • 本次實驗就是在以前實驗三的基礎上加上了第十二章客戶端、服務器的內容,看起來並非很難,可是真正實踐起來卻仍是出現了各類報錯,總的來講仍是要積極主動敲代碼纔可以真正學會,光靠看是永遠學不會的。
  • 經過本次實驗,也讓我明白了寫博客的好處,就是在本身忘記了前面學過的知識的時候,可以經過翻看本身的博客來喚起它們。

7、碼雲連接

8、參考資料

相關文章
相關標籤/搜索