Java 客户端-服务器加密聊天应用程序 |当第二个客户端连接时,加密失败并崩溃

问题描述

服务器线程类

public class HiloServidor extends Thread {
    DataInputStream fenTrada;
    Socket socket;
    boolean fin = false;
    static RSA rsa = new RSA();
    static String encriptado;
    static String desencriptado;

    public HiloServidor(Socket socket) {
        this.socket = socket;
        try {
            fenTrada = new DataInputStream(socket.getInputStream());
        } catch (IOException e) {
            System.out.println("Error de E/S");
            e.printstacktrace();
        }
    }

    // En el método run() lo primero que hacemos es enviar todos los mensajes actuales al cliente que se acaba de incorporar
    public void run() {
        Vista.mensajeServidor.setText("Número de conexiones actuales: " + ServidorChat.ACTUALES);
        String texto = Vista.textAreaServidor.getText();
        EnviarMensajes(texto);
        // Seguidamente,se crea un bucle en el que se recibe lo que el cliente escribe en el chat.
        // cuando un cliente finaliza con el botón Salir,se envía un * al servidor del Chat,sale
        // del bucle while,ya que termina el proceso del cliente,de esta manera se controlan las conexiones actuales
        while (!fin) {
            try {
                // Trabajamos con las claves privadas y públicas
                rsa.genKeyPair(512);
                rsa.openFromdiskPrivateKey("rsa.pri");
                rsa.openFromdiskPublicKey("rsa.pub");
                encriptado = fenTrada.readUTF();
                desencriptado = rsa.Decrypt(encriptado);
                System.out.println("\nTexto recibido (encriptado): " + encriptado);
                System.out.println("Texto recibido (desencriptado): " + desencriptado);

                if (desencriptado.trim().equals("*")) {
                    ServidorChat.ACTUALES--;
                    Vista.mensajeServidor.setText("Número de conexiones actuales: " + ServidorChat.ACTUALES);
                    fin = true;
                }
                // El texto que el cliente escribe en el chat,se añade al textarea del servidor y se reenvía a todos los clientes
                else {
                    Vista.textAreaServidor.append(desencriptado + "\n");
                    texto = Vista.textAreaServidor.getText();
                    EnviarMensajes(texto);
                }
            } catch (Exception ex) {
                ex.printstacktrace();
                fin = true;
            }
        }
    }

    // El método EnviarMensajes() envía el texto del textarea a todos los sockets que están en la tabla de sockets,// de esta forma todos ven la conversación. El programa abre un stream de salida para escribir el texto en el socket
    private void EnviarMensajes(String texto) {
        for (int i = 0; i < ServidorChat.CONEXIOnes; i++) {
            Socket socket = ServidorChat.tabla[i];
            try {
                DataOutputStream fsalida = new DataOutputStream(socket.getoutputStream());
                fsalida.writeUTF(texto);
            } catch (IOException e) {
                e.printstacktrace();
            }
        }
    }
}

服务器类

public class ServidorChat extends JFrame implements ActionListener {
    static final int PUERTO = 55555;
    private static final long serialVersionUID = 1L;
    static ServerSocket servidor;
    static int CONEXIOnes = 0;
    static int ACTUALES = 0;
    static int MAXIMO = 15;
    static Socket[] tabla = new Socket[MAXIMO];
    static boolean conectadoMasDeUno = false;

