2018-2019-2 20175224 实验五《网络编程与安全》实验报告

实验内容及步骤

任务一:两人一组结对编程:

  1. 参考http://www.cnblogs.com/rocedu/p/6766748.html#SECDSA
  2. 结对实现中缀表达式转后缀表达式的功能 MyBC.java
  3. 结对实现从上面功能中获取的表达式中实现后缀表达式求值的功能,调用MyDC.java
  4. 上传测试代码运行结果截图和码云链接

实现中缀表达式转后缀表达式

import java.util.Stack;
import java.util.StringTokenizer;

public class MyBC {
    /** constant for addition symbol */
    private final char ADD = '+';
    /** constant for subtraction symbol */
    private final char SUBTRACT = '-';
    /** constant for multiplication symbol */
    private final char MULTIPLY = '*';
    /** constant for division symbol */
    private final char DIVIDE = '/';
    /** the stack */
    Stack<Integer> stack=new Stack<Integer>();;

    String expression;
    public void setExpression(String str) {
        expression=str;
    }
    public  String changedWay() {
        String changedExpression = "";
        Stack signStack = new Stack();// 操作符栈
        for (int i = 0; i < expression.length(); i++) {
            char c = expression.charAt(i);
            if (c >= '0' && c <= '9') {
                changedExpression=changedExpression+c;
            }
            else if (c == '+' || c == '-' || c == '*' || c == '/') {
                changedExpression=changedExpression+" ";//分隔数字
                if (signStack.empty()) {
                    signStack.push(c);
                }
                else if (judgeValue(c) >= judgeValue((Character) signStack.peek())) {//优先级高于或等于,运算符号均进栈
                    signStack.push(c);
                }
                else {
                    changedExpression=changedExpression+(char)signStack.pop();
                    signStack.push(c);
                }
            }
            else if (c=='(') {
                signStack.push(c);
            }
            else if (c==')') {
                while((char)signStack.peek()!='(') {
                    changedExpression=changedExpression+" "+signStack.pop();
                }
                signStack.pop();
            }
        }
        while(!signStack.empty()){
            changedExpression=changedExpression+" "+String.valueOf(signStack.pop());
        }
        return changedExpression;
    }

    private static int judgeValue(char c) {
        int value = 0;
        switch (c) {
            case '(':
                value = 1;
                break;
            case '+':
            case '-':
                value = 2;
                break;
            case '*':
            case '/':
                value = 3;
                break;
            case ')':
                value = 4;
            default:
                value = 0;
        }
        return value;
    }
    public int evaluate (String expr)
    {//后缀表达式的运算方法
        int op1, op2, result = 0;
        String token;
        StringTokenizer tokenizer = new StringTokenizer (expr);//使用StringTokenizer类分解String对象的字符序列,默认为空格符...
        //此时tokenizer为一个分析器
        while (tokenizer.hasMoreTokens()) {
            token = tokenizer.nextToken();
            if (isOperator(token))
            {
                op2 = (stack.pop()).intValue();//出栈
                op1 = (stack.pop()).intValue();//出栈
                result = evalSingleOp (token.charAt(0), op1, op2);//String对象第一个字符转换为char类型的方法为:str.charAt(0)
                stack.push (new Integer(result));//进栈
            }
            else {
                stack.push(new Integer(Integer.parseInt(token)));//进栈
            }
        }
        return result;
    }
    private boolean isOperator (String token)
    {
        return ( token.equals("+") || token.equals("-") ||
                token.equals("*") || token.equals("/") );
    }
    private int evalSingleOp (char operation, int op1, int op2)
    {
        int result = 0;
        switch (operation)
        {
            case ADD:
                result = op1 + op2;
                break;
            case SUBTRACT:
                result = op1 - op2;
                break;
            case MULTIPLY:
                result = op1 * op2;
                break;
            case DIVIDE:
                result = op1 / op2;
        }
        return result;
    }
}
  • 实现截图为:

任务二:两人一组结对编程:

  1. 参考http://www.cnblogs.com/rocedu/p/6766748.html#SECDSA
  2. 结对实现中缀表达式转后缀表达式的功能 MyBC.java
  3. 结对实现从上面功能中获取的表达式中实现后缀表达式求值的功能,调用MyDC.java
  4. 上传测试代码运行结果截图和码云链接
  • 获取本机IP地址:DESKTOP-HN1B8O2/172.30.2.248
