实验五 网络编程与安全

一、实验内容

任务一:

  1. 结对实现中缀表达式转后缀表达式的功能 MyBC.java
  2. 结对实现从上面功能中获取的表达式中实现后缀表达式求值的功能,调用MyDC.java

这个代码在之前四则运算的结对编程写过,基本上是一样的

代码:

MyBC的中缀转后缀的函数

    public void conversion(String expr) {   //中缀转后缀
        String token;
        StringTokenizer tokenizer = new StringTokenizer(expr);

        while (tokenizer.hasMoreTokens()) {
            //当tokenizer有下一个值时,进行循环,并把值赋给token
            token = tokenizer.nextToken();

            if (token.equals("(")) {
                //如果是左括号,入栈
                stack.push(token);
            }else if (token.equals("+") || token.equals("-")) {
                //如果是“+”或“-”,继续判断栈是否为空
                if (!stack.empty()){
                    //如果栈非空,判断栈顶元素是什么
                    if (stack.peek().equals("(")) {
                        //如果栈顶为“(”,运算符入栈
                        stack.push(token);
                    }else{
                        //否则先把栈顶元素移除,加到列表中,再将运算符入栈
                        list.add(stack.pop());
                        stack.push(token);
                    }
                }else {
                    //若栈为空,运算符入栈
                    stack.push(token);
                }
            }else if (token.equals("*") || token.equals("÷")){
                //如果是“*”或“÷”,继续判断栈是否为空
                if (!stack.empty()) {
                    //如果栈非空,判断栈顶元素是什么
                    if (stack.peek().equals("*") || stack.peek().equals("÷")) {
                        //如果栈顶为“*”或“÷”,先把栈顶元素移除,加到列表中,再将运算符入栈
                        list.add(stack.pop());
                        stack.push(token);
                    }else {
                        //如果栈顶为其他,运算符直接入栈
                        stack.push(token);
                    }
                }else {
                    //如果栈为空,运算符直接入栈
                    stack.push(token);
                }
            } else if (token.equals(")")) {
                //如果遇到“)”,开始循环
                while (true) {
                    //先把栈顶元素移除并赋给A
                    String A = stack.pop();
                    if (!A.equals("(")) {
                        //如果A不为“(”,则加到列表
                        list.add(A);
                    } else {
                        //如果A为“(”,退出循环
                        break;
                    }
                }
            }else {
                //如果为操作数,进入列表
                list.add(token);
            }
        }
        while (!stack.empty()) {
            //将栈中元素取出,加到列表中,直到栈为空
            list.add(stack.pop());
        }
        ListIterator<String> li = list.listIterator();//返回此列表元素的列表迭代器(按适当顺序)。
        while (li.hasNext()) {
            //将迭代器中的元素依次取出,并加上空格作为分隔符
            Message += li.next() + " ";
            li.remove();
        }
        message = Message;

    }

    public String getMessage() {
        return message;
    }
}

通过MyDCTester来进行测试检验

截图:

任务二:

结对编程:负责服务器

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

我负责的是服务器,首先要知道我的IP地址,通过InetAddress的静态变量getLocalHost()进行获取
代码

import java.net.*;
public class Address {
    public static void main(String[] args) throws UnknownHostException {
        InetAddress net = InetAddress.getLocalHost();
        System.out.println(net.toString());
    }
}

截图:

服务器,先定义一个监听的端口号,再创建ServerSocket对象绑定监听端口,用accept方法与客户端创建连接,若连接异常则弹出错误。接着在利用DataInputStream获取客户端输入的数据和内容,并调用MyDC中的evaluate方法处理数据进行计算,接着利用DataOutputStream将数据返回给客户端,并打印出来结果,如有异常则弹出错误。等到socket为空时,关闭服务器结束。

服务器Sever2代码:

/*初始化socket和输入输出等*/
        try {
            System.out.println("等待客户呼叫");
            socketOnServer = serverForClient.accept();
            out = new DataOutputStream(socketOnServer.getOutputStream());
            in = new DataInputStream(socketOnServer.getInputStream());
            String s = in.readUTF();
            System.out.println("服务器收到客户的提问:" + s);
            MyDC myDC = new MyDC();
            answer = myDC.evaluate(s);
            out.writeUTF(answer + "");
            Thread.sleep(500);
        } catch (Exception e) {
            System.out.println("客户已断开" + e);
        }

    }
}