    public ServidorChat() {
        // Construimos el entorno gráfico
        super("SERVIDOR DE CHAT");
        setLayout(null);
        Vista.mensajeServidor.setBounds(10,10,400,30);
        add(Vista.mensajeServidor);
        Vista.mensajeServidor.setEditable(false);
        Vista.mensajeServidor2.setBounds(10,348,30);
        add(Vista.mensajeServidor2);
        Vista.mensajeServidor2.setEditable(false);
        Vista.textAreaServidor = new JTextArea();
        Vista.scrollPaneservidor = new JScrollPane(Vista.textAreaServidor);
        Vista.scrollPaneservidor.setBounds(10,50,300);
        add(Vista.scrollPaneservidor);
        Vista.salirservidor.setBounds(420,100,30);
        add(Vista.salirservidor);
        Vista.textAreaServidor.setEditable(false);
        // Se ha anulado el cierre de la ventana para que la finalización del servidor se haga desde el botón Salir.
        // cuando se pulsa el botón se cierra el ServerSocket y finaliza la ejecución
        Vista.salirservidor.addActionListener(this);
        setDefaultCloSEOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) throws Exception {
        // Desde el main se inicia el servidor y las variables y se prepara la pantalla
        servidor = new ServerSocket(PUERTO);
        System.out.println("Servidor iniciado");
        ServidorChat pantalla = new ServidorChat();
        pantalla.setBounds(0,540,450);
        pantalla.setVisible(true);
        Vista.mensajeServidor.setText("Número de conexiones actuales: " + 0);
        // Se usa un bucle para controlar el número de conexiones. Dentro del bucle el servidor espera la conexión
        // del cliente y cuando se conecta se crea un socket
        while (CONEXIOnes < MAXIMO) {
            Socket socket;
            try {
                socket = servidor.accept();
            } catch (SocketException sex) {
                // Sale por aquí si pulsamos el botón salir
                break;
            }
            // El socket creado se añade a la tabla,se incrementa el número de conexiones y se lanza el hilo para
            // gestionar los mensajes del cliente que se acaba de conectar
            tabla[CONEXIOnes] = socket;
            CONEXIOnes++;
            ACTUALES++;
            HiloServidor hilo = new HiloServidor(socket);
            hilo.start();
        }
        // Si se alcanzan 15conexiones o se pulsa el botón Salir,el programa se sale del bucle. Al pulsar Salir se
        // cierra el ServerSocket lo que provoca una excepción (SocketException) en la sentencia accept(),// la excepción se captura y se ejecuta un break para salir del bucle
        if (!servidor.isClosed()) {
            try {
                Vista.mensajeServidor2.setForeground(Color.red);
                Vista.mensajeServidor2.setText("Máximo Nº de conexiones establecidas: " + CONEXIOnes);
                servidor.close();
            } catch (IOException ex) {
                ex.printstacktrace();
            }
        } else {
            System.out.println("Servidor finalizado");
        }
    }

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == Vista.salirservidor) {
            try {
                servidor.close();
            } catch (IOException ex) {
                ex.printstacktrace();
            }
            System.exit(0);
        }
    }
}