netAddress address_3=InetAddress.getLocalHost();
System.out.println(address_3.toString());
  • 结对伙伴的IP为:172.30.4.50 所选端口为:1111
  • 负责客服端的构建,实现代码为:
  • 选用AES加密算法
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.*;

public class Client_2Socket {
    public static void main(String[] args) {
        Socket Client_2Socket;
        DataInputStream Client_2in=null;
        DataOutputStream Client_2out=null;
        String expr,str;
        Scanner scanner=new Scanner(System.in);
        System.out.println("请输入表达式:");
        str=scanner.nextLine();
        MyBC mybc=new MyBC();
        mybc.setExpression(str);
        expr=mybc.changedWay();
        try {
            Client_2Socket=new Socket("172.30.2.248",5353);
            Client_2in=new DataInputStream(Client_2Socket.getInputStream());
            Client_2out=new DataOutputStream(Client_2Socket.getOutputStream());
            Client_2out.writeUTF(expr);
            String s=Client_2in.readUTF();
            System.out.println("服务器回复:
"+s);
        }
        catch (Exception e) {
            System.out.println("服务器已断开"+e);
        }
    }
}
  • 结对伙伴负责的服务器端代码为:
import java.io.*;
import java.net.*;
import java.util.*;

public class Server_2 {
    public static void main(String[] args) {
        ServerSocket Server_2forClient_2=null;
        Socket SocketOnServer_2=null;
        DataOutputStream Server_2out=null;
        DataInputStream Server_2in=null;
        try {
            Server_2forClient_2=new ServerSocket(5353);
        }
        catch (IOException e1) {
            System.out.println(e1);
            //e1.printStackTrace();
        }
        try {
            System.out.println("等待客户端呼叫……");
            SocketOnServer_2=Server_2forClient_2.accept();
            Server_2out=new DataOutputStream(SocketOnServer_2.getOutputStream());
            Server_2in=new DataInputStream(SocketOnServer_2.getInputStream());
            String expr=Server_2in.readUTF();
            System.out.println("服务器接收到表达式:"+expr);
            int result;
            MyBC mybc=new MyBC();
            result=mybc.evaluate(expr);
            Server_2out.writeUTF("后缀表达式:"+expr+",运算结果为:"+result);
            Thread.sleep(500);
        }
        catch (Exception e2) {
            System.out.println("客户端已断开"+e2);
        }
    }
}
  • 实验截图

任务三:加密结对编程:1人负责客户端,一人负责服务器

  1. 注意责任归宿,要会通过测试证明自己没有问题
  2. 基于Java Socket实现客户端/服务器功能,传输方式用TCP
  3. 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式用3DES或AES算法加密后通过网络把密文发送给服务器
  4. 服务器接收到后缀表达式表达式后,进行解密(和客户端协商密钥,可以用数组保存),然后调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
  5. 客户端显示服务器发送过来的结果
  6. 上传测试结果截图和码云链接
  • 思路:密文通过TCP传输,而AES密钥通过机密通道传输。
  • 负责客服端的构建,代码为:
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.*;

public class Client_3 {
    public static void main(String[] args) {
        Socket Client_2Socket;
        DataInputStream Client_2in=null;
        DataOutputStream Client_2out=null;
        String expr=null;
        String str=null;
        String Ciphertext=null;
        Scanner scanner=new Scanner(System.in);
        System.out.println("请输入表达式:");
        str=scanner.nextLine();
        MyBC mybc=new MyBC();
        mybc.setExpression(str);
        expr=mybc.changedWay();
        try {
            AES.produceAESKey();//生成AES密钥
            byte[]cc= AES.EncryptionAES(expr);//需要传输的密文,数组形式传输。
            Ciphertext = Base64.getEncoder().encodeToString(cc);//将加密后的密文由byte[]转换为String类型
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            Client_2Socket=new Socket("172.30.2.248",5300);
            Client_2in=new DataInputStream(Client_2Socket.getInputStream());
            Client_2out=new DataOutputStream(Client_2Socket.getOutputStream());
            Client_2out.writeUTF(Ciphertext);
            String s=Client_2in.readUTF();
            System.out.println("服务器回复:
"+s);
        }
        catch (Exception e) {
            System.out.println("服务器已断开"+e);
        }
    }
}
  • 结对伙伴负责构建服务器,代码为:
import sun.security.krb5.internal.crypto.Aes128;

import java.io.*;
import java.net.*;
import java.util.*;

public class Server_3 {
    public static void main(String[] args) {
        ServerSocket Server_2forClient_2=null;
        Socket SocketOnServer_2=null;
        DataOutputStream Server_2out=null;
        DataInputStream Server_2in=null;
        try {
            Server_2forClient_2=new ServerSocket(5300);
        }
        catch (IOException e1) {
            System.out.println(e1);
        }
        try {
            System.out.println("等待客户端呼叫……");
            SocketOnServer_2=Server_2forClient_2.accept();
            Server_2out=new DataOutputStream(SocketOnServer_2.getOutputStream());
            Server_2in=new DataInputStream(SocketOnServer_2.getInputStream());
            String Ciphertext=Server_2in.readUTF();//密文
            byte[] data= Base64.getDecoder().decode(Ciphertext);
            String expr= AES.DecryptionAES(data);
            System.out.println("服务器接收到表达式:"+expr);
            int result;
            MyBC mybc=new MyBC();
            result=mybc.evaluate(expr);
            Server_2out.writeUTF("后缀表达式:"+expr+",运算结果为:"+result);
            Thread.sleep(500);
        }
        catch (Exception e2) {
            System.out.println("客户端已断开"+e2);
        }
    }
}
  • 实验截图

任务四:密钥分发结对编程:1人负责客户端,一人负责服务器

