20172329 2017-2018-2 《程序设计与数据结构》实验五报告
这是这学期最后一次实验了,也是学到了很多东西,希望自己可以可以善始善终,加油! 让我们开始这一篇博客吧!
20172329 2017-2018-2 《程序设计与数据结构》实验五报告
课程:《程序设计与数据结构》
班级: 1723
姓名: 王文彬
学号:20172329
实验教师:王志强
实验日期:2018年6月11日
必修/选修: 必修
一.实验内容
1、网络编程与安全-1
- 两人一组结对编程:
- a. 参考http://www.cnblogs.com/rocedu/p/6766748.html#SECDSA
- b. 结对实现中缀表达式转后缀表达式的功能 MyBC.java
- c. 结对实现从上面功能中获取的表达式中实现后缀表达式求值的功能,调用MyDC.java
- d. 上传测试代码运行结果截图和码云链接
2、网络编程与安全-2
- 结对编程:1人负责客户端,一人负责服务器
- a. 注意责任归宿,要会通过测试证明自己没有问题
- b. 基于Java Socket实现客户端/服务器功能,传输方式用TCP
- c. 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式通过网络发送给服务器
- d. 服务器接收到后缀表达式,调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
- e. 客户端显示服务器发送过来的结果
- f. 上传测试结果截图和码云链接
3、网络编程与安全-3
- 加密结对编程:1人负责客户端,一人负责服务器
- a. 注意责任归宿,要会通过测试证明自己没有问题
- b. 基于Java Socket实现客户端/服务器功能,传输方式用TCP
- c. 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式用3DES或AES算法加密后通过网络把密文发送给服务器
- d. 服务器接收到后缀表达式表达式后,进行解密(和客户端协商密钥,可以用数组保存),然后调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
- e. 客户端显示服务器发送过来的结果
- f. 上传测试结果截图和码云链接
4、网络编程与安全-4
- 密钥分发结对编程:1人负责客户端,一人负责服务器
- a. 注意责任归宿,要会通过测试证明自己没有问题
- b. 基于Java Socket实现客户端/服务器功能,传输方式用TCP
- c. 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式用3DES或AES算法加密通过网络把密文发送给服务器
- d. 客户端和服务器用DH算法进行3DES或AES算法的密钥交换
- e. 服务器接收到后缀表达式表达式后,进行解密,然后调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
- f. 客户端显示服务器发送过来的结果
- g. 上传测试结果截图和码云链接
5、网络编程与安全-5
- 完整性校验结对编程:1人负责客户端,一人负责服务器
- a. 注意责任归宿,要会通过测试证明自己没有问题
- b. 基于Java Socket实现客户端/服务器功能,传输方式用TCP
- c. 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式用3DES或AES算法加密通过网络把密文和明文的MD5値发送给服务器
- d. 客户端和服务器用DH算法进行3DES或AES算法的密钥交换
- e. 服务器接收到后缀表达式表达式后,进行解密,解密后计算明文的MD5值,和客户端传来的MD5进行比较,一致则调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
- f. 客户端显示服务器发送过来的结果
- g. 上传测试结果截图和码云链接
二. 实验过程及结果
1、网络编程与安全-1
- (1)首先我们进行对于题目的阅读,是让我们实现之前已经实现的四则运算里面的转后缀式以及计算一个后缀式的值,虽然在娄老师的博客里面给了两个方法进行对于一个中缀表达式转化成一个后缀表达式,但是因为当我们复制粘贴以后发现有一些代码需要我们去自己写的时候,就去选择了自己之前已经写好的代码进行对于实验目标的实现。PS:后期对于娄老师代码的理解后,发现娄老师的博客和我们的差异不是很大,而且自己的代码已经写好了,所以,就没有再做更换。
- (2)现在就来说一下具体实现的过程,虽然在之前的四则运算的结对编程里面有所体现,但是在这里还是再做叙述。
- first:这一步是将一个中缀转后缀的一个过程代码
- second:这一步是将一个后缀式计算出的代码
结果:
- third:最后将其进行计算结果以及中缀转后缀的测试
2、网络编程与安全-2
- (1)首先因为是第一次学习并且运用Java Socket实现客户端/服务器功能,传输方式用TCP进行实验,因此一开始老师给我们两个客户端和服务器的代码示例,这一个实验说是进行代码编写不如说是进行代码的补充和修改。首先这一个实验任务就是先构建一个服务器和客户端的连接,然后客户端将一个已经转为后缀式的中缀表达式传给服务器,然后服务器接收到这个中缀表达式,进行计算,通过网络编程与安全-1的实验代码进行计算的实现。
- (2)接下来具体来讲一讲实验的实现:
一、客户端
- first:通过这样一段代码,将建立与服务器的连接
//1.建立客户端Socket连接,指定服务器位置和端口
Socket socket = new Socket("127.0.0.1",8880);
//2.得到socket读写流
OutputStream outputStream = socket.getOutputStream();
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
//输入流
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
- second:通过一系列中缀表达式转为后缀表达式
//3.利用流按照一定的操作,对socket进行读写操作
Scanner scan = new Scanner(System.in);
System.out.println("我输入的中缀表达式为: ");
String a =scan.nextLine();
Calculate p =new Calculate();
String temp = "";
for (int x = 0; x < p.parse(p.zb(a)).size(); ++x) {
temp = temp + p.parse(p.zb(a)).get(x);
}
String info1 = temp;
String info = new String(info1.getBytes("GBK"),"utf-8");
outputStreamWriter.write(info);
outputStreamWriter.flush();
socket.shutdownOutput();
- third:接受到服务器的信息
//接收服务器的响应
String reply = null;
while (!((reply = bufferedReader.readLine()) ==null)){
System.out.println("接收服务器的信息为:" + reply);
}
二、服务器
- first:这一步将建立一个服务器,并且准备接受来自客户端的信息
//1.建立一个服务器Socket(ServerSocket)绑定指定端口
ServerSocket serverSocket=new ServerSocket(8880);
//2.使用accept()方法阻止等待监听,获得新连接
Socket socket=serverSocket.accept();
//3.获得输入流
InputStream inputStream=socket.getInputStream();
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
//获得输出流
OutputStream outputStream=socket.getOutputStream();
PrintWriter printWriter=new PrintWriter(outputStream);
//4.读取用户输入信息
String info=null;
while(!((info = bufferedReader.readLine()) ==null)){
System.out.println("我是服务器,用户信息为:" + info);
- second:接下来开始对于接受到的信息进行处理,也就是要开始对于接收到的后缀表达式要开始计算,并且将计算出的答案传给客户端
//给客户一个响应
Calculate a = new Calculate();
String str=info;
String[] temp = str.split(" ");
List<String> ls= Arrays.asList(temp);
String reply=String.valueOf(a.suanshu(ls));
printWriter.write(reply);
printWriter.flush();}
结果:
- 客户端:
- 服务器:
3、网络编程与安全-3
- (1)在这一个实验中,我们需要用到之前已经学习过的Java与密码学的练习那一个实验的相关知识,首先我们需要去温习一下加密算法有哪些类型,在之前我们了解过的有这样几种:3DES、AES、RSA、凯撒密码,MD5以及一个DH密钥交换协议的学习,在接下来的实验中,我们要依次运用到以上的相关知识。这次试验的目的是客户端需要将一个已经形成好的后缀表达式进行加密,然后传递给服务器将其进行解密,然后将解出来的后缀式进行计算。
- (2)具体来讲:
客户端:
- first:利用这两个类进行生成密钥
public class Skey_DES{
public static void main(String args[])
throws Exception{
KeyGenerator kg=KeyGenerator.getInstance("DESede");
kg.init(168);
SecretKey k=kg.generateKey( );
FileOutputStream f=new FileOutputStream("key1.dat");
ObjectOutputStream b=new ObjectOutputStream(f);
b.writeObject(k);
}
}
public class Skey_kb{
public static void main(String args[]) throws Exception{
FileInputStream f=new FileInputStream("key1.dat");
ObjectInputStream b=new ObjectInputStream(f);
Key k=(Key)b.readObject( );
byte[ ] kb=k.getEncoded( );
FileOutputStream f2=new FileOutputStream("keykb1.dat");
f2.write(kb);
// 打印密钥编码中的内容
for(int i=0;i<kb.length;i++){
System.out.print(kb[i]+",");
}
}
}
- second:进行加密
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);
String a="";
for(int i=0;i<ctext.length;i++){
a+=ctext[i] +",";
// System.out.print(a);
}
FileOutputStream f2=new FileOutputStream("SEnc.dat");
f2.write(ctext);
return a;
服务器:
- third:进行解密
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);
// 显示明文
String p=new String(ptext,"UTF8");
结果:
- 客户端:
- 服务器:
4、网络编程与安全-4
- (1)这一部分的实验要求我们需要生成新的密钥从而进行加密和解密的过程,而这个产生新密钥的过程就是需要用到我们之前学到的DH交换密钥协议,这里先说一下我对于DH算法的理解:
- 首先,就双方而言,我们需要去传递我们想要的密文就需要一个密钥,但是我们想要加强安全性,因此就产生了DH算法,它的流程是这样的:A方会产生一个公钥和私钥,这里我们简称为公A和私A,B方会产生一个公钥和一个私钥,这里简称为公B和私B,我们需要交换的是两个公钥,从而可以利用我们自己的公钥和对方的私钥产生一个新的密钥,这个密钥就是我们需要的共享密钥,我们需要用这个密钥进行对于明文的加密和解密。
可以结合这个图进行理解
- (2)具体分析:
- first:进行新密钥的生成,进行从"Bpub.dat","Apri.dat"读取文档,从而生成共享信息,然后进行加密
客户端:
FileInputStream f1=new FileInputStream("Bpub.dat");
ObjectInputStream b1=new ObjectInputStream(f1);
PublicKey pbk=(PublicKey)b1.readObject( );
//读取自己的DH私钥
FileInputStream f3=new FileInputStream("Apri.dat");
ObjectInputStream b2=new ObjectInputStream(f3);
PrivateKey prk=(PrivateKey)b2.readObject();
// 执行密钥协定
KeyAgreement ka=KeyAgreement.getInstance("DH");
ka.init(prk);
ka.doPhase(pbk,true);
//生成共享信息
byte[ ] sb=ka.generateSecret();
SecretKeySpec k = new SecretKeySpec(sb, 0,24,"DESede");
Cipher cp=Cipher.getInstance("DESede");
cp.init(Cipher.ENCRYPT_MODE, k);
byte ptext[]=s.getBytes("UTF8");
byte ctext[]=cp.doFinal(ptext);
String a="";
for(int i=0;i<ctext.length;i++){
a+=ctext[i] +",";
// System.out.print(a);
}
FileOutputStream f2=new FileOutputStream("SEnc.dat");
f2.write(ctext);
return a;
- second:进行解密
服务器:
FileInputStream f1=new FileInputStream("Apub.dat");
ObjectInputStream b1=new ObjectInputStream(f1);
PublicKey pbk=(PublicKey)b1.readObject( );
//读取自己的DH私钥
FileInputStream f2=new FileInputStream("Bpri.dat");
ObjectInputStream b2=new ObjectInputStream(f2);
PrivateKey prk=(PrivateKey)b2.readObject( );
// 执行密钥协定
KeyAgreement ka=KeyAgreement.getInstance("DH");
ka.init(prk);
ka.doPhase(pbk,true);
//生成共享信息
byte[ ] sb=ka.generateSecret();
SecretKeySpec k=new SecretKeySpec(sb,0,24,"DESede");
// 解密
Cipher cp=Cipher.getInstance("DESede");
cp.init(Cipher.DECRYPT_MODE, k);
byte []ptext=cp.doFinal(ctext);
// 显示明文
String p=new String(ptext,"UTF8");
// System.out.println(p);
结果:
5、网络编程与安全-5
- (1)第五个实验是关于MD5关于Java的摘要算法
- (2)具体实现:
- first:计算明文的MD5值
public String Diget(String x) throws UnsupportedEncodingException, NoSuchAlgorithmException {
MessageDigest m=MessageDigest.getInstance("MD5");
m.update(x.getBytes("UTF8"));
byte s[ ]=m.digest( );
String result="";
for (int i=0; i<s.length; i++){
result+=Integer.toHexString((0x000000ff & s[i]) |
0xffffff00).substring(6);
}
return result;
}
- second:进行对比,如果一样,则计算,不一样则抛出异常
DigestPass ooo=new DigestPass();
String reply;
if (ww.equals(ooo.Diget(p)))
{
reply= String.valueOf(a.suanshu(ls));
}
else {
reply="返回的MD5值不同";
}
结果:
- 客户端:
- 服务器:
三. 实验过程中遇到的问题和解决过程
-
问题1:在做第三个实验的时候,就到底如何传给服务器密钥大家产生了很大的分歧,很多人认为需要用像是传递原文的方式进行,有些人认为应该用文件直接拷贝过去就可以
-
问题1解答:我认为两种方式都可以。
-
首先我们先分析第一种方法,也就是通过传递原文的方式将其传递过去,但是这里就会有问题,因为,我们不清楚到底是如何去传递两个东西,我们并没有被要求去用双线程,况且我们也没有那么厉害,因此通过和余坤澎同学的讨论,我们可以用这个方法进行将两个东西传递过去,举个例子:假如我们现在要传递字符串A和字符串B,但是我们怎么把两个东西一起传递过去呢,首先我们想到,就字符串有一个
split
的方法,我们可以利用这个方法把这两个字符串用一个字符进行切分,然后分别保存,比如“String aa =A;B
”,我们用String []bb =aa.split";";
就可以将其分成bb[0]
和bb[1]
这样就把两个东西都传递过去了。 -
其次就是第二种方式,我相信第二种方式不用太过去强调,我认为是个人应该都会的,而且最开始想到的办法肯定也是这一种方法。
-
问题2:在服务器接受到的info总是空指针这问题
-
问题2解决方案:
String info=null;
String str1="";
while(!((info = bufferedReader.readLine()) ==null)){
System.out.println("我是服务器,用户信息为:" + info);
str1=info;}
String info=null;
while(!((info = bufferedReader.readLine()) ==null)){
System.out.println("我是服务器,用户信息为:" + info);
}
我们需要区分这两个代码的区别,运用第二个会出现报错,出现空指针,因为第二个代码的info是直到它为空才会跳出这个循环,自然会抛出空指针的异常。
- 问题3:在做到第四个实验的时候,有一个问题卡了我很久,就是密钥长度的问题
- 问题3解决方案:
- 因为一开始我查了为什么是这个问题,就是因为生成的密钥长度不符合3DES的要求,因此就会导致这个问题,我们知道3DES的密钥长度要求是8的倍数,并且在我们这个实验里DESede需要的是24位,但是我们得到的共享密钥是128位的所以它会冒出密钥长度的问题,在这里先感谢张旭升学长对我的帮助,解决方法就在这一句
SecretKeySpec k =new SecretKeySpec(sb,"DESede");
可以实现,首先我们的目的是想要把那个128的密钥只取24位,因此我们查找了API文档
看了看这句话的参数有什么,其中就有控制范围:
将语句变成SecretKeySpec k=new SecretKeySpec(sb,0,24,"DESede");
,问题也就可以解决了。
其他(感悟、思考等)
这一学期马上就要结束了,很多人现在也都已经开始放飞自我了,端午这三天,自己决定自己的收获还是非常大的,最近的几次编程,我觉得我学习Java的热情又回来了,然后我发现API真是个好东西,就像一本字典一样,而且讲解的都非常详细,希望自己可以坚持下去,自己立的flag,一定要做到!
参考资料
蓝墨云班课
2016-2017-2 《Java 程序设计》课堂实践项目
Java 密码学算法
JAVA加解密DH算法
3DES自定义密钥问题
JAVA实现DES加密实现详解
多线程方式实现Socket通信
TCP