2018-2019-2 20175320实验五《网络编程与安全》实验报告
一、相关介绍
在本次实验中我主要使用了书本第13章Java网络编程以及Java密码学的内容,基于TCP的信息传输使用socket进行实现,加密、解密、生成共享密钥以及进行摘要验证使用了Java中的JCA以及JCE。
二、实验步骤及内容
(一)任务一
任务要求:
- 结对实现中缀表达式转后缀表达式的功能并对后缀表达式进行计算
虽然该功能是后续步骤的基础,但在以前的结对项目中已经实现过了,在这里我就不再多做介绍了,详情请见博客结对编程项目-四则运算阶段性总结
运行结果如图:
(二)任务二
任务要求:
- 客户端中输入中缀表达式,然后将中缀表达式转化为后缀表达式,把后缀表达式通过网络发送给服务器。
- 服务器接收到后缀表达式后计算后缀表达式的值,并把结果发送给客户端
- 客户端显示服务器发送过来的结果
在加密时我选择了3DES,3DES在使用过程中只需将图示参数设置为“DESede”即可。
步骤:
1、创建数据输入以及输出流对象in和out用于建立连接后的数据传输。
2、客户端创建套接字对象mysocket,并用构造方法Socket(String host,int port)给mysocket创建实体,然后让mysocket调用getInputStream以及getoutputStream方法与服务器端建立连接。服务器端的步骤与客户端类似,但在创建套接字时服务器端先创建一个ServerSocket对象,并用该对象调用accept方法来创建Socket类的实例。
3、客户端与服务器端分别用对象out调用writeUTF方法来发送数据,对象in调用readUTF来接收数据。客户端在输入中缀表达式后转化为后缀表达式并通过out对象发送给服务器,服务器用in对象接收客户端发送过来的后缀表达式进行计算,并将计算结果使用out对象发送给客户端,客户端使用in对象接收客户端发来的数据并输出。
运行结果如下:
任务三
任务要求:
- 客户端中输入中缀表达式,然后将中缀表达式转化为后缀表达式
- 客户端使用3DES算法对后缀表达式进行加密,并将加密后的密文发送给服务器
- 服务器接收到密文后进行解密,并对解密后的明文计算表达式的结果,并把结果发送给客户端,客户端显示服务器发送过来的结果
该任务是在任务二的基础上进行功能拓展,于是本任务中的网络信息收发部分我就不再介绍,我主要对加密及解密过程进行描述,而生成密钥以及加密解密我都放在了Skey_kb
类中,并通过方法来实现功能的调用。
加解密步骤:
1、生成密钥
- 获取密钥生成器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);
2、改变密钥保存方式
在解密时需要使用字节数组形式的密钥,因此需要将密钥以另一种方式保存在文件中。
- 获取密钥,首先创建文件输入流,然后将其作为参数传递给对象输入流,最后执行对象输入流的readObject( )方法读取密钥对象。由于readObject( )返回的是Object类型,因此需要强制转换成Key类型。
- 获取主要编码格式,执行SecretKey类型的对象k的getEncoded( )方法,返回的编码放在byte类型的数组中。
- 保存密钥编码格式,创建文件输出流对象,在其参数中指定文件名,如keykb1.dat。然后执行文件输出流的write( )方法将第2步中得到的字节数组中的内容写入文件。、
3、加密
- 从文件中获取密钥
FileInputStream f=new FileInputStream("key1.dat");ObjectInputStream b=new ObjectInputStream(f);Key k=(Key)b.readObject( );
- 创建密码器(Cipher对象)Cipher
cp=Cipher.getInstance("DESede");
- 初始化密码器
cp.init(Cipher.ENCRYPT_MODE, k);
- 获取等待加密的明文
String s="Hello World!";` byte ptext[]=s.getBytes("UTF8");
- 执行加密
byte ctext[]=cp.doFinal(ptext);
- 处理加密结果
FileOutputStream f2=new FileOutputStream("SEnc.dat"); f2.write(ctext);
4、解密
- 获取密文
FileInputStream f=new FileInputStream("SEnc.dat"); int num=f.available(); byte[ ] ctext=new byte[num]; f.read(ctext);
- 获取密钥
- 创建密码器
- 初始化密码器
- 执行解密
需要注意的是,加密和解密的方法需要分别传入明文以及密文的字符串,并将对应的密文以及明文作为方法的输出。在加密时需要将密文二进制字节数组转化为十六进制后再转化为字符串作为发送数据,在解密时需要将密文转化为二进制后再进行解密。如果不进行数据转化的话在进行密文传输时系统会报错。
运行结果如下:
服务器端:
客户端:
任务四
任务要求:
- 客户端中输入中缀表达式,然后将中缀表达式转化为后缀表达式
- 客户端和服务器端使用DH算法生成各自的公钥与私钥,并用自己的私钥以及对方的公钥生成共有密钥
- 客户端使用3DES算法对后缀表达式进行加密,并将加密后的密文发送给服务器
- 服务器接收到密文后进行解密,并对解密后的明文计算表达式的结果,并把结果发送给客户端,客户端显示服务器发送过来的结果
该过程是在任务三的基础上进行的,我在这里只介绍DH算法相关的内容。由于生成公共密钥前需要两端交换公钥,而两端在两个不同的文件夹中,于是我我在调用程序生成两端各自的两个密钥后,使用套接字在客户端与服务器之间进行了沟通,客户端首先会给服务器发送已生成两个密钥钥的信息,服务器在接收到该消息时也已经生成了两个密钥,在这时需要手动将两者的pub.dat文件进行交换,并在交换完成后在服务器端输入准备好了的信息,并发送给客户端,这样一来就可以正常进行加密及解密密钥的生成了。需要注意的是由于我们使用的3DES是对外公布的限制密钥长度的版本,而DH算法生成的共享密钥有128位,所以需要在生成加密密钥后取得共享密钥的前几位,否则系统会显示密钥长度不合法。
运行结果如下:
任务五
任务要求:
- 客户端中输入中缀表达式,然后将中缀表达式转化为后缀表达式
- 客户端使用DH算法生成各自的公钥与私钥,并用自己的私钥以及对方的公钥生成共有密钥
- 客户端使用MD5算法计算明文的摘要,并将摘要发送给客户端
- 客户端使用3DES算法对后缀表达式进行加密,并将加密后的密文发送给服务器
- 服务器接收到密文后进行解密,并对解密后的明文计算其摘要,并与接收到的摘要进行比较,如果相同则计算解密后明文表达式的结果,并把结果发送给客户端,客户端显示服务器发送过来的结果
在这部分中我增加了一次out以及in的调用用于两端传递摘要,MD5创建摘要的过程如下所示:
1、生成MessageDigest对象MessageDigest
2、传入需要计算的字符串m.update(x.getBytes("UTF8" ));
3、?计算消息摘要byte s[ ]=m.digest( );
4、将计算结果s转换为字符串
运行结果如图:
服务器:
客户端:
二、实验时遇到的问题:
- 问题1:在运行中缀转后缀的过程中Trans类发送错误。
- 问题1解决方法:后来发现是结对编程时协调不足,Trans类有误且与封装了计算功能的类重复,没有遵循solid原则。Trans类删除并使用计算功能类中的方法。
- 问题2:如果将字节数组的密文直接转化为字符串,在传输步骤中程序报错。
- 问题2解决方法:需要将密文二进制字节数组转化为十六进制,然后再转化为字符串进行传输。
- 问题3:在使用DH算法生成的密钥进行加密时系统报错:
- 问题3解决方法:DH算法生成的共享密钥长度为128位,需要我们对密钥长度进行修改。
三、实验感想
本次实验是网络编程以及Java密码学的一个综合,在实验过程中遇到的问题使我印象深刻,SOLID原则十分重要,它不仅能减少代码的数量,更使得程序更加易读,更易于修改,且不容易出错。基于TCP的数据传输也不是随心所欲的,其对传输内容的格式也有要求,如果胡乱对传输内容进行格式转换就很可能出错。而我们使用的JCE以及JCA都是限制功能的版本,在加密时只能使用很短的密钥,从这也看出发达国家对技术的严格管制。
原文地址:https://www.cnblogs.com/nameless-student/p/10956421.html