截图:

任务三:

加密结对编程:负责服务器

  1. 把后缀表达式用3DES或AES算法加密后通过网络把密文发送给服务器
  2. 服务器接收到后缀表达式表达式后,进行解密(和客户端协商密钥,可以用数组保存),然后调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
  3. 其余与上任务同

实验步骤:首先运行Skey_DES.java,将在当前目录下生成key1.data保存产生的密钥。再运行Skey_kb.java,在当前目录下生成keykb1.dat保存上一步生成的密钥的编码。改写DES加密代码SEnc.java,新添一个String类型成员变量,新添一个接受字符串参数的构造方法,并把其主函数改成加密方法,方便在客户端代码中直接调用。运行客户端代码Client.java,在其中新建一个SEnc类实例,并调用该实例的加密方法,参数为后缀表达式;把密文文件和密钥编码文件发送给客户端,最后接收客户端返回的结果。

代码:

        try {
            System.out.println("等待客户呼叫");
            socketOnServer = serverForClient.accept(); //堵塞状态,除非有客户呼叫
            System.out.println("客户已连接");
            out = new DataOutputStream(socketOnServer.getOutputStream());
            in = new DataInputStream(socketOnServer.getInputStream());
            String leng = in.readUTF(); // in读取信息,堵塞状态
            byte ctext[] = new byte[Integer.parseInt(leng)];
            for (int i = 0;i<Integer.parseInt(leng);i++) {
                String temp = in.readUTF();
                ctext[i] = Byte.parseByte(temp);
            }
            // 获取密钥
            FileInputStream f2 = new FileInputStream("keykb1.dat");
            int num2 = f2.available();
            byte[] keykb = new byte[num2];
            f2.read(keykb);
            SecretKeySpec k = new SecretKeySpec(keykb, "DESede");
            // 解密
            Cipher cp = Cipher.getInstance("DESede");
            cp.init(Cipher.DECRYPT_MODE, k);
            byte[] ptext = cp.doFinal(ctext);
            System.out.println("");
            // 显示明文
            String p = new String(ptext,"UTF8");
            System.out.println("被解密的后缀表达式:" + p);
            System.out.println("计算后缀表达式" + p);
            out.writeUTF(mydc.evaluate(p)+"");
        } catch (Exception e) {
            System.out.println("客户已断开" + e);
        }
    }
}

截图:

任务四:

密钥分发结对编程:负责服务器

  1. 客户端和服务器用DH算法进行3DES或AES算法的密钥交换
  2. 服务器接收到后缀表达式表达式后,进行解密,然后调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
  3. 其余同上

甲为发送方,乙为接收方,甲乙双方构建密钥需要经过以下几个步骤:

  1. 由消息发送的一方构建密钥,这里由甲方构建密钥。
  2. 由构建密钥的一方向对方公布其公钥,这里由甲方向乙方发布公钥。
  3. 由消息接收的一方通过对方公钥构建自身密钥,这里由乙方使用甲方公钥构建乙方密钥。
  4. 由消息接收的一方向对方公布其公钥,这里由乙方向甲方公布公钥。
  5. 注意,乙方构建自己密钥对的时候需要使用甲方公钥作为参数这是很关键的一点