  1. 注意责任归宿,要会通过测试证明自己没有问题
  2. 基于Java Socket实现客户端/服务器功能,传输方式用TCP
  3. 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式用3DES或AES算法加密通过网络把密文发送给服务器
  4. 客户端和服务器用DH算法进行3DES或AES算法的密钥交换
  5. 服务器接收到后缀表达式表达式后,进行解密,然后调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
  6. 客户端显示服务器发送过来的结果
  7. 上传测试结果截图和码云链接

DH算法相关链接:

  • DH算法原理
  • 密钥交换算法DH(Java实现)

  • 密钥交换实现过程:
    1. 由消息发送的一方构建密钥,这里由甲方构建密钥。
    2. 由构建密钥的一方向对方公布其公钥,这里由甲方向乙方发布公钥。
    3. 由消息接收的一方通过对方公钥构建自身密钥,这里由乙方使用甲方公钥构建乙方密钥。
    4. 由消息接收的一方向对方公布其公钥,这里由乙方向甲方公布公钥。
  • 使用密钥协定创建共享密钥截图:

  • 使用创建的共享密钥就可以对AES的密钥进行加解密。因为生成的共享密钥为DESede密钥类型,则使用DESede加解密模式,密钥为共享密钥其余不变。即将共享密钥保存于文件Key_DESede_DH.dat中即可。

  • 实现关键代码:

SecretKeySpec k=new  SecretKeySpec(sb,"DESede");
        FileOutputStream  f=new FileOutputStream("A_Key_DESede_DH.dat");//指定产生密钥输出流文件
        ObjectOutputStream b=new  ObjectOutputStream(f);//将对象序列化,以流的方式进行处理
        b.writeObject(k);//通过以对象序列化方式将密钥保存在文件中
        

任务五:完整性校验结对编程:1人负责客户端,一人负责服务器

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

遇到问题及解决方案

  • 问题1:无法进行测试

  • 问题1解决方案:测试前应填入数据,再进行测试
  • 问题2:出现java.net.SocketException: Connection reset,客户端输出文件出错
  • 问题2解决方案:该异常在客户端和服务器端均有可能发生,引起该异常的原因有两个,第一个就是如果一端的Socket被关闭(或主动关闭或者因为异常退出而引起的关闭),另一端仍发送数据,发送的第一个数据包引发该异常(Connect reset by peer)。另一个是一端退出,但退出时并未关闭该连接,另一端如果在从连接中读数据则抛出该异常(Connection reset)。简单的说就是在连接断开后的读和写操作引起的。 在读取信息时不应该关闭客户端。

PSP

步骤 耗时(h) 百分比
设计 2 20%
代码实现 5 50%
测试 2 20%
分析总结 1 10%

实验小结

本次实验过程中出现了很多问题,反反复复折腾了几天,不过在不断地从书中网上查询解决问题的过程中,收获颇丰。其实不管是学习生活还是今后工作,有时候需要适当地逼一下自己,可能在某一方面投入90%精力但仍不见希望时,我们需要做的可能就是再努力10%。这应该是本学期最后一次实验了,虽然Java实验真的磨人,不过不论从知识的提升还是耐力的磨练,总算是有所收获,还是挺感谢娄老师。希望自己以后也能屏住最后一口气,撑住往前走。

原文地址:https://www.cnblogs.com/axyaxy/p/10946422.html