客户端类

   public class ClienteChat extends JFrame implements ActionListener {
        private static final long serialVersionUID = 1L;
        Socket socket;
        DataInputStream fenTrada;
        DataOutputStream fsalida;
        String nombre;
        boolean repetir = true;
        static RSA rsa = new RSA();
    
        public ClienteChat(Socket socket,String nombre) {
            // Prepara la pantalla. Se recibe el socket creado y el nombre del cliente
            super("CLIENTE: " + nombre);
            setLayout(null);
            Vista.mensajeCliente.setBounds(10,30);
            add(Vista.mensajeCliente);
            Vista.scrollPaneCliente.setBounds(10,300);
            add(Vista.scrollPaneCliente);
            Vista.enviar.setBounds(420,30);
            add(Vista.enviar);
            Vista.salirCliente.setBounds(420,30);
            add(Vista.salirCliente);
            Vista.textAreaCliente.setEditable(false);
            Vista.enviar.addActionListener(this);
            this.getRootPane().setDefaultButton(Vista.enviar);
            Vista.salirCliente.addActionListener(this);
            setDefaultCloSEOperation(JFrame.EXIT_ON_CLOSE);
            this.socket = socket;
            this.nombre = nombre;
            // Se crean los flujos de enTrada y salida. En el flujo de salida se escribe un mensaje indicando que el cliente
            // se ha unido al Chat. El HiloServidor recibe este mensaje y lo reenvía a todos los clientes conectados
            try {
                fenTrada = new DataInputStream(socket.getInputStream());
                fsalida = new DataOutputStream(socket.getoutputStream());
                String texto = "SERVIDOR > Entra en el chat: " + nombre;
                fsalida.writeUTF(rsa.Encrypt(texto));
                System.out.println("\nMensaje encriptado: " + rsa.Encrypt(texto));
                System.out.println("Mensaje desencriptado: " + texto);
            } catch (IOException ex) {
                System.out.println("Error de E/S");
                ex.printstacktrace();
                System.exit(0);
            } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException e) {
                e.printstacktrace();
            }
        }
    
        // El método main es el que lanza el cliente,para ello en primer lugar se solicita el nombre o nick del cliente,// una vez especificado el nombre se crea la conexión al servidor y se crear la pantalla del Chat(ClientChat)
        // lanzando su ejecución (ejecutar()).
        public static void main(String[] args) throws NoSuchAlgorithmException {
    
            // Trabajamos con las claves privadas y públicas
            rsa.genKeyPair(512);
            rsa.savetodiskPrivateKey("rsa.pri");
            rsa.savetodiskPublicKey("rsa.pub");
    
            String nombre = JOptionPane.showInputDialog("Introduce tu nombre:");
            Socket socket = null;
            try {
                socket = new Socket("127.0.0.1",ServidorChat.PUERTO);
            } catch (IOException ex) {
                ex.printstacktrace();
                JOptionPane.showMessageDialog(null,"Imposible conectar con el servidor \n" + ex.getMessage(),"<<Mensaje de Error:1>>",JOptionPane.ERROR_MESSAGE);
                System.exit(0);
            }
            if (!nombre.trim().equals("")) {
                ClienteChat cliente = new ClienteChat(socket,nombre);
                cliente.setBounds(0,400);
                cliente.setVisible(true);
                cliente.ejecutar();
            } else {
                System.out.println("El nombre está vacío");
            }
        }
    
        // cuando se pulsa el botón Enviar,el mensaje introducido se envía al servidor por el flujo de salida
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == Vista.enviar) {
                String texto = nombre + " > " + Vista.mensajeCliente.getText();
                try {
                    Vista.mensajeCliente.setText("");
                    fsalida.writeUTF(rsa.Encrypt(texto));
                    System.out.println("\nMensaje encriptado: " + rsa.Encrypt(texto));
                    System.out.println("Mensaje desencriptado: " + texto);
                } catch (IOException | NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException ex) {
                    ex.printstacktrace();
                }
            }
            // Si se pulsa el botón Salir,se envía un mensaje indicando que el cliente abandona el chat y también se envía
            // un * para indicar al servidor que el cliente se ha cerrado
            else if (e.getSource() == Vista.salirCliente) {
                String texto = "SERVIDOR > Abandona el chat " + nombre;
                try {
                    fsalida.writeUTF(rsa.Encrypt(texto));
                    fsalida.writeUTF(rsa.Encrypt("*"));
                    System.out.println("\nMensaje encriptado: " + rsa.Encrypt(texto));
                    System.out.println("Mensaje desencriptado: " + texto);
                    repetir = false;
                } catch (IOException | NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException ex) {
                    ex.printstacktrace();
                }
            }
        }
    
        // Dentro del método ejecutar(),el cliente lee lo que el hilo le manda (mensajes del Chat) y lo muestra en el
        // textarea. Esto se ejecuta en un bucle del que solo se sale en el momento que el cliente pulse el botón Salir
        // y se modifique la variable repetir
        public void ejecutar() {
            String texto;
            while (repetir) {
                try {
                    texto = fenTrada.readUTF();
                    Vista.textAreaCliente.setText(texto);
                } catch (IOException ex) {
                    JOptionPane.showMessageDialog(null,"<<Mensaje de Error:2>>",JOptionPane.ERROR_MESSAGE);
                    repetir = false;
                }
            }
            try {
                socket.close();
                System.exit(0);
            } catch (IOException ex) {
                ex.printstacktrace();
            }
        }
    }

加密类

public class RSA {

    public java.security.PrivateKey PrivateKey = null;
    public java.security.PublicKey PublicKey = null;

    public RSA() {
    }

    public String getPrivateKeyString() {
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(this.PrivateKey.getEncoded());
        return bytesToString(pkcs8EncodedKeySpec.getEncoded());
    }

    public void setPrivateKeyString(String key) throws NoSuchAlgorithmException,InvalidKeySpecException {
        byte[] encodedPrivateKey = stringToBytes(key);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
        PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
        this.PrivateKey = privateKey;
    }

