重构java本地聊天程序,TCP/UDP+GUI+RSA+多线程

重构本地聊天程序

源码地址:https://github.com/feimingabandon/GUI-

前前言:异地通讯

​         如果想实现QQ一样两个电脑进行聊天,最好是选择:客户端A - 服务器 - 客户端B 的形式、即我们可以将服务器的 ip 地址暴露(内网穿透,租云服务器等等都可以),然后A把要发送的消息传递给服务器,让服务器进行分发消息给对应的客户端。

问题1:为什么需要暴露 ip 地址

三个概念:

​        1、公网:拥有公网ip 的网络,这个网络可通过 ip 地址直接被访问到

​         2、外网:国外网站

​         3、内网:又称局域网、内网中的电脑使用的内网 ip ,这个 ip 不能直接被公网访问到

       ​ 因为 公网ip 少,不足以满足全球用户一个电脑一个ip地址,所以运营商一般会将一个城市或者地区分配一个公网 ip,此 ip地址 可被直接访问到,那么,在这个地区下的所有电脑都通过一个叫 NAT 的中间模块处理公网ip 与内网ip 的关系。

(最初是路由器中的模块,这里的路由器可大可小,可以是管理一个城市的路由器,也可以是管理一个家庭的路由器)

​        NAT :是处理公网ip与内网ip的映射关系,即记录一个公网ip对应的多个内网ip。

​         但是,它只是完成局域网中 IP 的映射,我们除了要知道 IP 地址外,还需要端口号才知道是发送给哪个进程,所以就要使用到:NAPT。

​        NAPT:与前者相同的是,除了有一对多的 IP 映射关系外,还会生成一个端口号,这个端口号会与内网中电脑进程分配的端口号匹配。

​         那么此时我们在 NAPT 中有:内网的IP和端口号,与内网对应的公网IP和一个端口号B,这个端口号B与内网IP和端口号这两个是绑定的,即可以通过端口号B知道内网IP和端口号的。

所以发送 - 接收消息的流程是:

​         将客户端A1,服务器B,客户端A2 的程序先运行起来,然后两客户端将自己的ip地址发送给服务器,注意:此时客户端是发送的自己内网IP和对应的端口号,但是在经过内网转公网过程中,NAPT会记录此内网IP与端口号,并生成一个端口映射C2(假设客户端公网IP为C1),即服务器最终接收到的是C1和C2,即公网IP与端口号C2

​        当客户端A1要给A2发送消息时,A1先将消息发送给服务器并通知服务器要将消息发送给A2,此时服务器会看A2是否在线,如果在线,就通过A2之前给的 公网IP C1与 端口号 C2 在路由间转发并最终找到此公网IP所在的路由,然后通过端口号 C2 在 NAPT 中找到对应的 内网IP 与 端口号,并发送。

问题2:为什么要加一个服务器进行中转?

​        首先,不加也行,但是内网IP有个特点容易变动,即IP地址和端口号会变,可能重新开机关机,重新连接网络,两者就发生变化了,这就不是我们希望看得到,所以加个服务器,服务器的特点就是有自己的公网IP,即IP地址永远不会变动,端口号可以手动设置,也能不变。

注:客户端的端口号也可以手动设置,但不管是服务器还是客户端手动设置就可能重新端口号冲突,即要使用的端口号以及被使用了。解决:要么换一个,要么将占用端口号的进程结束。一般情况下是让电脑自动在一定范围内分配端口号的。

0.0、前言

​        人们对事物总是报以崇高的期许。

​         这并不算特别优秀的作品、代码不够优雅、前端界面过于简单、不能完全解耦、等等等等,但相比于以前好了太多太多,模块功能简洁明了、业务逻辑足够清晰、代码尽量解耦且不臃肿、最重要的注释足够多、用我现在看以前的代码、确实是头疼不已、好在命名规范、翻译翻译也能知道写的是啥破玩意。

​        写了啥?

        一句话概括:基于RSA公私钥、网络编程、GUI三者进行数字签名与完整性认证的本地聊天程序。

​         实现出现的bug:

​         由于有经验、有些错误简单就不做赘述、提提给我使绊子的:

​        1、一个是通过字节数组传输到且解析后,可能会出现、解析错误,显示超过了127位或者128。

​         这个错误有两种原因:

