目录

  • 北京电子科技学院(BESTI)实验报告
  • 实验名称:实验五 网络编程与安全
  • 实验内容、步骤与体会:
    • 零、"两人一组"结对对象
    • 一、实验五 网络编程与安全-1
      • 代码
      • 过程
      • 截图
    • 二、实验五 网络编程与安全-2
      • 代码
      • 过程
      • 截图
    • 三、实验五 网络编程与安全-3
      • 代码
      • 过程
      • 截图
    • 四、实验五 网络编程与安全-4
      • 代码
      • 过程
      • 截图
    • 五、实验四 Android程序设计-5
      • 代码
      • 过程
      • 截图
    • 六、实验过程中遇到的问题以及解决方案
      • 1. 问题:实际运用 DH 算法时,使用密钥超过 JDK 默认密钥大小。
      • 2. 问题:通过 out.writeUTF(); 传输后无法正确接收,变为乱码
    • 七、代码链接
    • 八、 PSP

北京电子科技学院(BESTI)实验报告

课程:Java2实用教程 班级:201752 姓名:姚明宇 学号:20175223
成绩: 指导教师:娄嘉鹏 实验日期:5月31日
实验密级: 预习程度: 实验时间:
仪器组次: 必修/选修:选修 实验序号:

目录

实验名称:实验五 网络编程与安全

实验仪器:

名称 型号 数量
PC端 1

实验内容、步骤与体会:


零、"两人一组"结对对象

20175233严顺尧-负责客户端
20175223姚明宇-负责服务器


一、实验五 网络编程与安全-1

两人一组结对编程:

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

代码

(代码已折叠)

MyBC.java


import java.util.*;
import java.util.stream.Collectors;

public class MyBC{
private static final Map<Character, Integer> basic = new HashMap<Character, Integer>();
static {
basic.put('-', 1);
basic.put('+', 1);
basic.put('*', 2);
basic.put('/', 2);
basic.put('(', 0);
}

//中缀表达式 转 后缀表达式public static String toSuffix(String infix){List<String> queue = new ArrayList<String>();List<Character> stack = new ArrayList<Character>();char[] charArr = infix.trim().toCharArray();String standard = "*/+-()";char ch = '&';int len = 0;for (int i = 0; i < charArr.length; i++) {ch = charArr[i];if(Character.isDigit(ch)) {len++;}else if(Character.isLetter(ch)) {len++;}else if(ch == '.'){len++;}else if(Character.isSpaceChar(ch)) {if(len > 0) {queue.add(String.valueOf(Arrays.copyOfRange(charArr, i - len, i)));len = 0;}continue;}else if(standard.indexOf(ch) != -1) {if(len > 0) {queue.add(String.valueOf(Arrays.copyOfRange(charArr, i - len, i)));len = 0;}if(ch == '(') {stack.add(ch);continue;}if (!stack.isEmpty()) {int size = stack.size() - 1;boolean flag = false;while (size >= 0 && ch == ')' && stack.get(size) != '(') {queue.add(String.valueOf(stack.remove(size)));size--;flag = true;}while (size >= 0 && !flag && basic.get(stack.get(size)) >= basic.get(ch)) {queue.add(String.valueOf(stack.remove(size)));size--;}}if(ch != ')') {stack.add(ch);} else {stack.remove(stack.size() - 1);}}if(i == charArr.length - 1) {if(len > 0) {queue.add(String.valueOf(Arrays.copyOfRange(charArr, i - len+1, i+1)));}int size = stack.size() - 1;while (size >= 0) {queue.add(String.valueOf(stack.remove(size)));size--;}}}return queue.stream().collect(Collectors.joining(" "));
}

}

MyDC.java


