• 20145221 《Java程序设计》实验报告五:网络编程及安全


    20145221 《Java程序设计》实验报告五:网络编程及安全

    实验要求

    • 掌握Socket程序的编写
      • 运行TCP代码包,结对进行,一人服务器,一人客户端
    • 掌握密码技术的使用
      • 利用加解密代码包,编译运行代码,一人加密,一人解密
    • 设计安全传输系统

    实验内容

    • 客户端(加密)与服务器(解密)连接
    • 密钥的加密:

    发送方A——>接收方B
    A加密时,用B的公钥
    B解密时,用B的私钥

    • 发送方A对信息(明文)采用DES密钥加密,使用RSA公钥加密前面的DES密钥信息,最终将混合信息进行传递。同时用hash函数将明文进行用作验证。
    • 接收方B接收到信息后,用RSA私钥解密DES密钥信息,再用RSA解密获取到的密钥信息解密密文信息,最终就可以得到我们要的信息(明文)。用hash函数对解出的明文进行验证,与发送过来的hash值相等,验证通过。

    实验步骤

    确认服务器与客户端

    客户端(Client):20145221高其

    • 客户端(Client)是指网络编程中首先发起连接的程序,客户端一般实现程序界面和基本逻辑实现,在进行实际的客户端编程时,客户端的编程主要由三个步骤实现:
      • 建立网络连接:连接时需要指定连接到的服务器的IP地址和端口号,建立完成以后,会形成一条虚拟的连接,后续的操作就可以通过该连接实现数据交换了。
      • 交换数据:连接建立以后,就可以通过这个连接交换数据了。交换数据严格按照请求响应模型进行,由客户端发送一个请求数据到服务器,服务器反馈一个响应数据给客户端,如果客户端不发送请求则服务器端就不响应。根据逻辑需要,可以多次交换数据,但是还是必须遵循请求响应模型。
      • 关闭网络连接:在数据交换完成以后,关闭网络连接,释放程序占用的端口、内存等系统资源,结束网络编程。

    服务器(Serve):20145233韩昊辰

    • 服务器端(Server)是指在网络编程中被动等待连接的程序,服务器端一般实现程序的核心逻辑以及数据存储等核心功能。服务器端的编程步骤和客户端不同,是由四个步骤实现,依次是:
      • 监听端口: 服务器端属于被动等待连接,所以服务器端启动以后,不需要发起连接,而只需要监听本地计算机的某个固定端口即可。这个端口就是服务器端开放给客户端的端口,服务器端程序运行的本地计算机的IP地址就是服务器端程序的IP地址。
      • 获得连接:当客户端连接到服务器端时,服务器端就可以获得一个连接,这个连接包含客户端的信息,例如客户端IP地址等等,服务器端和客户端也通过该连接进行数据交换。一般在服务器端编程中,当获得连接时,需要开启专门的线程处理该连接,每个连接都由独立的线程实现。
      • 交换数据:服务器端通过获得的连接进行数据交换。服务器端的数据交换步骤是首先接收客户端发送过来的数据,然后进行逻辑处理,再把处理以后的结果数据发送给客户端。简单来说,就是先接收再发送,这个和客户端的数据交换数序不同。其实,服务器端获得的连接和客户端连接是一样的,只是数据交换的步骤不同。当然,服务器端的数据交换也是可以多次进行的。
      • 关闭连接:当服务器程序关闭时,需要关闭服务器端,通过关闭服务器端使得服务器监听的端口以及占用的内存可以释放出来,实现了连接的关闭。

    设置IP地址

    • Step1:用手机开热点,确保服务器与客户端在同一网络下的同一网段

    • Step2:打开cmd,输入ipconfig指令

    • Step3:找到上图中无线局域网适配器 WLAN -> IPv4 地址,后面的值:192.168.43.105,就是计算机在该局域网中的IP地址。

    • Step4:服务器用同样的方法获得的IP地址后,将IP地址告诉我是:192.168.43.2,为了验证双方确实连通,在cmd中输入ping 192.168.43.2,得到下图,证实确实已连通。

    Socket对象的建立

    • 在一个Socket对象中同时包含了远程服务器的IP地址和端口信息, 以及客户本地的IP地址和端口信息. 此外, 从Socket 对象中还可以获得输出流和输入流, 分别用于向服务器发送数据, 以及接收从服务器端发来的数据. 以下方法用于获取Socket的有关信息。
    getInetAddress(): 获得远程服务器的IP 地址.
    getPort(): 获得远程服务器的端口.
    getLocalAddress(): 获得客户本地的IP 地址.
    getLocalPort(): 获得客户本地的端口.
    getInputStream(): 获得输入流. 如果Socket 还没有连接, 或者已经关闭, 或者已经通过 shutdownInput() 方法关闭输入流, 那么此方法会抛出IOException.
    getOutputStream(): 获得输出流, 如果Socket 还没有连接, 或者已经关闭, 或者已经通过 shutdownOutput() 方法关闭输出流, 那么此方法会抛出IOException.
    
    • 建立一个Socket对象,用来连接特定服务器的指定端口,输入的参数是刚刚获取的ip地址和双方默认的同一端口。
      Socket socket = new Socket("192.168.43.2",2030);//创建连接到服务器的2030端口

    完整代码

    import java.net.*;
    import java.io.*;
    import java.security.*;
    import javax.crypto.*;
    import javax.crypto.spec.*;
    import java.security.spec.*;
    import javax.crypto.interfaces.*;
    import java.security.interfaces.*;
    import java.math.*;
    
    public class Client {
        public static void main(String args[]) throws Exception{
            try {
                KeyGenerator kg=KeyGenerator.getInstance("DESede");//Java中KeyGenerator类中提供了创建对称密钥的方法;
                kg.init(168); //初始化密钥生成器,指定密钥的长度。
                SecretKey k=kg.generateKey( );//生成密钥,使用第一步获得的KeyGenerator类型的对象中generateKey( )方法可以获得密钥。其类型为SecretKey类型,可用于以后的加密和解密。
                byte[] ptext2=k.getEncoded();// 获取主要编码格式,将返回的编码放在byte类型的数组中。
                Socket socket = new Socket("192.168.43.2",2030);//创建连接到服务器的2030端口
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));//从服务器端获得输入流
                PrintWriter out=new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true); //获得向服务器端输出数据的输出
                BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); //从键盘上输入信息
    
                //服务器端RSA的公钥对DES的密钥进行加密
                FileInputStream f3=new FileInputStream("Skey_RSA_pub.dat");//将文件中保存的对象读取出来以便使用
                ObjectInputStream b2=new ObjectInputStream(f3);
                RSAPublicKey  pub_key=(RSAPublicKey)b2.readObject( );//生成公钥
                BigInteger e=pub_key.getPublicExponent();
                BigInteger n=pub_key.getModulus();//模
                System.out.println("e= "+e);
                System.out.println("n= "+n);//(n,e)是公钥
                BigInteger m=new BigInteger(ptext2);
                BigInteger c=m.modPow(e,n);
                System.out.println("c= "+c);
                String cs=c.toString( );
                out.println(cs);  //传送到服务器
                System.out.print("请输入待发送的数据:");
                String s=stdin.readLine(); //从键盘读入要发送的发信息
                Cipher cp=Cipher.getInstance("DESede");//获取Cipher实例 密码器
                cp.init(Cipher.ENCRYPT_MODE, k);//初始化cipher ENCRYPT_MODE表示加密DECRYPT_MODE解密 , k是密钥
                byte by[]=s.getBytes("UTF8");//获取字符串的utf8字节码
                byte miby[]=cp.doFinal(by);//加密后的字节码
                String str=parseByte2HexStr(miby);//获取密文字符串
                out.println(str);  //传送到服务器
    
    
                //将客户端的明文哈希值传送给密文
                String x=s;
                MessageDigest m2=MessageDigest.getInstance("MD5");//通过其静态方法getInstance( )生成MessageDigest对象。
                m2.update(x.getBytes( ));//x为需要计算的字符串,用getBytes( )方法生成字符串数组。
                byte ss[ ]=m2.digest( );//计算的结果通过字节类型的数组返回。
                String result="";//将计算结果ss转换为字符串
                for (int i=0; i<ss.length; i++){
                    result+=Integer.toHexString((0x000000ff & ss[i]) | 0xffffff00).substring(6);
                }
    
                System.out.println(result);
                out.println(result);
    
                str=in.readLine();//读取结果
                System.out.println( "从服务器接收到的结果为:"+str);
            }
    
            catch (Exception e) {
                System.out.println(e);
            }
        }
    
        //将二进制转换成16进制
        public static String parseByte2HexStr(byte buf[]) {
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < buf.length; i++) {
                String hex = Integer.toHexString(buf[i] & 0xFF);
                if (hex.length() == 1) {
                    hex = '0' + hex;
                }
                sb.append(hex.toUpperCase());
            }
            return sb.toString();
        }
    
        //将16进制转换为二进制
        public static byte[] parseHexStr2Byte(String hexStr) {
            if (hexStr.length() < 1)
                return null;
            byte[] result = new byte[hexStr.length()/2];
            for (int i = 0;i< hexStr.length()/2; i++) {
                int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16);
                int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16);
                result[i] = (byte) (high * 16 + low);
            }
            return result;
        }
    }
    

    原理解释

    • 获取密钥生成器:KeyGenerator kg=KeyGenerator.getInstance("DESede");
      • Java中KeyGenerator类中提供了创建对称密钥的方法。Java中的类一般使用new操作符通过构造器创建对象,但KeyGenerator类不是这样,它预定义了一个静态方法getInstance(),通过它获得KeyGenerator类型的对象。这种类成为工厂类或工厂。
      • 方法getInstance( )的参数为字符串类型,指定加密算法的名称。可以是 “Blowfish”、“DES”、“DESede”、“HmacMD5”或“HmacSHA1”等。这些算法都可以实现加密,这里我们不关心这些算法的细节,只要知道其使用上的特点即可。
    • 初始化密钥生成器:kg.init(168);
      • 该步骤一般指定密钥的长度。如果该步骤省略的话,会根据算法自动使用默认的密钥长度。
      • 指定长度时,若第一步密钥生成器使用的是“DES”算法,则密钥长度必须是56位;若是“DESede”,则可以是112或168位,其中112位有效;若是“AES”,可以是128, 192或256位。
    • 生成密钥: SecretKey k=kg.generateKey( );
      • 使用第一步获得的KeyGenerator类型的对象中generateKey( )方法可以获得密钥。其类型为SecretKey类型,可用于以后的加密和解密。
    • 通过对象序列化方式从文件中读取密钥:
    FileInputStream f3=new FileInputStream("Skey_RSA_pub.dat");
    ObjectInputStream b2=new ObjectInputStream(f3);
    

    ObjectInputStream类中提供的writeObject方法可以将对象序列化,以流的方式进行处理。
    这里将文件读取流作为参数传递给ObjectOInputStream类的构造器,这样将文件Skey_RSA_pub.dat中的密钥传递赋值给b2变量。

    运行结果

    • 服务器运行代码,等待建立连接:

    • 客户端运行代码,建立连接:

    • 客户端输入明文信息,进行加密传输:

    • 服务器对收到的信息进行解密,并校验哈希值:

    遇到问题及解决办法

    问题一:不能连接到服务器

    • 问题描述:
      • 已经将客户端与服务器连接在同一局域网下,可是ping服务器的地址,发现并不能连上。
    • 解决方案:
      • 经过我们两台电脑的ipconfig结果的对比,发现我比他多了一个以太网适配器 VMware Network Adapter VMnet1/8,这是电脑中虚拟机的网络IP。

      • 考虑到可能是因为这个的影响,在电脑网络设置中关掉了,虚拟机网络。

    问题二:不能读取密钥文件

    • 问题描述:

      • 在客户端与服务器建立连接后,IDEA运行报错,显示错误“系统找不到指定文件”。也就是不能程序读取我代码中的Skey_RSA_pub.dat文件。
    • 解决方案:

      • 之前我把Skey_RSA_pub.dat文件放在了与Client.java文件的同一目录下,最后将其移至与src文件夹同目录下,即可成功读取。

    总结

    • 这次实验我觉得应该是最为复杂的了,相比之前的四个实验,这个实验中老师给出了大量的相关知识和各个代码,只是将这些东西结合起来本事就是一项比较复杂的工作,因为需要将每个部分的代码搞清楚,并且在连接时候,总是有很多错误,结合了学长学姐之前的一些学习经验,明白了如何查找相关的IP地址以及通过cmd来检验是否连接成功。

    • 最后试验成功的时候,还是很让人高兴的,毕竟花了这么长时间,从这次实验中学到了很多,没有想到java原来还可以这么用,真的是很方便,我觉得我需要学习的地方还有很多,这个实验也很复杂,我还没有完全搞清楚,这个还需要努力。

    • 在实验开始的时候发一个信息没办法发送,显示超出129byte,但是多次重试后就可成功。我们认为这是线程的问题。

    • PSP(Personal Software Process)时间:

    步骤 耗时 百分比
    需求设计 1h 12.5%
    设计 2h 25.0%
    代码实现 2h 25.0%
    测试 1h 12.5%
    分析总结 2h 25.0%

    参考资料

  • 相关阅读:
    redis key命名规范
    git修改commit作者名
    spring cloud 入门学习
    IDEA 插件大全
    2019 java中高级 面试题汇总
    Spring boot整合Mybatis【详细的一匹】
    MySQL存储过程
    HTTP状态码详解
    java 职业规划
    javaweb项目实现文件上传下载
  • 原文地址:https://www.cnblogs.com/20145221GQ/p/5470269.html
Copyright © 2020-2023  润新知