​         1、是你发送的消息即字符串超过了117个,解决办法:分组发送。

​         2、接收到的字节数组以及接收用的字节数组长度是要超出或者等于,要接收的消息的,那么会照成字节数组的默认值0也会参与解析。

​         解决办法:利用字符串分割方法、我们发送时在消息末尾添加一个空格 ,接收到后先用分割方法一空格为分割符进行分割,这样可只取出我们所需要的消息。当然还有其他方法,比如接收端利用循环判断进行取出有效值(所以前者是我偷懒想出来的)

​         2、显示解密错误,即利用RSA进行解密时出错。

​         原因:很简单,秘钥用错了,要么是A的东西用了B的秘钥。要么是最狗血的一种:秘钥过时了,即我已经更新了秘钥但是你用旧秘钥解密,不错才怪。

​         后者解决办法:重新写一个产生密钥对的类,且序列化密钥对,之后只需要使用,不需要生成。

​         3、我在使用UDP传输时想完成一个请求连接的功能,即A端间隔1秒不断发送请求连接的消息,B则等待接收,若接收到,则给予A回执消息,通知A,A接收到通知则停止发送请求并显示连接完成。

​         原因:上述看似无差错,但是!A发给B后,B从接收到发送回执消息这段时间A是不知道它接收到了的,所以A又会继续发送请求,导致B会在连接成功后又收到一次请求消息。(这个错误没影响,但但是以为这个错误与第二个错误中的第二种错误有关,快烦死我了)

​         解决方法:发送请求的时间拉长一点,可三秒一次等等。

能让我头疼的错误不多,就想到这么多了。其实错误原因还是以为考虑少了,不能完全考虑到运行情况。但这也让我小小温故而知新一次、颇有收益。

1.1、项目结构

请添加图片描述

1.2、项目代码

1.2.1、PublicPrivateKeyAcquisition.java

实现手段:

通过java的jdk中的API进行公私钥的生成与序列化。

import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;

//1、公私钥获取:生成公私钥、序列化公私钥、获取公钥、获取私钥。
public class PublicPrivateKeyAcquisition {

    public KeyPair keyPair;

    // 产生公私钥。并初始化RSA算法。
    public void produceKeyPair() throws NoSuchAlgorithmException {
        //使用RSA算法获得密钥对生成器对象keyPairGenerator
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        //设置密钥长度为1024
        keyPairGenerator.initialize(1024);
        //生成密钥对
        keyPair = keyPairGenerator.generateKeyPair();
    }

    // 即 将存储了公私钥的对象存储进文件中,方便下次使用
    // 序列化对象,name是作为序列化后文件的名字
    public void serialize(String name) throws Exception {
        String str = name+".txt";
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(str));
        oos.writeObject(keyPair);
        oos.close();
    }
    // 通过相应的文件取出对应的公私钥对象,可通过公私钥对象获得公私钥。
    // 反序列化
    public KeyPair deserialization(String route) throws IOException, ClassNotFoundException {
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(route+".txt"));
        Object obj=ois.readObject();
        ois.close();
        return (KeyPair) obj;
    }

    // 获得公钥
    public Key getPublicKey(){
        return keyPair.getPublic();
    }

    // 获得私钥
    public Key getPrivateKey(){
        return keyPair.getPrivate();
    }

}

1.2.3、RSAOperation.java

实现手段:

jdk自带的加密解密、SHA摘要生成、只有完整性认证是通过接收到的原文生成的SHA与接收到的SHA进行比较是否相同。

package com;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import java.security.Key;
import java.security.MessageDigest;


//2、RSA包含:加密信息,解密信息,SHA散列值获取,完整性认证。
public class RSAOperation {

    // 参数1:明文,参数2:私钥,作用:加密原始消息
    public String encryption(String str, Key Key) throws Exception {
        //获取一个加密算法为RSA的加解密器对象cipher。
        Cipher cipher = Cipher.getInstance("RSA");
        //设置为加密模式,并将公钥给cipher。
        cipher.init(Cipher.ENCRYPT_MODE, Key);
        //获得密文
        byte[] secret = cipher.doFinal(str.getBytes());

        //进行Base64编码并返回
        // 注:使用Base64编码方式,是因为加密后的消息为二进制形式的数据,
        // 而我们传输的应该为字符串,所以要用到Base64的编码与解码。(Base64编码可将字节数组转换为字符串,解码为逆过程)
        return new BASE64Encoder().encode(secret);
    }

