2018-2019-2 20175126谢文航 实验五《网络编程与安全》实验报告
一、实验封面
-
课程:Java程序设计 班级:1751 班 姓名:谢文航 学号:20175126
-
指导教师:娄嘉鹏
-
实验日期:2019年5月29日
-
实验序号:实验五
-
实验名称:网络编程与安全
-
实验内容:
- 完成云班课中的检查点,也可以先完成实验报告,直接提交。注意不能只有截图,要有知识点,原理,遇到的问题和解决过程等说明。实验报告中一个检查点要有多张截图。
- 发表实验报告博客,标题“学期(如2018-2019-2) 学号(如20175300)实验五 Java网络编程 实验报告”
二、实验内容
任务一:
实验要求:
两人一组结对编程:
0. 参考http://www.cnblogs.com/rocedu/p/6766748.html#SECDSA
1. 结对实现中缀表达式转后缀表达式的功能 MyBC.java
2. 结对实现从上面功能中获取的表达式中实现后缀表达式求值的功能,调用MyDC.java
3. 上传测试代码运行结果截图和码云链接
知识点:
栈的一个应用是用来对四则运算表达式进行求值。
表达式Exp = S1 + OP + S2
(S1 ,S2是两个操作数,OP为运算符)有三种标识方法:
- OP + S1 + S2 为前缀表示法
- S1 + OP + S2 为中缀表示法
- S1 + S2 + OP 为后缀表示法
例如:Exp = a * b + (c - d / e) * f
- 前缀式: + * a b * - c / d e f
- 中缀式: a * b + c - d / e * f
- 后缀式: a b * c d e / - f * +
我们可以看出:
-
- 操作数之间的相对次序不变;
- 运算符的相对次序不同;
- 中缀式丢失了括弧信息,致使运算次序不确定;
- 前缀式的运算规则为:连续出现的两个操作数和在它们之前且紧靠它们的运算符构成一个最小表达式;
- 后缀式的运算规则为:运算符在式中出现的顺序恰为表达式的运算顺序;每个运算符和在它之前出现且紧靠它的两个操作数构成一个最小表达式。
实验步骤:
- 编写后缀表达式计算的代码MyDC.java:
-
import java.util.Stack; public class MyDC { static Stack<Character> op = new Stack<>(); public static Float getv(char op, Float f1, Float f2) { if (op == '+') { return f2 + f1; } else if (op == '-') { return f2 - f1; } else if (op == '*') { return f2 * f1; } else if (op == '/') { return f2 / f1; } else { return Float.valueOf(-0); } } public static float calrp(String rp) { Stack<Float> v = new Stack<>(); char[] arr = rp.toCharArray(); int len = arr.length; for (int i = 0; i < len; i++) { Character ch = arr[i]; if (ch >= '0' && ch <= '9') { v.push(Float.valueOf(ch - '0')); } else { v.push(getv(ch, v.pop(), v.pop())); } } return v.pop(); } }
- 编写中缀转后缀的代码MyBC.java:
-
import java.util.Stack; public class MyBC { static Stack<Character> op = new Stack<>(); public static String getrp(String s){ char[] arr = s.toCharArray(); int len = arr.length; String out = ""; for(int i =0;i<len;i++){ char ch = arr[i]; if(ch == ' ') continue; if(ch>='0'&&ch<='9'){ out +=ch; continue; } if(ch =='(') op.push(ch); if(ch == '+'|| ch=='-'){ while(!op.empty()&&(op.peek()!='(')) out +=op.pop(); op.push(ch); continue; } if(ch=='*'||ch=='/'){ while(!op.empty()&&(op.peek()=='*'||op.peek()=='/')) out+=op.pop(); op.push(ch); continue; } if(ch == ')'){ while(!op.empty()&&op.peek()!='(') out += op.pop(); op.pop(); continue; } } while(!op.empty()) out += op.pop(); return out; } }
- 编写主函数代码Main.java:
-
import java.util.Scanner; public class Main { public static void main(String[] args){ Scanner in = new Scanner(System.in); System.out.println("请输入运算式:"); String s = in.nextLine(); String s2 = MyBC.getrp(s); System.out.println("转换为后缀表达式:"+s2); System.out.println(MyDC.calrp(s2)); } }
运行效果截图:
任务二:
实验要求:
结对编程:1人负责客户端,一人负责服务器
0. 注意责任归宿,要会通过测试证明自己没有问题
1. 基于Java Socket实现客户端/服务器功能,传输方式用TCP
2. 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式通过网络发送给服务器
3. 服务器接收到后缀表达式,调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
4. 客户端显示服务器发送过来的结果
5. 上传测试结果截图和码云链接
实验步骤:
该任务需要利用Socket实现用客户端和用户端。
- 客户端Client代码:
-
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 SocketClient { // 搭建客户端 public static void main(String[] args) throws IOException { try { // 1、创建客户端Socket,指定服务器地址和端口 //下面是你要传输到另一台电脑的IP地址和端口 Socket socket = new Socket("192.168.56.1", 5209); System.out.println("客户端启动成功"); // 2、获取输出流,向服务器端发送信息 // 向本机的52000端口发出客户请求 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 由系统标准输入设备构造BufferedReader对象 PrintWriter write = new PrintWriter(socket.getOutputStream()); // 由Socket对象得到输出流,并构造PrintWriter对象 //3、获取输入流,并读取服务器端的响应信息 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 由Socket对象得到输入流,并构造相应的BufferedReader对象 String readline; readline = br.readLine(); // 从系统标准输入读入一字符串 readline = MyBC.getrp(readline); while (!readline.equals("end")) { // 若从标准输入读入的字符串为 "end"则停止循环 write.println(readline); // 将从系统标准输入读入的字符串输出到Server write.flush(); // 刷新输出流,使Server马上收到该字符串 System.out.println("客户:" + readline); // 在系统标准输出上打印读入的字符串 System.out.println("服务:" + in.readLine()); // 从Server读入一字符串,并打印到标准输出上 readline = br.readLine(); // 从系统标准输入读入一字符串 } // 继续循环 //4、关闭资源 write.close(); // 关闭Socket输出流 in.close(); // 关闭Socket输入流 socket.close(); // 关闭Socket } catch (Exception e) { System.out.println("can not listen to:" + e);// 出错,打印出错信息 } } }
- 服务器端Server代码:
-
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class SocketService { //搭建服务器端 public static void main(String[] args) throws IOException{ SocketService socketService = new SocketService(); //1、a)创建一个服务器端Socket,即SocketService socketService.oneServer(); } public void oneServer(){ try{ ServerSocket server=null; try{ //下面是端口,端口可以和客户端代码里面的端口一样 server=new ServerSocket(5209); //b)指定绑定的端口,并监听此端口。 System.out.println("服务器启动成功"); //创建一个ServerSocket在端口5209监听客户请求 }catch(Exception e) { System.out.println("没有启动监听:"+e); //出错,打印出错信息 } Socket socket=null; try{ socket=server.accept(); //2、调用accept()方法开始监听,等待客户端的连接 //使用accept()阻塞等待客户请求,有客户 //请求到来则产生一个Socket对象,并继续执行 }catch(Exception e) { System.out.println("Error."+e); //出错,打印出错信息 } //3、获取输入流,并读取客户端信息 String line; BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream())); //由Socket对象得到输入流,并构造相应的BufferedReader对象 PrintWriter writer=new PrintWriter(socket.getOutputStream()); //由Socket对象得到输出流,并构造PrintWriter对象 BufferedReader br=new BufferedReader(new InputStreamReader(System.in)); //由系统标准输入设备构造BufferedReader对象 Float m1=MyDC.calrp(in.readLine()); System.out.println("Client:"+m1); //在标准输出上打印从客户端读入的字符串 line = m1.toString(); //从标准输入读入一字符串 //4、获取输出流,响应客户端的请求 while(!line.equals("end")){ //如果该字符串为 "bye",则停止循环 writer.println(line); //向客户端输出该字符串 writer.flush(); //刷新输出流,使Client马上收到该字符串 System.out.println("服务:"+line); //在系统标准输出上打印读入的字符串 System.out.println("客户:"+in.readLine()); //从Client读入一字符串,并打印到标准输出上 line=br.readLine(); //从系统标准输入读入一字符串 } //继续循环 //5、关闭资源 writer.close(); //关闭Socket输出流 in.close(); //关闭Socket输入流 socket.close(); //关闭Socket server.close(); //关闭ServerSocket }catch(Exception e) {//出错,打印出错信息 System.out.println("Error."+e); } } }
运行效果截图:
- 客户端界面:
- 服务器端界面:
任务三:
实验要求:
加密结对编程:1人负责客户端,一人负责服务器
0. 注意责任归宿,要会通过测试证明自己没有问题
1. 基于Java Socket实现客户端/服务器功能,传输方式用TCP
2. 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式用3DES或AES算法加密后通过网络把密文发送给服务器
3. 服务器接收到后缀表达式表达式后,进行解密(和客户端协商密钥,可以用数组保存),然后调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
4. 客户端显示服务器发送过来的结果
5. 上传测试结果截图和码云链接
实验步骤:
该任务需要使用AES加密算法。
- 编写AES加密算法代码:
- Skey_AES.java
-
import java.io.*; import javax.crypto.*; public class Skey_AES{ public static void main(String args[]) throws Exception{ KeyGenerator kg=KeyGenerator.getInstance("AES"); kg.init(128); SecretKey k=kg.generateKey( ); FileOutputStream f=new FileOutputStream("key1.dat"); ObjectOutputStream b=new ObjectOutputStream(f); b.writeObject(k); } }
- Encoder.java
-
import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Base64; import java.util.Scanner; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; public class Encoder { public static String AESEncode(String encodeRules,String content){ try { KeyGenerator keygen=KeyGenerator.getInstance("AES"); keygen.init(128, new SecureRandom(encodeRules.getBytes())); SecretKey original_key=keygen.generateKey(); byte [] raw=original_key.getEncoded(); SecretKey key=new SecretKeySpec(raw, "AES"); Cipher cipher=Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, key); byte [] byte_encode=content.getBytes("utf-8"); byte [] byte_AES=cipher.doFinal(byte_encode); return AES_encode; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return null; } public static String AESDncode(String encodeRules,String content){ try { KeyGenerator keygen=KeyGenerator.getInstance("AES"); keygen.init(128, new SecureRandom(encodeRules.getBytes())); SecretKey original_key=keygen.generateKey(); byte [] raw=original_key.getEncoded(); SecretKey key=new SecretKeySpec(raw, "AES"); Cipher cipher=Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, key); byte [] byte_content= new BASE64Decoder().decodeBuffer(content); byte [] byte_decode=cipher.doFinal(byte_content); String AES_decode=new String(byte_decode,"utf-8"); return AES_decode; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } return null; } }
- 加入到Client.java
-
import java.io.*; import java.net.*; import java.lang.*; import java.util.Scanner; public class Client { public static void main(String args[]) throws Exception { String key = ""; int n = -1; byte[] a = new byte[128]; try { File f = new File("key1.dat"); InputStream in = new FileInputStream(f); while ((n = in.read(a, 0, 100)) != -1) { key = key + new String(a, 0, n); } in.close(); } catch (IOException e) { System.out.println("File read Error" + e); } Socket mysocket; DataInputStream in = null; DataOutputStream out = null; System.out.println("客户端连接成功:"); Scanner scanner = new Scanner(System.in); String str = scanner.nextLine();//输入算式 str = MyBC.getrp(str); String secret=Encoder.AESEncode(key,str);//客户端进行加密 System.out.println("已加密:"+secret); try { mysocket = new Socket("127.1.0.0", 2010); in = new DataInputStream(mysocket.getInputStream()); out = new DataOutputStream(mysocket.getOutputStream()); out.writeUTF(key); out.writeUTF(secret); String s = in.readUTF(); //in读取信息,堵塞状态 System.out.println("客户收到服务器的回答:" + s); Thread.sleep(500); } catch (Exception e) { System.out.println("服务器已断开" + e); } } }
- 加入到Server.java
-
import java.io.*; import java.net.*; public class Server { public static void main (String args[]) throws Exception { ServerSocket serverForClient=null; Socket socketOnServer=null; DataOutputStream out=null; DataInputStream in=null; try { serverForClient = new ServerSocket(2010); } catch(IOException e1) { System.out.println(e1); } try{ System.out.println("等待客户呼叫"); socketOnServer = serverForClient.accept(); //堵塞状态,除非有客户呼叫 out=new DataOutputStream(socketOnServer.getOutputStream()); in=new DataInputStream(socketOnServer.getInputStream()); String key = in.readUTF(); String s=in.readUTF(); // in读取信息,堵塞状态 System.out.println("服务器收到的信息:"+s); String clear=Encoder.AESDncode(key,s); System.out.println("解密后:"+clear); float answer=MyDC.calrp(clear); out.writeUTF(answer+""); Thread.sleep(500); } catch(Exception e) { System.out.println("客户已断开"+e); } } }
运行效果截图:
客户端:
服务器端:
任务四:
实验要求:
任务详情
密钥分发结对编程:1人负责客户端,一人负责服务器
0. 注意责任归宿,要会通过测试证明自己没有问题
1. 基于Java Socket实现客户端/服务器功能,传输方式用TCP
2. 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式用3DES或AES算法加密通过网络把密文发送给服务器
3. 客户端和服务器用DH算法进行3DES或AES算法的密钥交换
4. 服务器接收到后缀表达式表达式后,进行解密,然后调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
5. 客户端显示服务器发送过来的结果
6. 上传测试结果截图和码云链接
实验步骤:
该任务需要用到密钥对生成器、公钥及私钥:
- 创建密钥对生成器:
KeyPairGenerator kpg=KeyPairGenerator.getInstance("RSA");
- 初始化密钥生成器
kpg.initialize(1024);
- 生成密钥对
KeyPair kp=kpg.genKeyPair( );
- 获取公钥和私钥
PublicKey pbkey=kp.getPublic( );
码云链接:https://gitee.com/Apollo20175126/java-besti-20175126/tree/master/20175126/test10
运行效果截图:
客户端:
服务器端:
任务五:
实验要求:
完整性校验结对编程:1人负责客户端,一人负责服务器
0. 注意责任归宿,要会通过测试证明自己没有问题
1. 基于Java Socket实现客户端/服务器功能,传输方式用TCP
2. 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式用3DES或AES算法加密通过网络把密文和明文的MD5値发送给服务器
3. 客户端和服务器用DH算法进行3DES或AES算法的密钥交换
4. 服务器接收到后缀表达式表达式后,进行解密,解密后计算明文的MD5值,和客户端传来的MD5进行比较,一致则调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
5. 客户端显示服务器发送过来的结果
6. 上传测试结果截图和码云链接
实验步骤:
该任务需要使用DH算法:
- DH.java
-
import javax.crypto.spec.DHParameterSpec; import java.io.FileOutputStream; import java.io.ObjectOutputStream; import java.math.BigInteger; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; public class DH { private static final byte SKIP1024_MODULUS_BYTES[] = { (byte)0xF4, (byte)0x88, (byte)0xFD, (byte)0x58, (byte)0x4E, (byte)0x49, (byte)0xDB, (byte)0xCD, (byte)0x20, (byte)0xB4, (byte)0x9D, (byte)0xE4, (byte)0x91, (byte)0x07, (byte)0x36, (byte)0x6B, (byte)0x33, (byte)0x6C, (byte)0x38, (byte)0x0D, (byte)0x45, (byte)0x1D, (byte)0x0F, (byte)0x7C, (byte)0x88, (byte)0xB3, (byte)0x1C, (byte)0x7C, (byte)0x5B, (byte)0x2D, (byte)0x8E, (byte)0xF6, (byte)0xF3, (byte)0xC9, (byte)0x23, (byte)0xC0, (byte)0x43, (byte)0xF0, (byte)0xA5, (byte)0x5B, (byte)0x18, (byte)0x8D, (byte)0x8E, (byte)0xBB, (byte)0x55, (byte)0x8C, (byte)0xB8, (byte)0x5D, (byte)0x38, (byte)0xD3, (byte)0x34, (byte)0xFD, (byte)0x7C, (byte)0x17, (byte)0x57, (byte)0x43, (byte)0xA3, (byte)0x1D, (byte)0x18, (byte)0x6C, (byte)0xDE, (byte)0x33, (byte)0x21, (byte)0x2C, (byte)0xB5, (byte)0x2A, (byte)0xFF, (byte)0x3C, (byte)0xE1, (byte)0xB1, (byte)0x29, (byte)0x40, (byte)0x18, (byte)0x11, (byte)0x8D, (byte)0x7C, (byte)0x84, (byte)0xA7, (byte)0x0A, (byte)0x72, (byte)0xD6, (byte)0x86, (byte)0xC4, (byte)0x03, (byte)0x19, (byte)0xC8, (byte)0x07, (byte)0x29, (byte)0x7A, (byte)0xCA, (byte)0x95, (byte)0x0C, (byte)0xD9, (byte)0x96, (byte)0x9F, (byte)0xAB, (byte)0xD0, (byte)0x0A, (byte)0x50, (byte)0x9B, (byte)0x02, (byte)0x46, (byte)0xD3, (byte)0x08, (byte)0x3D, (byte)0x66, (byte)0xA4, (byte)0x5D, (byte)0x41, (byte)0x9F, (byte)0x9C, (byte)0x7C, (byte)0xBD, (byte)0x89, (byte)0x4B, (byte)0x22, (byte)0x19, (byte)0x26, (byte)0xBA, (byte)0xAB, (byte)0xA2, (byte)0x5E, (byte)0xC3, (byte)0x55, (byte)0xE9, (byte)0x2F, (byte)0x78, (byte)0xC7 }; private static final BigInteger SKIP1024_MODULES = new BigInteger(1, SKIP1024_MODULUS_BYTES); private static final BigInteger SKIP1024_BASE = BigInteger.valueOf(2); public static void createPubAndPriKey(String inpub,String inpri) throws Exception{ DHParameterSpec dhp = new DHParameterSpec(SKIP1024_MODULES, SKIP1024_BASE); KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH"); kpg.initialize(dhp); KeyPair kp = kpg.generateKeyPair(); PublicKey pbk = kp.getPublic(); PrivateKey prk = kp.getPrivate(); FileOutputStream f1 = new FileOutputStream(inpub); ObjectOutputStream b1 = new ObjectOutputStream(f1); b1.writeObject(pbk); FileOutputStream f2 = new FileOutputStream(inpri); ObjectOutputStream b2 = new ObjectOutputStream(f2); b2.writeObject(prk); } }
- SEnc.java
-
import java.io.*; import java.security.*; import javax.crypto.*; public class SEnc{ public static void main(String args[]) throws Exception{ String s="Hello World!"; FileInputStream f=new FileInputStream("key1.dat"); ObjectInputStream b=new ObjectInputStream(f); Key k=(Key)b.readObject( ); Cipher cp=Cipher.getInstance("DESede"); cp.init(Cipher.ENCRYPT_MODE, k); byte ptext[]=s.getBytes("UTF8"); for(int i=0;i<ptext.length;i++){ System.out.print(ptext[i]+","); } System.out.println(""); byte ctext[]=cp.doFinal(ptext); for(int i=0;i<ctext.length;i++){ System.out.print(ctext[i] +","); } FileOutputStream f2=new FileOutputStream("SEnc.dat"); f2.write(ctext); } }
运行效果截图:
客户端:
服务器端:
三、遇到的问题及解决办法
问题一:
服务器调用MyDC时出现错误:
问题一解决办法:
这是由于MyDC.calrp的返回值是float,所以应该将String改为Float型:
问题二:
编译运行时,出现找不到文件的错误:
问题二解决办法:
这是由于敲代码时把文件名字写错导致,改回来即可:
问题三:
出现javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
报错
问题三解决办法:
产生的密文是byte[]数组,但是传送给服务器时所使用的参数应该是String类型,我是直接使用了String s = new String(ctext);,所以就出现了错误。所以只用将ctext[]二进制转化成十六进制传送给服务器,服务器接收后再将十六进制转为二进制即可。
四、感悟
本次实验是JAVA课程最后一次实验,五个任务都是循序渐进一个接一个的递进关系,感觉自己学会了很多,但在AES加解密的部分还是有点欠缺,需要再加强!
五、码云链接
https://gitee.com/Apollo20175126/java-besti-20175126/tree/master/20175126