问题描述
服务器线程类
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 (将#修改为@)