    // 参数1:密文,参数2:公钥、作用:解密密文
    public String decryption(String secret,Key Key) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA");
        //传递私钥,设置为解密模式。
        cipher.init(Cipher.DECRYPT_MODE, Key);
        //解密器解密由Base64解码后的密文,获得明文字节数组
        byte[] b = cipher.doFinal(new BASE64Decoder().decodeBuffer(secret));
        //转换成字符串
        return new String(b);
    }

    // 生成基于原始消息的 SHA 散列值
    public String encoderSHA(String str) throws Exception {
        MessageDigest sha=MessageDigest.getInstance("SHA");
        BASE64Encoder base64en = new BASE64Encoder();
        //加密后的字符串
        // 使用digest方法后的数据为二进制数组,需通过encode变成字符串。后面接收到后逆过程可获得相同的字节数组
        return base64en.encode(sha.digest(str.getBytes("utf-8")));
    }

    // 验证消息完整性
    // 比较接收到的SHA摘要和通过接收到的原文产生的SHA摘要是否相同进行判断
    public boolean checkIntegrity(String ReceivedSHA,String CalculatedSHA) throws Exception {

        return (encoderSHA(ReceivedSHA)).equals(CalculatedSHA);

    }


}

1.2.4、UDPChatReceiver.java

关于端口号要着重说明一下

一共是要开启四个端口号,若是两者本地通讯。

A的发送与接收,B的发送与接收,都需要不同的端口号。其中,我们发送时还需要用到对方的端口号,这时是用到对方接收的端口号(这个不要错了)

实现手段:

jdk自带的网络编程知识、额外用到了多线程知识来实现一直接收消息,因接收程序是阻塞式,即接收到了才进行下一步、所以另起一线程。

package com;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.security.Key;


// 继承此接口,表示另外开启一个线程,线程会执行run()方法中的代码。
public class UDPChatReceiver implements Runnable{

    DatagramSocket socket =null;  // 用来创建接收端口号的变量
    public int myPort;       // 端口号
    RSAOperation rsa = null;   // 获得数据后用此类中的方法进行解密
    GUIChat gui = null;      // 获得数据且解密后,用此类显示在图形化界面中
    String heName = "";      // 对方的名字
    Key publicKey = null; // 对方公钥

    public String receiveData="";       // 存储接收到的字符串