    public String getPublicKeyString() {
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(this.PublicKey.getEncoded());
        return bytesToString(x509EncodedKeySpec.getEncoded());
    }

    public void setPublicKeyString(String key) throws NoSuchAlgorithmException,InvalidKeySpecException {
        byte[] encodedPublicKey = stringToBytes(key);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedPublicKey);
        PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
        this.PublicKey = publicKey;
    }

    public void genKeyPair(int size) throws NoSuchAlgorithmException {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
        kpg.initialize(size);
        KeyPair kp = kpg.genKeyPair();
        PublicKey publicKey = kp.getPublic();
        PrivateKey privateKey = kp.getPrivate();
        this.PrivateKey = privateKey;
        this.PublicKey = publicKey;
    }

    public String Encrypt(String plain) throws NoSuchAlgorithmException,NoSuchPaddingException,InvalidKeyException,IllegalBlockSizeException,BadPaddingException {
        byte[] encryptedBytes;
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE,this.PublicKey);
        encryptedBytes = cipher.doFinal(plain.getBytes());
        return bytesToString(encryptedBytes);
    }

    public String Decrypt(String result) throws NoSuchAlgorithmException,BadPaddingException {
        byte[] decryptedBytes;
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE,this.PrivateKey);
        decryptedBytes = cipher.doFinal(stringToBytes(result));
        return new String(decryptedBytes);
    }

    public String bytesToString(byte[] b) {
        byte[] b2 = new byte[b.length + 1];
        b2[0] = 1;
        System.arraycopy(b,b2,1,b.length);
        return new BigInteger(b2).toString(36);
    }

    public byte[] stringToBytes(String s) {
        byte[] b2 = new BigInteger(s,36).toByteArray();
        return Arrays.copyOfRange(b2,b2.length);
    }

    public void savetodiskPrivateKey(String path) {
        try {
            Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path),StandardCharsets.UTF_8));
            out.write(this.getPrivateKeyString());
            out.close();
        } catch (Exception e) {
        }
    }

    public void savetodiskPublicKey(String path) {
        try {
            Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path),StandardCharsets.UTF_8));
            out.write(this.getPublicKeyString());
            out.close();
        } catch (Exception e) {
        }
    }

    public void openFromdiskPublicKey(String path) throws IOException,NoSuchAlgorithmException,InvalidKeySpecException {
        String content = this.readFileAsstring(path);
        this.setPublicKeyString(content);
    }

    public void openFromdiskPrivateKey(String path) throws IOException,InvalidKeySpecException {
        String content = this.readFileAsstring(path);
        this.setPrivateKeyString(content);
    }

    private String readFileAsstring(String filePath) throws IOException {
        StringBuffer fileData = new StringBuffer();
        BufferedReader reader = new BufferedReader(new FileReader(filePath));
        char[] buf = new char[1024];
        int numRead;
        while ((numRead = reader.read(buf)) != -1) {
            String readData = String.valueOf(buf,numRead);
            fileData.append(readData);
        }
        reader.close();
        return fileData.toString();
    }
}

查看类

public class Vista {
    // Vista del servidor
    static JTextField mensajeServidor = new JTextField("");
    static JTextField mensajeServidor2 = new JTextField("");
    static JTextArea textAreaServidor;
    static JButton salirservidor = new JButton("Salir");
    static JScrollPane scrollPaneservidor;
    // Vista del cliente
    static JTextField mensajeCliente = new JTextField();
    static JTextArea textAreaCliente = new JTextArea();
    static JScrollPane scrollPaneCliente = new JScrollPane(Vista.textAreaCliente);
    static JButton enviar = new JButton("Enviar");
    static JButton salirCliente = new JButton("Salir");
}

当我尝试将多个客户端连接到服务器时,出现此错误

javax.crypto.BadPaddingException: 解密错误 java.base/sun.security.rsa.RSAPadding.unpadV15(RSAPadding.java:378) 在 java.base/sun.security.rsa.RSAPadding.unpad(RSAPadding.java:290) 在 java.base/com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:366) 在 java.base/com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:392) 在 java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202) 在 chatAsimetrico.RSA.Decrypt(RSA.java:72) 在 chatAsimetrico.HiloServidor.run(HiloServidor.java:41)

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)