代码:

 try {
            System.out.println("等待客户呼叫");
            socketOnServer = serverForClient.accept(); //堵塞状态,除非有客户呼叫
            System.out.println("客户已连接");
            out = new DataOutputStream(socketOnServer.getOutputStream());
            in = new DataInputStream(socketOnServer.getInputStream());

            Key_DH.DH("Spub.dat","Spri.dat");
            int len = Integer.parseInt(in.readUTF());
            byte np[] = new byte[len];
            for (int i = 0;i<len;i++) {
                String temp = in.readUTF();
                np[i] = Byte.parseByte(temp);
            }
            ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (np));
            Key k2 = (Key)ois.readObject();;
            FileOutputStream f2 = new FileOutputStream("Cpub.dat");
            ObjectOutputStream b2 = new ObjectOutputStream(f2);
            b2.writeObject(k2);

            FileInputStream fp = new FileInputStream("Spub.dat");
            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();
            out.writeUTF(kb.length + "");
            for (int i = 0; i < kb.length; i++) {
                out.writeUTF(kb[i] + "");
            }

            KeyAgree.DH("Cpub.dat","Spri.dat");

            String leng = in.readUTF(); // in读取信息,堵塞状态
            byte ctext[] = new byte[Integer.parseInt(leng)];
            for (int i = 0;i<Integer.parseInt(leng);i++) {
                String temp = in.readUTF();
                ctext[i] = Byte.parseByte(temp);
            }
            // 获取密钥
            FileInputStream f = new FileInputStream("sb.dat");
            byte[] keysb = new byte[24];
            f.read(keysb);
            System.out.println("公共密钥:");
            for (int i = 0;i<24;i++) {
                System.out.print(keysb[i]+",");
            }
            System.out.println("");
            SecretKeySpec k = new SecretKeySpec(keysb, "DESede");
            // 解密
            Cipher cp = Cipher.getInstance("DESede");
            cp.init(Cipher.DECRYPT_MODE, k);
            byte[] ptext = cp.doFinal(ctext);
            System.out.println("");
            // 显示明文
            String p = new String(ptext,"UTF8");
            System.out.println("被解密的后缀表达式:" + p);
            System.out.println("计算后缀表达式" + p);
            out.writeUTF(mydc.evaluate(p)+"");
        } catch (Exception e) {
            System.out.println("客户已断开" + e);
        }
    }
}

截图:

任务五:

完整性校验结对编程:负责服务器

  1. 服务器接收到后缀表达式表达式后,进行解密,解密后计算明文的MD5值,和客户端传来的MD5进行比较,一致则调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
  2. 其余与上同

代码:

        try {
            System.out.println("等待客户呼叫");
            socketOnServer = serverForClient.accept(); //堵塞状态,除非有客户呼叫
            System.out.println("客户已连接");
            out = new DataOutputStream(socketOnServer.getOutputStream());
            in = new DataInputStream(socketOnServer.getInputStream());

            Key_DH.DH("Spub.dat","Spri.dat");
            int len = Integer.parseInt(in.readUTF());
            byte np[] = new byte[len];
            for (int i = 0;i<len;i++) {
                String temp = in.readUTF();
                np[i] = Byte.parseByte(temp);
            }
            ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream(np));
            Key k2 = (Key)ois.readObject();;
            FileOutputStream f2 = new FileOutputStream("Cpub.dat");
            ObjectOutputStream b2 = new ObjectOutputStream(f2);
            b2.writeObject(k2);

            FileInputStream fp = new FileInputStream("Spub.dat");
            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();
            out.writeUTF(kb.length + "");
            for (int i = 0; i < kb.length; i++) {
                out.writeUTF(kb[i] + "");
            }

            KeyAgree.DH("Cpub.dat","Spri.dat");

            String leng = in.readUTF(); // in读取信息,堵塞状态
            byte ctext[] = new byte[Integer.parseInt(leng)];
            for (int i = 0;i<Integer.parseInt(leng);i++) {
                String temp = in.readUTF();
                ctext[i] = Byte.parseByte(temp);
            }
            String check = in.readUTF();
            // 获取密钥
            FileInputStream f = new FileInputStream("sb.dat");
            byte[] keysb = new byte[24];
            f.read(keysb);
            System.out.println("公共密钥:");
            for (int i = 0;i<24;i++) {
                System.out.print(keysb[i]+",");
            }
            System.out.println("");
            SecretKeySpec k = new SecretKeySpec(keysb, "DESede");
            // 解密
            Cipher cp = Cipher.getInstance("DESede");
            cp.init(Cipher.DECRYPT_MODE, k);
            byte[] ptext = cp.doFinal(ctext);
            System.out.println("");
            // 显示明文
            String p = new String(ptext, "UTF8");
            String pMd5 = DigestPass.DP(p);
            System.out.println("被解密明文的MD5值:"+pMd5);
            if (pMd5.equals(check)){
                System.out.println("和客户端的MD5值一致");
                System.out.println("计算后缀表达式" + p);
                out.writeUTF(mydc.evaluate(p)+"");
            }
            else {
                System.out.println("警告:和客户端的MD5值不一致!");
            }
        } catch (Exception e) {
            System.out.println("客户已断开" + e);
        }
    }
}

截图:

6(选做)

用Android Studio实现

总结

整个任务其实就是一环紧扣一环的,一层一层递进从而实现最后的部分,密码学部分的实现参照参考资料。

参考资料

Java密码学算法