    // 生成接收端口的进程
    public UDPChatReceiver(int myPort,RSAOperation rsa,GUIChat gui,String heName){
        this.myPort=myPort;
        this.rsa = rsa;
        this.gui = gui;
        this.heName = heName;

        //在本机地址上建立端口,以接收
        try {
            socket=new DatagramSocket(myPort);

            // 获取对方公钥
            publicKey = new PublicPrivateKeyAcquisition().deserialization(heName).getPublic();

        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    @Override
    public void run() {

        // 通过循环能够一直保持接收,因为下方接收消息是阻塞式,即只有接收到了才会执行下一步,所以才重新开启一个线程去作为接收消息的程序。
        while (true) {
            try {

                //接收数据包
                byte[] container = new byte[1024];
                //构造一个 DatagramPacket用于接收长度的数据包 length 。
                DatagramPacket packet = new DatagramPacket(container, 0, container.length);
                //接收来自DatagramPacket的数据包,阻塞式
                socket.receive(packet);

                //获得包裹中的数据
                receiveData = new String(packet.getData(), 0, packet.getData().length);

                // 获取到数据后,解密数据,清空GUI中的文本域,然后重新赋值文本域。
                decryptionAndDisplay(receiveData);

                // 释放资源的判定
                if(receiveData.equals("exit_0"))break;
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        //关闭流
        socket.close();

    }

    // 解密数据并显示数据
    public void decryptionAndDisplay(String str){

        try {
            String[] s = str.split(" "); // 分割接收到的信息,方便解读

            if (s[0].equals("请求连接")) {
                receiveData = s[0];
            }else {

                // 解密密文
                String str2 = rsa.decryption(s[0], publicKey);

                // 将获得的密文与SHA显示在左边的文本域中,记得之前的文本不能删除了。
                // 可直接在原基础上插入内容
                gui.text_Ciphertext.append("密文:\n" + s[0] + "\n\n" + "SHA摘要:" + s[1] + "\n\n" + "完整性认证:" + rsa.checkIntegrity(str2, s[1]) + "\n" + "------------------------------------------------" + "\n");

                // 将解密后的明文显示在右边文本域中
                gui.text_Plaintext.append("\n" + heName + ": " + str2 + "\n");
                }
            } catch(Exception e){
                e.printStackTrace();
        }

    }
}

1.2.5、UDPChatSender.java

实现手段:

jdk自带的API、但此时没有另起线程使用发送功能,因GUI界面点击发送按钮才会进行发送,所以不需要另外开线程。且GUI的程序本身就是一个线程

package com;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

public class UDPChatSender  {
    public int myPort;          // 我自己发送的端口号
    DatagramSocket socket=null;

    public UDPChatSender(int myPort){
        this.myPort=myPort;
        //建立一个Socket
        try {
            socket=new DatagramSocket(myPort);
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }


    public void send(String str,int hePort){//str就是要发送的数据、hePort对方接收的端口号

        if (!str.equals("exit_0")) { // 这是一个关闭IO流的操作,一般是结束时关闭
            try {
                //获取本机地址
                InetAddress inetAddress = InetAddress.getByName("localhost");

                //参数:数据(Byte类型),发送数据的长度,要发给谁
                DatagramPacket packet = new DatagramPacket(str.getBytes(), 0, str.getBytes().length, inetAddress, hePort);

                socket.send(packet);//发送包
            } catch (IOException e) {
                e.printStackTrace();
            }

        }else {
            //关闭流
            socket.close();
        }
    }
}

1.2.6、GUIChat.java

实现手段:

​ 利用Swing知识。发送按钮点击后会先分析输入框有没有字符串,有才下一步。之后将要发送的消息在聊天界面先显示,然后再加密原消息,与生成对应的SHA摘要。然后将两者拼接,中间使用空格分开,末尾加上空格。最后调用发送程序。

package com;


import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.security.Key;

public class GUIChat extends JFrame {

    public JTextArea text_Ciphertext; // 展示密文的文本框
    public JTextArea text_Plaintext;  // 展示明文的文本框
    public JTextField text_Input;     // 输入文本框
    public JButton button_send;       // 发送按钮
    public JScrollPane jScrollPane_C;   // 滚动面板有两个、且放在面板中
    public JScrollPane jScrollPane_P;   // 滚动面板有两个、且放在面板中
    public JPanel jPanel;             // 面板只有一个、且放在滚动面板中中
    Key privateKey=null;                // 自己的私钥

    // name是聊天者身份,hePort,对方是端口号,send,是发送按钮点击时需要用的此类进行发送消息
    // 此方法是生成图形化界面
    public void init(String myName, int hePort, UDPChatSender send,RSAOperation rsa){

        // 设置生成窗体的位置,宽高
        this.setBounds(600,200,800,600);
        //获得一个容器,只有这样设置颜色才会生效
        Container contentPane = this.getContentPane();
        contentPane.setBackground(Color.yellow);

        //绝对布局,依照设定的x,y坐标定位布局组件
        contentPane.setLayout(null);

        // 生成面板
        jPanel = new JPanel();

        jPanel.setBackground(Color.black);// 设置面板颜色
        jPanel.setLayout(null);//设置面板的布局类型

        // 设置面板大小
        jPanel.setBounds(0,0,800,600);


        // 生成两个文本域、一个文本框、一个按钮
        text_Ciphertext=new JTextArea();   // 20为超过20个字则换行
        text_Plaintext=new JTextArea(20,10);
        text_Input = new JTextField(20);             // 20为文本框的高度
        button_send = new JButton("发送");

        // 设置文本域中字体的属性,行楷,字体类型(加粗等,这里是标准),大小
        text_Ciphertext.setFont(new Font("行楷",Font.PLAIN,20));
        text_Plaintext.setFont(new Font("楷书",Font.PLAIN,20));

        // 设置文本框和按钮的位置,以生成的(即生成的窗体)JFrame的左上角为(0,0)开始设置的位置
         text_Input.setBounds(400,535,300,30);
         button_send.setBounds(700,535,100,30);


        // 生成滚动面板、并将对应的文本域放入
        jScrollPane_C = new JScrollPane(text_Ciphertext);
        jScrollPane_P = new JScrollPane(text_Plaintext);

        // 设置滚动面板在面板中的位置
        jScrollPane_C.setBounds(0,0,400,565);
        jScrollPane_P.setBounds(400,0,400,535);


        // 将滚动面板、按钮放入面板
        jPanel.add(jScrollPane_C);
        jPanel.add(jScrollPane_P);
        jPanel.add(text_Input);
        jPanel.add(button_send);

        //将面板放入JFarm中
        contentPane.add(jPanel);

        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);//点 x 后直接退出

        // 设置窗口大小固定
        setResizable(false);
        // 设置窗口名称
        setTitle(myName);
        // 设置可见性
        setVisible(true);

        // 从本地的序列化文件中获得自己的私钥
        PublicPrivateKeyAcquisition acquisition = new PublicPrivateKeyAcquisition();
        try {
            // 获取私钥
            privateKey = acquisition.deserialization(myName).getPrivate();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 按钮事件监听器,即点击按钮后会发生文本框中的内容
        button_send.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String text_encryption="";
                // 先获取文本框内容
                String text = text_Input.getText();

                // 判断文本框是否存在内容,如果存在则发送
                if (!text.equals("")){

                    try {

                        // 将要发送的消息显示在右边文本域中
                        text_Plaintext.append("\n" + myName + ": " + text + "\n");
                        // 加密原消息
                        text_encryption = rsa.encryption(text, privateKey);

                        // 给加密后的消息添加SHA摘要
                        text_encryption = text_encryption+" "+rsa.encoderSHA(text)+" ";
                    } catch (Exception exception) {
                        exception.printStackTrace();
                    }

                    // 利用UDP发生内容给指定对象
                    send.send(text_encryption,hePort);

                    // 最后清空文本框
                    text_Input.setText("");
                }
            }
        });
    }
}

1.2.7、GeneratePublicPrivateKey.java

实现手段:

调用生成密钥对、序列化的工具类

package com.Test;

import com.PublicPrivateKeyAcquisition;

public class GeneratePublicPrivateKey {
    public static void main(String[] args) {
        // 生成公私钥,要生成两对密钥对。
        try {
            PublicPrivateKeyAcquisition acquisition = new PublicPrivateKeyAcquisition();
            acquisition.produceKeyPair();// 生成密钥对
            acquisition.serialize("Bob");// 序列化密钥对

            acquisition.produceKeyPair();// 生成密钥对
            acquisition.serialize("Alice");// 序列化密钥对

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

1.2.8、Bob.java

实现手段:

两个用户实现手段差不多,具体看下面Alice的解释。

package com.Test;

import com.*;


public class Bob {
    GUIChat guiChat = null;                 // 获得图形化界面的对象,用来作为启动图形化界面的变量
    UDPChatSender sender = null;            // 发送消息所用到的变量
    RSAOperation rsaOperation = null;       // 数字签名,加密,解密,完整性认证,SHA摘要所用到的变量
    UDPChatReceiver receiver = null;        // 接收消息所用到的变量

    // 初始化、即启动图形化界面,启动接收线程。
    public void init(){

        // 开启图形化界面
        guiChat = new GUIChat();

        // 设置好发送所需要的信息
        sender = new UDPChatSender(9999);

        // new出其对象
        rsaOperation = new RSAOperation();

        // 初始化图形化界面
        guiChat.init("Bob",8881,sender,rsaOperation);


        // 先开启接收线程
        receiver = new UDPChatReceiver(9991,rsaOperation,guiChat,"Alice");
        new Thread(receiver).start();

        // 尝试连接用户
        initConnection();

    }

    // 尝试连接用户、即被动等待 A 发送过来的连接信息,接收到后再返回一个回执信息给A
    public void initConnection(){
        boolean flag=true;  // 连接成功后用来退出连接的变量
        int i=0;
        // 不主动发信息,先获得对方的连接信息然后再回复对方一个信息
        // 循环中内容是为了将尝试连接这个消息进行动态变化
        while (flag) {
            i=(++i)%3;

            try {
                Thread.sleep(1000); // 休眠一秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            guiChat.text_Ciphertext.setText("");  //清空文本域

            if (i==0)
                // 设置文本域显示连接标志
                guiChat.text_Ciphertext.setText("尝试连接中.");

            else if (i==1)
                // 设置文本域显示连接标志
                guiChat.text_Ciphertext.append("尝试连接中..");

            else if (i==2)
                // 设置文本域显示连接标志
                guiChat.text_Ciphertext.append("尝试连接中...");

            if (receiver.receiveData.equals("请求连接")){
                flag = false;

                // 发送回执信息
                sender.send("请求连接 B ",8881);

                guiChat.text_Ciphertext.setText("");  //清空文本域,即设置文本域的内容为空
                guiChat.text_Ciphertext.setText("连接成功!\n");
            }


        }
    }
    public static void main(String[] args) {
        new Bob().init();
    }
}

1.2.9、Alice.java

实现手段:

​ 先初始化生成我们要用的类、图形化界面类、发送、接收类、RAS工具类。在设置发送、接收类时定义好端口号。

​ 然后调用图形化界面的初始化方法,显示图形化界面。

​ 开启接收线程、可以及时接收到请求连接的信息。

​ 然后调用尝试连接用户的方法,等待连接。

​ 连接成功、正常通信。

package com.Test;


import com.*;

public class Alice {
    GUIChat guiChat = null;
    UDPChatSender sender = null;
    RSAOperation rsaOperation = null;
    UDPChatReceiver receiver = null;
    public void init(){

        // 开启图形化界面
        guiChat = new GUIChat();
        sender = new UDPChatSender(8888);
        rsaOperation = new RSAOperation();

        guiChat.init("Alice",9991,sender,rsaOperation);

        // 先开启接收线程
        receiver = new UDPChatReceiver(8881,rsaOperation,guiChat,"Bob");
        new Thread(receiver).start();

        // 尝试连接用户
        initConnection();

    }

    // 尝试连接用户
    public void initConnection(){
        boolean flag=true;
        int i=0;


        // 隔 3 秒发送一次信息、等待回执信息、获得回执后接束发送。
        while (flag) {
                i=(++i)%3;

                try {
                    Thread.sleep(1000);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                    guiChat.text_Ciphertext.setText("");  //清空文本域
                if (i==1)
                    // 设置文本域显示连接标志
                    guiChat.text_Ciphertext.setText("尝试连接中.");

                else if (i==2)
                    // 设置文本域显示连接标志
                    guiChat.text_Ciphertext.append("尝试连接中..");

                else if (i==0) {
                    // 设置文本域显示连接标志
                    guiChat.text_Ciphertext.append("尝试连接中...");
                    // 发送信息
                    sender.send("请求连接 A ",9991);
                }
            if (receiver.receiveData.equals("请求连接")){
                flag = false;
                guiChat.text_Ciphertext.setText("");  //清空文本域
                guiChat.text_Ciphertext.setText("连接成功!\n");

            }
        }
    }

    public static void main(String[] args) {
        new Alice().init();
    }
}

1.3、设计架构

​ 目标:编写基于RSA进行数字签名和图形化界面的本地通讯程序。
​ 要求:1、发送的信息包括原始消息和签名,原始消息通过RSA的私钥加密,签名则是利用SHA散列函数对原始消息进行摘要。
​ 2、接收到消息后通过SHA的值检测原始消息的完整性。
​ 3、利用图形化界面完成。
​ 架构设计:
​ 整个程序应分为四部分:1、基于RSA的公私钥生成。2、RSA。3、本地聊天。4、图形化界面。
​ 1、公私钥获取:生成公私钥、序列化公私钥、获取公钥、获取私钥。(PublicPrivateKeyAcquisition类)
​ 2、RSA包含:加密信息,解密信息,SHA散列值获取,完整性认证。(RSAOperation类)
​ 3、UDP。
​ 4、GUI。

1.4、操作步骤

​ 1、先生成自己的公私钥并序列化保存(手动运行GeneratePublicPrivateKey类)
​ 2、开启聊天软件,等待与对方进行连接(分别启动Alice和Bob类)
​ 3、连接成功,进行加密通信

1.5、程序运行截图

1.5.1、尝试连接截图:

请添加图片描述

1.5.2、连接成功截图

请添加图片描述

1.5.3、发送消息截图:

请添加图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值