2016-2017-2 20155322 实验五 网络编程与安全
2016-2017-2 20155322 实验五 网络编程与安全
目录
实践一
- 参考博客
- 结对实现中缀表达式转后缀表达式的功能 MyBC.java
- 结对实现从上面功能中获取的表达式中实现后缀表达式求值的功能,调用MyDC.java
1. 相关概念
表达式Exp = S1 + OP + S2
(S1 ,S2是两个操作数,OP为运算符)有三种标识方法:
- OP + S1 + S2 为前缀表示法
- S1 + OP + S2 为中缀表示法
- S1 + S2 + OP 为后缀表示法
后缀式的运算规则为:运算符在式中出现的顺序恰为表达式的运算顺序;每个运算符和在它之前出现且紧靠它的两个操作数构成一个最小表达式。
举个例子,如:a * b + (c - d / e) * f
可以表示为:
a * b + (c - d / e) * f
2. 代码实现
**首先是首实现中缀转后缀:** 实现这个功能主要是利用栈的特性,根据中缀表达式中的字符入栈出栈转化为后缀表达式,大体思路如下:-
1、如果是"("直接压入stack栈。
-
2、如果是")",依次从stack栈弹出运算符加到数组newExpressionStrs的末尾,知道遇到"(";
-
3、如果是非括号,比较扫描到的运算符,和stack栈顶的运算符。如果扫描到的运算符优先级高于栈顶运算符则,把运算符压入栈。否则的话,就依次把栈中运算符弹出加到数组newExpressionStrs的末尾,直到遇到优先级低于扫描到的运算符或栈空,并且把扫描到的运算符压入栈中。
-
4、就这样依次扫描,知道结束为止。如果扫描结束,栈中还有元素,则依次弹出加到数组newExpressionStrs的末尾,就得到了后缀表达式。
相应代码如下:
/**
* Created by 阿茂 on 2017/6/1.
*/
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class MyBC {
/**
优先级比较
return 小于等于返回false,大于返回true
*/
public boolean comparePrior(String operator1, String operator2) {
if("(".equals(operator2)) {
return true;
}
if ("*".equals(operator1) || "/".equals(operator1)) {
if ("+".equals(operator2) || "-".equals(operator2)) {
return true;
}
}
return false;
}
public String[] toSuffixExpression(String[] expressionStrs) {
//新组成的表达式
List<String> newExpressionStrs = new ArrayList<String>();
Stack<String> stack = new Stack<String>();
for (int i = 0; i < expressionStrs.length; i++) {
if ("(".equals(expressionStrs[i])) { // 如果是左括号,则入栈
stack.push(expressionStrs[i]);
} else if ("+".equals(expressionStrs[i]) || "-".equals(expressionStrs[i]) || "*".equals(expressionStrs[i]) || "/".equals(expressionStrs[i])) {
if (!stack.empty()) { // 取出先入栈的运算符
String s = stack.pop();
if(comparePrior(expressionStrs[i], s)) { //如果栈值优先级小于要入栈的值,则继续压入栈
stack.push(s);
} else { //否则取出值
newExpressionStrs.add(s);
}
}
stack.push(expressionStrs[i]);
} else if (")".equals(expressionStrs[i])) { //如果是")",则出栈,一直到遇到"("
while (!stack.empty()) {
String s = stack.pop();
if (!"(".equals(s)) {
newExpressionStrs.add(s);
} else {
break;
}
}
} else {
newExpressionStrs.add(expressionStrs[i]);
}
}
while (!stack.empty()) {
String s = stack.pop();
newExpressionStrs.add(s);
}
return newExpressionStrs.toArray(new String[0]);
}
**然后实现对MyDC的调用:
- 新建MyDC对象,然后使用其
evaluate
方法 - 使用了两个测试用例
public static void main(String[] args) {
//前台传过来的字符格式,所以测试也写成这个格式
String expressionStr = "5;+;(;4;-;5;+;1;);-;4;+;(;6;-;5;+;3;);+;2;";
// 分割成表达式数组
String[] expressionStrs = expressionStr.split(";");
System.out.println("测试一");
System.out.print("前缀表达式为:");
for (int i = 0; i < expressionStrs.length; i++) {
System.out.print(expressionStrs[i]);
}
System.out.println();
String[] newExpressionStrs = new MyBC().toSuffixExpression(expressionStrs);
String str1 = "",str2 = "";
for (int j = 0; j < newExpressionStrs.length; j++) {
//System.out.print(newExpressionStrs[j]);
str1+=newExpressionStrs[j];
str1+=" ";
}
System.out.println("后缀表达式为:" + str1);
MyDC test1 = new MyDC();
int answer1 = test1.evaluate(str1);
System.out.println("计算结果为:" + answer1);
System.out.println();
expressionStr = "5;+;(;4;-;5;*;1;);-;4;/;(;6;*;5;+;3;);/;2;";
expressionStrs = expressionStr.split(";");
System.out.println("测试二");
System.out.print("前缀表达式为:");
for (int i = 0; i < expressionStrs.length; i++) {
System.out.print(expressionStrs[i]);
}
newExpressionStrs = new MyBC().toSuffixExpression(expressionStrs);
for (int k = 0; k < newExpressionStrs.length; k++) {
//System.out.print(newExpressionStrs[k]);
str2+=newExpressionStrs[k];
str2+=" ";
}
System.out.println();
System.out.println("后缀表达式为:" + str2);
MyDC test2 = new MyDC();
int answer2 = test2.evaluate(str2);
System.out.println("计算结果为:" + answer2);
}
}
结果截图:
实践二
- 注意责任归宿,要会通过测试证明自己没有问题
- 基于Java Socket实现客户端/服务器功能,传输方式用TCP
- 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式通过网络发送给服务器
- 服务器接收到后缀表达式,调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
- 客户端显示服务器发送过来的结果
1. 相关概念
基于Java Socket: 套接字是一个网络连接的端点。在java中,使用java.net.Socket对象来表示一个套接字。 Socket(java.lang.String host, int port)。 - host是远程机器名或IP地址 - port是远程应用程序的端口号一旦成功创建了Socket类的一个实例,就可以使用它发送或接收字节流。要发送字节流,必须先调用Socket类的getOutputStream方法来获取一个java.io.OutputStream
对象。要向远程应用程序发送文本,通常要从返回的OutputStream对象构建一个java.io.PrintWriter
对象。要接收来自连接的另一端的字节流,可以调用Socket类的getInputStream方法,它返回一个java.io.InputStream
。
2. 代码实现
我负责的是服务器的代码编写,主体代码如下: // 注意服务器地址一定要是自己电脑连接的
// 端口可以自己设置,只要不占用一些专用端口就好
Socket socket=new Socket("172.16.2.25",5322);
System.out.println("客户端启动成功");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 由系统标准输入设备构造BufferedReader对象
PrintWriter write = new PrintWriter(socket.getOutputStream());
// 由Socket对象得到输出流,并构造PrintWriter对象
// 获取输入流,并读取服务器端的响应信息
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String readline;
readline = br.readLine();
while (!readline.equals("end")) {
write.println(readline);
// 将从系统标准输入读入的字符串输出到Server
write.flush();
// 刷新输出流,使Server马上收到该字符串
System.out.println("Client:" + readline);
// 在系统标准输出上打印读入的字符串
System.out.println("Server:" + in.readLine());
// 从Server读入一字符串,并打印到标准输出上
readline = br.readLine(); // 从系统标准输入读入一字符串
}
write.close(); // 关闭Socket输出流
in.close(); // 关闭Socket输入流
socket.close(); // 关闭Socket
执行结果如图:
实践三
- 注意责任归宿,要会通过测试证明自己没有问题
- 基于Java Socket实现客户端/服务器功能,传输方式用TCP
. 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式用3DES或AES算法加密后通过网络把密文发送给服务器 - 服务器接收到后缀表达式表达式后,进行解密(和客户端协商密钥,可以用数组保存),然后调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
- 客户端显示服务器发送过来的结果
1. 相关概念
这里加解密的代码参考娄老师的[Java 密码学算法](http://www.cnblogs.com/rocedu/p/6683948.html),这里不再过多赘述。2. 代码实现
我们使用的DES加密算法,我负责的是服务器部分的编写,在实践二的代码基础上,进行了一定的修改,主要修改的代码是对服务器收到的明文进行接收和解密: ServerSocket server=null;
try{
server=new ServerSocket(5204);
System.out.println("服务器启动成功");
}catch(Exception e) {
System.out.println("没有启动监听:"+e);
}
Socket socket=null;
try{
socket=server.accept();
}catch(Exception e) {
System.out.println("Error."+e);
}
String line;
BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer=new PrintWriter(socket.getOutputStream());
line=in.readLine();
System.out.printf("密文=%s\n",line);
// 获取密钥
byte[]ctext=line.getBytes("ISO-8859-1");
FileInputStream f2=new FileInputStream("keykb1.dat");
int num2=f2.available();
byte[ ] keykb=new byte[num2];
System.out.printf("\n");
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");
实现效果如下:
实践四
- 注意责任归宿,要会通过测试证明自己没有问题
- 基于Java Socket实现客户端/服务器功能,传输方式用TCP
- 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式用3DES或AES算法加密通过网络把密文发送给服务器
- 客户端和服务器用DH算法进行3DES或AES算法的密钥交换
- 服务器接收到后缀表达式表达式后,进行解密,然后调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
- 客户端显示服务器发送过来的结果
1. 相关概念
这里我参考[密钥交换算法DH(Java实现)](http://www.2cto.com/kf/201611/565578.html)等一系列文章,DH算法大致步骤如下: - 1)由消息发送的一方构建密钥,这里由甲方构建密钥。-
2)由构建密钥的一方向对方公布其公钥,这里由甲方向乙方发布公钥。
-
3)由消息接收的一方通过对方公钥构建自身密钥,这里由乙方使用甲方公钥构建乙方密钥。
-
4)由消息接收的一方向对方公布其公钥,这里由乙方向甲方公布公钥。
2. 代码实现
这里在实践三的基础上,我对原有代码进行了修改,主要的修改部分如下:
public void oneServer(String []args) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
MyDC mydc=new MyDC();
try{
ServerSocket server=null;
try{
server=new ServerSocket(5322);
System.out.println("服务器启动成功");
}catch(Exception e) {
System.out.println("没有启动监听:"+e);
}
Socket socket=null;
try{
socket=server.accept();
}catch(Exception e) {
System.out.println("Error."+e);
}
String line;
BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer=new PrintWriter(socket.getOutputStream());
line=in.readLine();
System.out.printf("密文=%s\n",line);
// 获取密钥
byte[]ctext=line.getBytes("ISO-8859-1");
// 读取对方的DH公钥
FileInputStream f1=new FileInputStream(args[0]);
ObjectInputStream b1=new ObjectInputStream(f1);
PublicKey pbk=(PublicKey)b1.readObject( );
//读取自己的DH私钥
FileInputStream f2=new FileInputStream(args[1]);
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();
byte[]ssb=new byte[24];
for(int i=0;i<24;i++)
ssb[i]=sb[i];
SecretKeySpec k=new SecretKeySpec(ssb,"DESede");
// 解密
Cipher cp=Cipher.getInstance("DESede");
cp.init(Cipher.DECRYPT_MODE, k);
byte []ptext=cp.doFinal(ctext);
// 显示明文
String p=new String(ptext,"UTF8");
运行效果如下:
实践五
- 注意责任归宿,要会通过测试证明自己没有问题
- 基于Java Socket实现客户端/服务器功能,传输方式用TCP
- 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式用3DES或AES算法加密通过网络把密文和明文的MD5値发送给服务器
- 客户端和服务器用DH算法进行3DES或AES算法的密钥交换
- 服务器接收到后缀表达式表达式后,进行解密,解密后计算明文的MD5值,和客户端传来的MD5进行比较,一致则调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
相关概念
1. 参考[MD5算法原理](http://blog.csdn.net/forgotaboutgirl/article/details/7258109) **MD5功能:** - 输入任意长度的信息,经过处理,输出为128位的信息(数字指纹); - 不同的输入得到的不同的结果(唯一性); - 根据128位的输出结果不可能反推出输入的信息(不可逆); 2. 参考[MD5的java两种实现方法](http://sakajiaofu.iteye.com/blog/1654357) 参考代码:import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class TestMain {
public String md5(String plainText) throws NoSuchAlgorithmException, UnsupportedEncodingException {
// a b c d e f 也可以改成大写的 A B C D E F
char[] feedArray = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
MessageDigest msgDigest = MessageDigest.getInstance("MD5");
msgDigest.update(plainText.getBytes("UTF-8"));
byte[] bytes = msgDigest.digest();
int length = bytes.length;
char[] out = new char[length << 1];
for (int i = 0, j = 0; i < length; i++) {
out[j++] = feedArray[(0xF0 & bytes[i]) >>> 4];
out[j++] = feedArray[0xF0 & bytes[i]];
}
String md5Str = new String(out);
return md5Str;
}
}
2. 代码实现
通过对实践四的代码进行修改,增加了**计算明文的MD5值**并**与客户端比较**这两个功能:public void oneServer() throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
MyDC mydc=new MyDC();
try{
ServerSocket server=null;
try{
server=new ServerSocket(5209);
System.out.println("服务器启动成功");
}catch(Exception e) {
System.out.println("没有启动监听:"+e);
}
Socket socket=null;
try{
socket=server.accept();
}catch(Exception e) {
System.out.println("Error."+e);
}
String line;
BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer=new PrintWriter(socket.getOutputStream());
line=in.readLine();
String line1=in.readLine();
System.out.printf("密文=%s\n",line);
// 获取密钥
byte[]ctext=line.getBytes("ISO-8859-1");
FileInputStream f2=new FileInputStream("keykb1.dat");
int num2=f2.available();
byte[ ] keykb=new byte[num2];
System.out.printf("\n");
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");
int a;
System.out.println("明文:"+p);
String Np="";
for(int i=1;i<p.length();i++)
Np+=p.charAt(i);
a=mydc.evaluate(Np);
String x=p;
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);
}
if(!(line1.equals(result)))System.out.printf("MD5比对正确!\n");
writer.println(a);
writer.flush();
writer.close();
in.close();
socket.close();
server.close();
}catch(Exception e) {
System.out.println("Error."+e);
}
实现效果:
实验心得与体会
这一次的实验是前几次实验的一个总结,几乎把前几次实验中所学到的东西实践了一遍,花费的时间较多,主要花在实现功能这一块,再加上期末将至,事情比较多,代码质量比较低。