import java.util.StringTokenizer;
import java.util.Stack;public class MyDC {/*** 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*/private Stack  stack;public MyDC() {stack = new Stack  ( );}public int evaluate(String expr) {int op1, op2, result = 0;String token;StringTokenizer tokenizer = new StringTokenizer (expr);while (tokenizer.hasMoreTokens ( )) {token = tokenizer.nextToken ( );//如果是运算符,调用isOperatorif (isOperator(token)==true) {op2=stack.pop();op1=stack.pop();//从栈中弹出操作数2//从栈中弹出操作数1result=evalSingleOp(token.charAt(0),op1,op2);//根据运算符和两个操作数调用evalSingleOp计算result;stack.push(result);//计算result入栈;}else//如果是操作数{stack.push(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;break;default:return 0;}return result;}
}

Client.java


import java.util.Scanner;public class MyBCTest {public static void main(String[] args) {MyBC mybc = new MyBC ();MyDC mydc = new MyDC ();String inExpression;String str;Scanner reader = new Scanner (System.in);System.out.println ("Enter a expression: ");inExpression = reader.nextLine ();str = mybc.toSuffix (inExpression);System.out.println ("Exchange the expression by MyBC: " +str);System.out.println ("The calculation result of MyDC: " +mydc.evaluate(str));}
}

过程

实现后缀表达式求值的功能:

MyDC evaluator = new MyDC ( );
//用 Scanner 输入 evaluator 的内容
String result = evaluator.evaluate (expression);
//输出 result

截图


二、实验五 网络编程与安全-2

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

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

代码

(代码已折叠)

Client.java


import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client {
public static void main(String args[]) {
Scanner reader = new Scanner (System.in);
System.out.println ("客户输入一个中缀表达式: ");
String str = reader.nextLine ();
Socket mysocket;
DataInputStream in = null;
DataOutputStream out = null;
try {
mysocket = new Socket ("127.0.0.1", 2010);
in = new DataInputStream (mysocket.getInputStream ( ));
out = new DataOutputStream (mysocket.getOutputStream ( ));
out.writeUTF (str);
//in读取信息,堵塞状态
String temp = in.readUTF ( );
System.out.println ("客户收到服务器的后缀表达式:\n" + temp);
String answer = in.readUTF ( );
System.out.println ("客户收到服务器的计算结果:\n" + answer);
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[]) {String question, temp, answer;MyDC mydc = new MyDC ();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 ( ));// in读取信息,堵塞状态question = in.readUTF ( );System.out.println ("服务器收到客户的中缀表达式:\n" + question);temp = MyBC.toSuffix (question);System.out.println ("服务器将中缀表达式变形为后缀表达式:\n" +temp);out.writeUTF (temp);answer = String.valueOf(mydc.evaluate(temp));out.writeUTF (answer);Thread.sleep (500);} catch (Exception e) {System.out.println ("客户已断开" + e);}}
}

过程

1.服务器建立链接。

ServerSocket serverForClient = new ServerSocket (2010);;
Socket socketOnServer = null;
socketOnServer = serverForClient.accept ( );

客户端建立对应链接相连。
Socket mysocket = new Socket ("127.0.0.1", 2010);
再创建 in , out 对象,使用 in.readUTF(); out.writeUTF(); 进行数据交换。
2.服务器实现把中缀表达式转化为后缀表达式的功能:
String temp = MyBC.toSuffix (question);
注:由于 MyBC 类中的 toSuffix() 方法为 public static 静态方法,可以直接通过类名调用。
3.服务器实现后缀表达式求值的功能:

MyDC str = new MyDC ( );
String result = str.evaluate (expression);

4.服务器通过 out.writeUTF(result); 输出 result;客户端通过 in.readUTF(); 接受后,打印输出。

截图



三、实验五 网络编程与安全-3

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

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

代码

(代码已折叠)

AES.java


import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class AES {
public static String ecodes(String content, String key) {
if (content == null || content.length ( ) < 1) {
return null;
}
try {
KeyGenerator kgen = KeyGenerator.getInstance ("AES");
SecureRandom random = SecureRandom.getInstance ("SHA1PRNG");
random.setSeed (key.getBytes ( ));
kgen.init (128, random);
SecretKey secretKey = kgen.generateKey ( );
byte[] enCodeFormat = secretKey.getEncoded ( );
SecretKeySpec secretKeySpec = new SecretKeySpec (enCodeFormat, "AES");
Cipher cipher = Cipher.getInstance ("AES");
byte[] byteContent = content.getBytes ("utf-8");
cipher.init (Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] byteRresult = cipher.doFinal (byteContent);
StringBuffer sb = new StringBuffer ( );
for (int i = 0; i < byteRresult.length; i++) {
String hex = Integer.toHexString (byteRresult[i] & 0xFF);
if (hex.length ( ) == 1) {
hex = '0' + hex;
}
sb.append (hex.toUpperCase ( ));
}
return sb.toString ( );
} catch (NoSuchAlgorithmException e) {
e.printStackTrace ( );
} catch (NoSuchPaddingException e) {
e.printStackTrace ( );
} catch (InvalidKeyException e) {
e.printStackTrace ( );
} catch (UnsupportedEncodingException e) {
e.printStackTrace ( );
} catch (IllegalBlockSizeException e) {
e.printStackTrace ( );
} catch (BadPaddingException e) {
e.printStackTrace ( );
}
return null;
}

public static String dcodes(String content, String key) {if (content == null || content.length ( ) < 1) {return null;}if (content.trim ( ).length ( ) < 19) {return content;}byte[] byteRresult = new byte[content.length ( ) / 2];for (int i = 0; i < content.length ( ) / 2; i++) {int high = Integer.parseInt (content.substring (i * 2, i * 2 + 1), 16);int low = Integer.parseInt (content.substring (i * 2 + 1, i * 2 + 2), 16);byteRresult[i] = (byte) (high * 16 + low);}try {KeyGenerator kgen = KeyGenerator.getInstance ("AES");SecureRandom random = SecureRandom.getInstance ("SHA1PRNG");random.setSeed (key.getBytes ( ));kgen.init (128, random);SecretKey secretKey = kgen.generateKey ( );byte[] enCodeFormat = secretKey.getEncoded ( );SecretKeySpec secretKeySpec = new SecretKeySpec (enCodeFormat, "AES");Cipher cipher = Cipher.getInstance ("AES");cipher.init (Cipher.DECRYPT_MODE, secretKeySpec);byte[] result = cipher.doFinal (byteRresult);return new String (result);} 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 ( );}return null;
}

}

ServerAES.java


import java.io.*;
import java.net.*;public class ServerAES {public static void main(String args[]) {String miwen, temp, answer;MyDC mydc = new MyDC ( );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 ( ));// in读取信息,堵塞状态miwen = in.readUTF ( );System.out.println ("服务器收到客户的密文:\n" + miwen);String key = "20175223yaomingyushidashuaibi111";String mingwen = AES.dcodes (miwen, key);answer = String.valueOf (mydc.evaluate (mingwen));out.writeUTF (answer);System.out.println ("\n**计算结果由客户端打印输出**");Thread.sleep (500);} catch (Exception e) {System.out.println ("客户已断开" + e);}}
}

ClientAES.java


import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Scanner;public class ClientAES {public static void main(String args[]) {Scanner reader = new Scanner (System.in);System.out.println ("客户输入一个中缀表达式: ");String str = reader.nextLine ( );Socket mysocket;DataInputStream in = null;DataOutputStream out = null;try {mysocket = new Socket ("127.0.0.1", 2010);in = new DataInputStream (mysocket.getInputStream ( ));out = new DataOutputStream (mysocket.getOutputStream ( ));String key = "20175223yaomingyushidashuaibi111";//将中缀表达式变形为后缀表达式String temp = MyBC.toSuffix (str);System.out.println ("服务器将中缀表达式变形为后缀表达式:\n" + temp);//输入密文,32字符密钥String miwen = AES.ecodes (temp, key);System.out.println ("客户发往服务器的密文:\n" + miwen + "\n");out.writeUTF (miwen);//in读取信息,堵塞状态String answer = in.readUTF ( );System.out.println ("客户收到服务器的计算结果:\n" + answer);Thread.sleep (500);} catch (Exception e) {System.out.println ("服务器已断开" + e);}}
}

过程

1.首先,服务器和客户端协商密钥为:String key = "20175223yaomingyushidashuaibi111"; ,各自存在本地;
AES算法及其相关方法通过 AES.java 实现。
2.客户端实现把中缀表达式转化为后缀表达式的功能(同 网络编程与安全-2),将后缀表达式明文加密:

String  = MyBC.toSuffix (str);
String miwen = AES.ecodes(mingwen,key);

再发往服务器。
3.服务器将接收到的密文用密钥解密,调用方法 mydc.evaluate() 计算后缀表达式:

String mingwen = AES.dcodes(miwen, key);
String answer = String.valueOf(mydc.evaluate(mingwen));

4.服务器将答案发往客户端,由客户端打印输出。

截图




四、实验五 网络编程与安全-4

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

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

代码

(代码已折叠)

DH.java


import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**

  • 非对称加密算法DH算法组件
  • 非对称算法一般是用来传送对称加密算法的密钥来使用的,所以这里我们用DH算法模拟密钥传送
  • 对称加密AES算法继续做我们的数据加解密
  • @author kongqz
  • */
    public class DH {
    //非对称密钥算法
    public static final String KEY_ALGORITHM="DH";

    //本地密钥算法,即对称加密算法。可选des,aes,desede
    public static final String SECRET_ALGORITHM="AES";

    /**

    • 密钥长度,DH算法的默认密钥长度是1024
    • 密钥长度必须是64的倍数,在512到1024位之间
    • */
      private static final int KEY_SIZE=512;
      //公钥
      private static final String PUBLIC_KEY="DHPublicKey";

    //私钥
    private static final String PRIVATE_KEY="DHPrivateKey";

    /**

    • 初始化甲方密钥
    • @return Map 甲方密钥的Map
    • */
      public static Map<String,Object> initKey() throws Exception{
      //实例化密钥生成器
      KeyPairGenerator keyPairGenerator=KeyPairGenerator.getInstance(KEY_ALGORITHM);
      //初始化密钥生成器
      keyPairGenerator.initialize(KEY_SIZE);
      //生成密钥对
      KeyPair keyPair=keyPairGenerator.generateKeyPair();
      //甲方公钥
      DHPublicKey publicKey=(DHPublicKey) keyPair.getPublic();
      //甲方私钥
      DHPrivateKey privateKey=(DHPrivateKey) keyPair.getPrivate();
      //将密钥存储在map中
      Map<String,Object> keyMap=new HashMap<String,Object>();
      keyMap.put(PUBLIC_KEY, publicKey);
      keyMap.put(PRIVATE_KEY, privateKey);
      return keyMap;

    }

    /**

    • 初始化乙方密钥
    • @param key 甲方密钥(这个密钥是通过第三方途径传递的)
    • @return Map 乙方密钥的Map
    • */
      public static Map<String,Object> initKey(byte[] key) throws Exception{
      //解析甲方的公钥
      //转换公钥的材料
      X509EncodedKeySpec x509KeySpec=new X509EncodedKeySpec(key);
      //实例化密钥工厂
      KeyFactory keyFactory=KeyFactory.getInstance(KEY_ALGORITHM);
      //产生公钥
      PublicKey pubKey=keyFactory.generatePublic(x509KeySpec);
      //由甲方的公钥构造乙方密钥
      DHParameterSpec dhParamSpec=((DHPublicKey)pubKey).getParams();
      //实例化密钥生成器
      KeyPairGenerator keyPairGenerator=KeyPairGenerator.getInstance(keyFactory.getAlgorithm());
      //初始化密钥生成器
      keyPairGenerator.initialize(dhParamSpec);
      //产生密钥对
      KeyPair keyPair=keyPairGenerator.genKeyPair();
      //乙方公钥
      DHPublicKey publicKey=(DHPublicKey)keyPair.getPublic();
      //乙方私钥
      DHPrivateKey privateKey=(DHPrivateKey)keyPair.getPrivate();
      //将密钥存储在Map中
      Map<String,Object> keyMap=new HashMap<String,Object>();
      keyMap.put(PUBLIC_KEY, publicKey);
      keyMap.put(PRIVATE_KEY, privateKey);
      return keyMap;
      }
      /**
    • 加密
    • @param data 待加密数据
    • @param key 密钥
    • @return byte[] 加密数据
    • */
      public static byte[] encrypt(byte[] data,byte[] key) throws Exception{
      //生成本地密钥
      SecretKey secretKey=new SecretKeySpec(key,SECRET_ALGORITHM);
      //数据加密
      Cipher cipher=Cipher.getInstance(secretKey.getAlgorithm());
      cipher.init(Cipher.ENCRYPT_MODE, secretKey);
      return cipher.doFinal(data);
      }
      /**
    • 解密
    • @param data 待解密数据
    • @param key 密钥
    • @return byte[] 解密数据
    • */
      public static byte[] decrypt(byte[] data,byte[] key) throws Exception{
      //生成本地密钥
      SecretKey secretKey=new SecretKeySpec(key,SECRET_ALGORITHM);
      //数据解密
      Cipher cipher=Cipher.getInstance(secretKey.getAlgorithm());
      cipher.init(Cipher.DECRYPT_MODE, secretKey);
      return cipher.doFinal(data);
      }
      /**
    • 构建密钥
    • @param publicKey 公钥
    • @param privateKey 私钥
    • @return byte[] 本地密钥
    • */
      public static byte[] getSecretKey(byte[] publicKey,byte[] privateKey) throws Exception{
      //实例化密钥工厂
      KeyFactory keyFactory=KeyFactory.getInstance(KEY_ALGORITHM);
      //初始化公钥
      //密钥材料转换
      X509EncodedKeySpec x509KeySpec=new X509EncodedKeySpec(publicKey);
      //产生公钥
      PublicKey pubKey=keyFactory.generatePublic(x509KeySpec);
      //初始化私钥
      //密钥材料转换
      PKCS8EncodedKeySpec pkcs8KeySpec=new PKCS8EncodedKeySpec(privateKey);
      //产生私钥
      PrivateKey priKey=keyFactory.generatePrivate(pkcs8KeySpec);
      //实例化
      KeyAgreement keyAgree=KeyAgreement.getInstance(keyFactory.getAlgorithm());
      //初始化
      keyAgree.init(priKey);
      keyAgree.doPhase(pubKey, true);
      //生成本地密钥
      SecretKey secretKey=keyAgree.generateSecret(SECRET_ALGORITHM);
      return secretKey.getEncoded();
      }
      /**
    • 取得私钥
    • @param keyMap 密钥map
    • @return byte[] 私钥
    • */
      public static byte[] getPrivateKey(Map<String,Object> keyMap){
      Key key=(Key)keyMap.get(PRIVATE_KEY);
      return key.getEncoded();
      }
      /**
    • 取得公钥
    • @param keyMap 密钥map
    • @return byte[] 公钥
    • */
      public static byte[] getPublicKey(Map<String,Object> keyMap) throws Exception{
      Key key=(Key) keyMap.get(PUBLIC_KEY);
      return key.getEncoded();
      }
      /**
    • @param args
    • @throws Exception
      */
      public static void main(String[] args) throws Exception {
      //生成甲方的密钥对
      Map<String,Object> keyMap1=DH.initKey();
      //甲方的公钥
      byte[] publicKey1=DH.getPublicKey(keyMap1);

      //甲方的私钥
      byte[] privateKey1=DH.getPrivateKey(keyMap1);
      System.out.println("甲方公钥:/n"+Base64.encodeBase64String(publicKey1));
      System.out.println("甲方私钥:/n"+Base64.encodeBase64String(privateKey1));

      //由甲方的公钥产生的密钥对
      Map<String,Object> keyMap2=DH.initKey(publicKey1);
      byte[] publicKey2=DH.getPublicKey(keyMap2);
      byte[] privateKey2=DH.getPrivateKey(keyMap2);
      System.out.println("乙方公钥:/n"+Base64.encodeBase64String(publicKey2));
      System.out.println("乙方私钥:/n"+Base64.encodeBase64String(privateKey2));

      //组装甲方的本地加密密钥,由乙方的公钥和甲方的私钥组合而成
      byte[] key1=DH.getSecretKey(publicKey2, privateKey1);
      System.out.println("甲方的本地密钥:/n"+Base64.encodeBase64String(key1));

      //组装乙方的本地加密密钥,由甲方的公钥和乙方的私钥组合而成
      byte[] key2=DH.getSecretKey(publicKey1, privateKey2);
      System.out.println("乙方的本地密钥:/n"+Base64.encodeBase64String(key2));

      System.out.println("================密钥对构造完毕,开始进行加密数据的传输=============");
      String str="密码交换算法";
      System.out.println("/n===========甲方向乙方发送加密数据==============");
      System.out.println("原文:"+str);
      System.out.println("===========使用甲方本地密钥对进行数据加密==============");
      //甲方进行数据的加密
      byte[] code1=DH.encrypt(str.getBytes(), key1);
      System.out.println("加密后的数据:"+Base64.encodeBase64String(code1));

      System.out.println("===========使用乙方本地密钥对数据进行解密==============");
      //乙方进行数据的解密
      byte[] decode1=DH.decrypt(code1, key2);
      System.out.println("乙方解密后的数据:"+new String(decode1)+"/n/n");

      System.out.println("===========反向进行操作,乙方向甲方发送数据==============/n/n");

      str="乙方向甲方发送数据DH";

      System.out.println("原文:"+str);

      //使用乙方本地密钥对数据进行加密
      byte[] code2=DH.encrypt(str.getBytes(), key2);
      System.out.println("===========使用乙方本地密钥对进行数据加密==============");
      System.out.println("加密后的数据:"+Base64.encodeBase64String(code2));

      System.out.println("=============乙方将数据传送给甲方======================");
      System.out.println("===========使用甲方本地密钥对数据进行解密==============");

      //甲方使用本地密钥对数据进行解密
      byte[] decode2=DH.decrypt(code2, key1);

      System.out.println("甲方解密后的数据:"+new String(decode2));
      }
      }

ClientDH.java


import org.apache.commons.codec.binary.Base64;import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Map;
import java.util.Scanner;public class ClientDH {public static void main(String args[]) {String cipherText, plainText, answer;Scanner reader = new Scanner (System.in);System.out.println ("客户输入一个中缀表达式: ");String str = reader.nextLine ();Socket mysocket;DataInputStream in = null;DataOutputStream out = null;try {mysocket = new Socket ("127.0.0.1", 2010);in = new DataInputStream (mysocket.getInputStream ( ));out = new DataOutputStream (mysocket.getOutputStream ( ));//设立AES算法的32字符密钥,输入密文与密钥String AES_Key = "20175223yaomingyushidashuaibi111";//将中缀表达式变形为后缀表达式plainText = MyBC.toSuffix (str);System.out.println ("后缀表达式明文:\n" + plainText);//将后缀表达式明文通过AES加密,并将后缀表达式密文发往客户端cipherText = AES.ecodes (plainText, AES_Key);System.out.println ("后缀表达式密文:\n" + cipherText + "\n");//out发送信息out.writeUTF (cipherText);//对AES算法的32字符密钥进行DH算法加密//生成客户端的密钥对Map  keyMap1 = DH.initKey ( );//客户端的公钥byte[] publicKey1 = DH.getPublicKey (keyMap1);//客户端的私钥byte[] privateKey1 = DH.getPrivateKey (keyMap1);System.out.println ("客户端公钥:/n" + Base64.encodeBase64String (publicKey1));System.out.println ("客户端私钥:/n" + Base64.encodeBase64String (privateKey1));String tempKey1 = Base64.encodeBase64String (publicKey1);//out发送信息out.writeUTF (tempKey1);//组装客户端的本地加密密钥,由服务器的公钥和客户端的私钥组合而成//in读取信息,堵塞状态String tempKey2 = in.readUTF ( );byte[] publicKey2 = Base64.decodeBase64 (tempKey2);System.out.println ("服务器公钥:/n" + Base64.encodeBase64String (publicKey1));byte[] key1 = DH.getSecretKey (publicKey2, privateKey1);System.out.println ("客户端的本地密钥:/n" + Base64.encodeBase64String (key1));//客户端使用本地密钥对AES_Key进行消息加密,并发给服务器byte[] code1 = DH.encrypt (AES_Key.getBytes ( ), key1);System.out.println ("客户端使用本地密钥对AES_Key进行加密后的数据:" + Base64.encodeBase64String (code1));//out发送信息,333333out.writeUTF (Base64.encodeBase64String (code1));//接受服务器的计算结果answer = in.readUTF ( );System.out.println ("\n**计算由服务器进行**\n\n客户收到服务器的计算结果:\n" + answer);Thread.sleep (500);} catch (Exception e) {System.out.println ("服务器已断开" + e);}}
}

ServerDH.java


import org.apache.commons.codec.binary.Base64;import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;public class ServerDH {public static void main(String args[]) {String cipherText, plainText, answer;MyDC mydc = new MyDC ( );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 ( ));// in读取信息,堵塞状态cipherText = in.readUTF ( );System.out.println ("服务器收到客户的后缀表达式密文:\n" + cipherText + "\n");//由客户端的公钥产生的密钥对// in读取信息,堵塞状态String tempKey1 = in.readUTF ( );byte[] publicKey1 = Base64.decodeBase64 (tempKey1);System.out.println ("客户端公钥:/n" + Base64.encodeBase64String (publicKey1));Map  keyMap2 = DH.initKey (publicKey1);byte[] publicKey2 = DH.getPublicKey (keyMap2);byte[] privateKey2 = DH.getPrivateKey (keyMap2);System.out.println ("服务器公钥:/n" + Base64.encodeBase64String (publicKey2));System.out.println ("服务器私钥:/n" + Base64.encodeBase64String (privateKey2));//为组装客户端的本地加密密钥,将服务器的公钥发给客户端String tempKey2 = Base64.encodeBase64String (publicKey2);//out发送信息out.writeUTF (tempKey2);//组装服务器的本地加密密钥,由客户端的公钥和服务器的私钥组合而成byte[] key2 = DH.getSecretKey (publicKey1, privateKey2);System.out.println ("服务器的本地密钥:/n" + Base64.encodeBase64String (key2));//接受客户端的AES_Key的加密信息,对其解密byte[] code1 = Base64.decodeBase64 (in.readUTF ( ));byte[] decode1 = DH.decrypt (code1, key2);String AES_Key = new String (decode1);System.out.println ("\n服务器解密后的AES_Key数据:" + AES_Key);//使用解密后的AES_Key对后缀表达式密文解密,并算出结果plainText = AES.dcodes (cipherText, AES_Key);System.out.println ("\nAES_Key对后缀表达式密文解密:\n" + plainText);answer = String.valueOf (mydc.evaluate (plainText));//将计算结果发给客户端out.writeUTF (answer);System.out.println ("\n**结果由客户端进行输出**" );Thread.sleep (500);} catch (Exception e) {System.out.println ("客户已断开" + e);}}
}

过程

网络编程与安全-3 基础上,利用成熟的 DH 算法(实现于 DH.java)实现 AES 密钥客户端和服务器加解密,具体步骤如下:

//生成客户端的密钥对
Map<String,Object> keyMap1=DH.initKey();
//客户端的公钥
byte[] publicKey1=DH.getPublicKey(keyMap1);//客户端的私钥
byte[] privateKey1=DH.getPrivateKey(keyMap1);//由客户端的公钥产生的密钥对
Map<String,Object> keyMap2=DH.initKey(publicKey1);
byte[] publicKey2=DH.getPublicKey(keyMap2);
byte[] privateKey2=DH.getPrivateKey(keyMap2);//组装客户端的本地加密密钥,由服务器的公钥和客户端的私钥组合而成
byte[] key1=DH.getSecretKey(publicKey2, privateKey1);//组装服务器的本地加密密钥,由客户端的公钥和服务器的私钥组合而成
byte[] key2=DH.getSecretKey(publicKey1, privateKey2);//客户端进行数据的加密
byte[] code1=DH.encrypt(str.getBytes(), key1);//服务器进行数据的解密
byte[] decode1=DH.decrypt(code1, key2);//使用服务器本地密钥对数据进行加密
byte[] code2=DH.encrypt(str.getBytes(), key2);//客户端使用本地密钥对数据进行解密
byte[] decode2=DH.decrypt(code2, key1);

截图




五、实验四 Android程序设计-5

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

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

代码

(代码已折叠)

MD5.java


import java.security.MessageDigest;

public class MD5 {
public static String numberMD5(String plainText) throws Exception {
String x = plainText;
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;
}

}

ClientMD5.java


import org.apache.commons.codec.binary.Base64;import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Map;
import java.util.Scanner;public class ClientMD5 {public static void main(String args[]) {String cipherText, plainText, answer;Scanner reader = new Scanner (System.in);System.out.println ("客户输入一个中缀表达式: ");String str = reader.nextLine ();Socket mysocket;DataInputStream in = null;DataOutputStream out = null;try {mysocket = new Socket ("127.0.0.1", 2010);in = new DataInputStream (mysocket.getInputStream ( ));out = new DataOutputStream (mysocket.getOutputStream ( ));//设立AES算法的32字符密钥,输入密文与密钥String AES_Key = "20175223yaomingyushidashuaibi111";//将中缀表达式变形为后缀表达式plainText = MyBC.toSuffix (str);System.out.println ("后缀表达式明文:\n" + plainText);//将后缀表达式明文通过AES加密,并将后缀表达式密文发往服务器cipherText = AES.ecodes (plainText, AES_Key);System.out.println ("后缀表达式密文:\n" + cipherText + "\n");//out发送信息out.writeUTF (cipherText);//对AES算法的32字符密钥进行DH算法加密//生成客户端的密钥对Map  keyMap1 = DH.initKey ( );//客户端的公钥byte[] publicKey1 = DH.getPublicKey (keyMap1);//客户端的私钥byte[] privateKey1 = DH.getPrivateKey (keyMap1);System.out.println ("客户端公钥:/n" + Base64.encodeBase64String (publicKey1));System.out.println ("客户端私钥:/n" + Base64.encodeBase64String (privateKey1));String tempKey1 = Base64.encodeBase64String (publicKey1);//out发送信息out.writeUTF (tempKey1);//组装客户端的本地加密密钥,由服务器的公钥和客户端的私钥组合而成//in读取信息,堵塞状态String tempKey2 = in.readUTF ( );byte[] publicKey2 = Base64.decodeBase64 (tempKey2);System.out.println ("服务器公钥:/n" + Base64.encodeBase64String (publicKey1));byte[] key1 = DH.getSecretKey (publicKey2, privateKey1);System.out.println ("客户端的本地密钥:/n" + Base64.encodeBase64String (key1));//客户端使用本地密钥对AES_Key进行消息加密,并发给服务器byte[] code1 = DH.encrypt (AES_Key.getBytes ( ), key1);System.out.println ("客户端使用本地密钥对AES_Key进行加密后的数据:" + Base64.encodeBase64String (code1));//out发送信息out.writeUTF (Base64.encodeBase64String (code1));//计算后缀表达式明文的MD5值,并发往服务器String valueMD5 = MD5.numberMD5 (plainText);System.out.println ("\n客户端计算的MD5值:" + valueMD5);//out发送信息out.writeUTF (valueMD5);//接受服务器的计算结果answer = in.readUTF ( );System.out.println ("\n**计算由服务器进行**\n\n客户收到服务器的计算结果:\n" + answer);Thread.sleep (500);} catch (Exception e) {System.out.println ("服务器已断开" + e);}}
}

ServerMD5.java


import org.apache.commons.codec.binary.Base64;import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
public class ServerMD5 {public static void main(String args[]) {String cipherText, plainText, answer;MyDC mydc = new MyDC ( );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 ( ));// in读取信息,堵塞状态cipherText = in.readUTF ( );System.out.println ("服务器收到客户的后缀表达式密文:\n" + cipherText + "\n");//由客户端的公钥产生的密钥对// in读取信息,堵塞状态String tempKey1 = in.readUTF ( );byte[] publicKey1 = Base64.decodeBase64 (tempKey1);System.out.println ("客户端公钥:/n" + Base64.encodeBase64String (publicKey1));Map  keyMap2 = DH.initKey (publicKey1);byte[] publicKey2 = DH.getPublicKey (keyMap2);byte[] privateKey2 = DH.getPrivateKey (keyMap2);System.out.println ("服务器公钥:/n" + Base64.encodeBase64String (publicKey2));System.out.println ("服务器私钥:/n" + Base64.encodeBase64String (privateKey2));//为组装客户端的本地加密密钥,将服务器的公钥发给客户端String tempKey2 = Base64.encodeBase64String (publicKey2);//out发送信息out.writeUTF (tempKey2);//组装服务器的本地加密密钥,由客户端的公钥和服务器的私钥组合而成byte[] key2 = DH.getSecretKey (publicKey1, privateKey2);System.out.println ("服务器的本地密钥:/n" + Base64.encodeBase64String (key2));//接受客户端的AES_Key的加密信息,对其解密byte[] code1 = Base64.decodeBase64 (in.readUTF ( ));byte[] decode1 = DH.decrypt (code1, key2);String AES_Key = new String (decode1);System.out.println ("\n服务器解密后的AES_Key数据:" + AES_Key);//使用解密后的AES_Key对后缀表达式密文解密,并算出结果plainText = AES.dcodes (cipherText, AES_Key);System.out.println ("\nAES_Key对后缀表达式密文解密:" + plainText);answer = String.valueOf (mydc.evaluate (plainText));//接受客户端的MD5值,计算解密后后缀表达式的MD5值,并判断是否相同,不同则停止String clicetValueMD5 = in.readUTF ();String valueMD5 = MD5.numberMD5 (plainText);if (!clicetValueMD5.equals (valueMD5)) {return;}System.out.println ("\n服务器MD5值:"+valueMD5 +"\n**与客户端相同**");//将计算结果发给客户端out.writeUTF (answer);System.out.println ("\n**结果由客户端进行输出**" );Thread.sleep (500);} catch (Exception e) {System.out.println ("客户已断开" + e);}}
}

过程

1.实现 MD5 算法:
将参考代码 public static void main 改为可调用的静态方法(于上 MD5.java):
public static String numberMD5(String plainText)

参考代码:DigestPass.java


import java.security.*;public class DigestPass{public static void main(String args[ ]) throws Exception{String x=args[0];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);}System.out.println(result);}
}

2.在 网络编程与安全-4 基础上,客户端将后缀表达式明文的MD5值算出,发往服务器:
String valueMD5 = MD5.numberMD5 (plainText);
3.服务器接受客户端MD5值,与自身算出的后缀表达式明文的MD5值比较,若相同则继续:

String clicetValueMD5 = in.readUTF ();
String valueMD5 = MD5.numberMD5 (plainText);
if (!clicetValueMD5.equals (valueMD5)) {return;
}

截图




六、实验过程中遇到的问题以及解决方案

1. 问题:实际运用 DH 算法时,使用密钥超过 JDK 默认密钥大小。

---使用甲方本地密钥对数据进行加密---
Exception in thread "main" java.security.InvalidKeyException: Illegal key size or default parametersat javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1026)at javax.crypto.Cipher.implInit(Cipher.java:801)at javax.crypto.Cipher.chooseProvider(Cipher.java:864)at javax.crypto.Cipher.init(Cipher.java:1249)at javax.crypto.Cipher.init(Cipher.java:1186)at DHCoder.encrypt(DHCoder.java:101)at DHTest.main(DHTest.java:56)Process finished with exit code 1

解决方案:

因为某些国家的进口管制限制,Java发布的运行环境包中的加解密有一定的限制。比如默认不允许256位密钥的AES加解密,解决方法就是修改策略文件。
下载与JDK或JRE对应版本的jce文件包,当前机器的jdk为1.8,所以下载jce_policy-8.zip。
将解压得到的两个jar文件 local_policy.jarUS_export_policy.jar 也放到 %JDK_HOME%\jre\lib\security 下,进行替换。
附 jce_policy-8.zip下载链接

2. 问题:通过 out.writeUTF(); 传输后无法正确接收,变为乱码

客户端:

//通过 `Base64` 的方法 `encodeBase64String ();` 将数组转化为字符串类型
String tempKey1 = Base64.encodeBase64String (publicKey1);
//out发送信息
out.writeUTF (tempKey1);

服务器:

......//由客户端的公钥产生的密钥对
String tempKey1 = in.readUTF ();
byte [] publicKey1 = tempKey1.getBytes ();
System.out.println("客户端公钥:/n"+Base64.encodeBase64String(publicKey1));
Map<String,Object> keyMap2=DH.initKey(publicKey1);
......

报错:密钥格式不对。

解决方案:

因为 out.writeUTF(); in.readUTF (); 传输的数据类型为 String ,而不是 byte [] 数组,所以不可用 getBytes (); 方法来提取。
要使用 Base64 方法 encodeBase64String (); 的对应的方法 decodeBase64 (tempKey1); 来转换数据类型。

String tempKey1 = in.readUTF ( );
byte[] publicKey1 = Base64.decodeBase64 (tempKey1);
System.out.println ("客户端公钥:/n" + Base64.encodeBase64String (publicKey1));
Map <String, Object> keyMap2 = DH.initKey (publicKey1);

七、代码链接

码云仓库:YogileOne https://gitee.com/Yogile/YogileOne.git

码云项目网页链接:https://gitee.com/Yogile/YogileOne/tree/master/str/exam_5


八、 PSP

步骤 耗时 百分比
需求分析 10min 7.7%
设计 30min 23.1%
代码实现 50min 38.5%
测试 30min 23.1%
分析总结 10min 7.6%

转载于:https://www.cnblogs.com/Yogile/p/10938944.html

2018-2019-2 20175223 实验五 《网络编程与安全》实验报告相关推荐

  1. java小球碰撞实验报告_20155317 《Java程序设计》实验五网络编程与安全实验报告...

    20155317 <Java程序设计>实验五网络编程与安全实验报告 遇到问题 在刚开始启动客户端或者服务端时,出现了一系列的错误情况,总是提示异常信息 后来经过询问同学,反应将端口号修改一 ...

  2. 实验五 网络编程与安全-----实验报告

    一.实验五 网络编程与安全-1 1.实验要求: 两人一组结对编程: (1)参考http://www.cnblogs.com/rocedu/p/6766748.html#SECDSA : (2)结对实现 ...

  3. 2017-2018-2 20165329 实验五 网络编程与安全

    2017-2018-2 20165329 实验五 网络编程与安全 实验报告封面 课程:Java程序设计 班级:1653班 姓名:何佳伟 学号:20165329 指导教师:娄嘉鹏 实验日期:2018年5 ...

  4. 20175212童皓桢 实验五 网络编程与安全

    20175212童皓桢 实验五 网络编程与安全 实验内容 结对编程,完成网络编程与安全相关内容 实验步骤 任务一 任务一要求: 结对实现中缀表达式转后缀表达式的功能 MyBC.java 结对实现从上面 ...

  5. java 网络实验_Java实验五网络编程与安全

    实验五 网络编程与安全 实验准备 活动一 两人一组结对编程: 0. 参考http://www.cnblogs.com/rocedu/p/6766748.html#SECDSA 1. 结对实现中缀表达式 ...

  6. 20155207 实验五 网络编程与安全

    20155207 实验五 网络编程与安全 实验内容 任务一 两人一组结对编程: 参考http://www.cnblogs.com/rocedu/p/6766748.html#SECDSA 结对实现中缀 ...

  7. #《JAVA程序设计》 20155214 实验五 网络编程与安全

    <JAVA程序设计> 20155214 实验五 网络编程与安全 实验内容 掌握Socket程序的编写: 掌握密码技术的使用: 设计安全传输系统. 实验要求 要求一 结对实现中缀表达式转后缀 ...

  8. 实验五 网络编程与安全 20162304 张浩林

    实验五 网络编程与安全 实验五 网络编程与安全-1 试验内容 结对实现中缀表达式转后缀表达式的功能 MyBC.java 结对实现从上面功能中获取的表达式中实现后缀表达式求值的功能,调用MyDC.jav ...

  9. 实验五 网络编程与安全

    一.实验报告封面 课程:Java程序设计 班级:1653班 姓名:高君天 学号:20165319 指导教师:娄嘉鹏 实验日期:2018年5月28日 实验时间:13:45 - 3:25 实验序号:实验五 ...

  10. 20165201 实验五 网络编程与安全

    ##20165201 实验五 网络编程与安全 网络编程与安全-1 实验目的与要求: 两人一组结对编程: 参考http://www.cnblogs.com/rocedu/p/6766748.html#S ...

最新文章

  1. 【ACM】CODE[VS] 2806(DFS)
  2. 【图像】jpg与jpeg的区别
  3. C++ Opengl WaveFlag(飘扬的旗帜)源码
  4. 2020 年,为什么非要采用 DevOps 文化不可?
  5. default.html文件,default.html
  6. c++实现web服务框架
  7. 关键时刻不可或缺的5款高科技紧急应用
  8. 【Linux】服务管理命令和压缩管理的详细解读
  9. JavaScript 运行机制详解:再谈Event Loop
  10. Android从assets和res中读取文件
  11. C语言链表的操作和讲解
  12. mysql 表格入门_MySQL-快速入门(1)基本数据库、表操作语句
  13. 电销机器人百度百科_晓芯智能电话机器人百度百科
  14. 华东师范大学副校长周傲英:未来,中国需要什么样的数据库?
  15. php-ews发送邮件,使用php-ews回复电子邮件
  16. python数据分析实战:生存分析与电信用户流失预测
  17. [基础]-requests模块使用详解
  18. 苹果动态壁纸库怎么增加_苹果xr如何添加动态壁纸设置
  19. Android之——模拟实现检测心率变化的应用实例
  20. 五、c++学习(加餐1:汇编基础学习)

热门文章

  1. java练手小程序_Java小程序练习
  2. 拥塞避免算法、快重传、快恢复、慢启动
  3. 设计模式之GOF23责任链模式
  4. 2021-03-08
  5. html中label中的for属性
  6. Elasticsearch2.4.X 搜索引擎框架 安装配置
  7. Android JSON 数据解析 之原生 API
  8. Spring Boot 2.x 切换 Servlet 嵌入式容器 Tomcat、Jetty、Undertow
  9. 使用IDEA 导入桌面的项目(解压之后的项目)
  10. js 图片加载时 按比例设置图片宽高_JS自动等比例缩放图片,判断网页与图片加载完成。...