学号 20175313 《实验五 网络编程与安全》实验报告

一、实验内容

任务1:实现中缀表达式转后缀表达式,后缀表达式求值
0. 参考http://www.cnblogs.com/rocedu/p/6766748.html#SECDSA

  1. 结对实现中缀表达式转后缀表达式的功能 MyBC.java
  2. 结对实现从上面功能中获取的表达式中实现后缀表达式求值的功能,调用MyDC.java
  3. 上传测试代码运行结果截图和码云链接

任务2:通过客户端、服务器实现任务1
0. 注意责任归宿,要会通过测试证明自己没有问题

  1. 基于Java Socket实现客户端/服务器功能,传输方式用TCP
  2. 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式通过网络发送给服务器
  3. 服务器接收到后缀表达式,调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
  4. 客户端显示服务器发送过来的结果
  5. 上传测试结果截图和码云链接

任务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值,比对是否与客户端传来的值相同
0. 注意责任归宿,要会通过测试证明自己没有问题

  1. 基于Java Socket实现客户端/服务器功能,传输方式用TCP
  2. 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式用3DES或AES算法加密通过网络把密文和明文的MD5値发送给服务器
  3. 客户端和服务器用DH算法进行3DES或AES算法的密钥交换
  4. 服务器接收到后缀表达式表达式后,进行解密,解密后计算明文的MD5值,和客户端传来的MD5进行比较,一致则调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
  5. 客户端显示服务器发送过来的结果
  6. 上传测试结果截图和码云链接

任务6:将上述过程用Android实现,更多详情请参见学号 20175313 《实验五 网络编程与安全》实验报告番外篇

二、实验步骤

本次实验有5个小步,每一步都是建立在前一步的基础上进行的拓展。

三、关键代码解析

任务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);
  }

四、实验结果截图

任务1

  • MyBC类测试
  • MyDC类测试

任务2

  • 输入非法字符'a'
  • 连续输入三个操作数
  • 连续输入两个操作符
  • 缺少右括号
  • 缺少左括号
  • 除数为0
  • 正常情况

任务3

任务4

任务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);

六、心得体会

  • 本次实验就是在之前实验三的基础上加上了第十二章客户端、服务器的内容,看起来并不是很难,但是真正实践起来却还是出现了各种报错,总的来说还是要积极主动敲代码才能够真正学会,光靠看是永远学不会的。
  • 通过本次实验,也让我明白了写博客的好处,就是在自己忘记了前面学过的知识的时候,能够通过翻看自己的博客来唤起它们。

七、码云链接

八、参考资料

posted @ 2019-05-27 01:34  20175313张黎仙  阅读(392)  评论(0编辑  收藏  举报