zoukankan      html  css  js  c++  java
  • 学号 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);
    

    六、心得体会

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

    七、码云链接

    八、参考资料

  • 相关阅读:
    TCP Data Flow and Window Management(3)
    全渠道java b2b b2c o2o平台
    springmvc mybatis shiro ios android构建cms系统
    电子商务系统+java+web+完整项目+包含源码和数据库Java实用源码
    大型互联网 b2b b2c o2o 电子商务微服务云平台
    mybatis电子商务平台b2b2c
    spring mvc mybatis shiro构建cms系统ios android
    spring mvc+mybatis 构建 cms + 实现UC浏览器文章功能
    b2b b2c o2o电子商务微服务云平台
    java分布式电子商务云平台b2b b2c o2o需要准备哪些技术??
  • 原文地址:https://www.cnblogs.com/xiannvyeye/p/10928652.html
Copyright © 2011-2022 走看看