如何正确地通过套接字进行非对称加密和解密 java

How to properly asymmetrically encrypt and decrypt over sockets java

我希望能够使用具有指定端口的回显模型架构在简单的客户端和服务器上加密和解密字符串。

客户端与服务端通信方式如下:

客户端首先是 运行 并生成一个密钥对,将其 public 密钥打印到控制台,并等待输入(这将是服务器的 public 密钥通过复制和粘贴)

然后服务器 运行 也做同样的事情。打印出它的 public 密钥并等待客户端的 public 密钥(也复制和粘贴。)

先复制粘贴(客户端public键)到服务器,然后启动服务器监听端口4444。

然后客户端也通过复制和粘贴启动(服务器 public 键),通过端口 4444 发送一个字符串。 使用服务器的 public 密钥加密。

字符串将由客户端加密,发送,并由将解密字符串的服务器接收。我想添加的附加功能应该允许对多个字符串进行加密和解密(通过 while 循环完成)。

我的客户端控制台似乎能够使用 out.write() and out.flush() 发送字符串(转换为字节,然后发送),但使用(数据不为空)读取加密字节时遇到问题... in.read().

抛出的异常是Exception in thread "main" javax.crypto.BadPaddingException: Decryption error

我的客户代码:


public class EchoClient {
    private Socket clientSocket;
    private DataOutputStream out;
    private DataInputStream in;

    private PrivateKey generateKeyPairClient() throws NoSuchAlgorithmException {
        final KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
        kpg.initialize(2048);

        final KeyPair kp = kpg.generateKeyPair();

        final PublicKey clientPublicKey = kp.getPublic();

        System.out.println(clientPublicKey);
        System.out.println("Client Public Key (Encoded) is " + Base64.getEncoder().encodeToString(clientPublicKey.getEncoded()));
        return kp.getPrivate();
    }

    private PublicKey obtainServerPublicKey() {
        System.out.println("Please enter the servers's public key below:");
        Scanner sc = new Scanner(System.in);
        String key = sc.next();
        sc.close();
        PublicKey serverPublicKey = null;
        try{
            byte[] byteKey = Base64.getDecoder().decode(key.getBytes());
            X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(byteKey);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            serverPublicKey = kf.generatePublic(X509publicKey);
        }
        catch(Exception e){
            System.out.println("The public key you entered for the server is not valid, please try again");
        }
        return serverPublicKey;
    }

    private void startConnection(String ip, int port) {
        try {
            clientSocket = new Socket(ip, port);
            out = new DataOutputStream(clientSocket.getOutputStream());
            in = new DataInputStream(clientSocket.getInputStream());
        } catch (IOException e) {
            System.out.println("Error when initializing connection");
        }
    }

    private void sendPlainTextToEncryptedText(String original, PublicKey serverPublicKey, PrivateKey clientPrivateKey) {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, serverPublicKey);

            byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8);
            byte[] cipherTextBytes = cipher.doFinal(originalBytes);

            System.out.println("Client will send the ciphertext: "+ Util.bytesToHex(cipherTextBytes));

            out.write(cipherTextBytes);
            out.flush();
        } catch (Exception e) {
            System.out.println("There was a problem when trying to encrypt/decrypt, please try again");
            e.printStackTrace();
            return;
        }
    }

    private void stopConnection() {
        try {
            in.close();
            out.close();
            clientSocket.close();
        } catch (IOException e) {
            System.out.println("error when closing");
        }
    }

    public static void main(String[] args) throws NoSuchAlgorithmException {
        EchoClient client = new EchoClient();

        PrivateKey clientPrivateKey = client.generateKeyPairClient(); 

        PublicKey serverPublicKey = client.obtainServerPublicKey();

        System.out.println("Key Exchange Complete for Client");

        client.startConnection("127.0.0.1", 4444);

        client.sendPlainTextToEncryptedText("everyone", serverPublicKey, clientPrivateKey);

        client.stopConnection();
    }
}

我的服务器代码:


public class EchoServer {
    private ServerSocket serverSocket;
    private Socket clientSocket;
    private DataOutputStream out;
    private DataInputStream in;

    private PrivateKey generateKeyPairServer() throws NoSuchAlgorithmException {
        final KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
        kpg.initialize(2048);

        final KeyPair kp = kpg.generateKeyPair();

        final PublicKey serverPublicKey = kp.getPublic();

        System.out.println(serverPublicKey);
        System.out.println("Server Public Key (Encoded) is " + Base64.getEncoder().encodeToString(serverPublicKey.getEncoded()));
        return kp.getPrivate();
    }

    private PublicKey obtainClientPublicKey() {
        System.out.println("Please enter the clients's public key below:");
        Scanner sc = new Scanner(System.in);
        String key = sc.next();
        sc.close();
        PublicKey clientPublicKey = null;
        try{
            byte[] byteKey = Base64.getDecoder().decode(key.getBytes());
            X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(byteKey);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            clientPublicKey = kf.generatePublic(X509publicKey);
        }
        catch(Exception e){
            System.out.println("The public key you entered for the client is not valid, please try again");
        }
        return clientPublicKey;
    }

    public void start(int port) {
        try {
            serverSocket = new ServerSocket(port);
            clientSocket = serverSocket.accept();
            out = new DataOutputStream(clientSocket.getOutputStream());
            in = new DataInputStream(clientSocket.getInputStream());   
        } catch (IOException e) {
            System.out.println("Error when establishing/accepting server connection");
        }
    }

    private void receiveEncryptedTextToPlainText(PrivateKey serverPrivateKey) throws UnsupportedEncodingException, IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        byte[] data = new byte[8];
        @SuppressWarnings("unused")
        int numBytes;
        while ((numBytes = in.read(data)) != -1) {
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.DECRYPT_MODE, serverPrivateKey);

            byte[] decryptedBytes = cipher.doFinal(data);
            String text = new String(decryptedBytes, StandardCharsets.UTF_8);

            System.out.println("Server has recieved the plain text: "+ text);
        }
        stop();
    }

    public void stop() {
        try {
            in.close();
            out.close();
            clientSocket.close();
            serverSocket.close();
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, UnsupportedEncodingException, IOException, IllegalBlockSizeException, BadPaddingException {
        EchoServer server = new EchoServer();

        PrivateKey serverPrivateKey = server.generateKeyPairServer(); 
        
        //Sorry for the mis-named var, will rename in code
        PublicKey serverPublicKey = server.obtainClientPublicKey();

        System.out.println("Key Exchange Complete for Server");
        System.out.println("Now waiting for incoming connection...");

        server.start(4444);

        server.receiveEncryptedTextToPlainText(serverPrivateKey);

        server.stop();
    }
}

Util辅助class

import java.io.UnsupportedEncodingException;

public class Util {
    public static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X ", b));
        }
        return sb.toString();
    }

    public static String strToHex(String s) {
        s = "failed decoding";
        try  {
            s = Util.bytesToHex(s.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            System.out.println("Unsupported Encoding Exception");
        }
        return s;
    }
}

控制台输出:

我不确定这里可能出了什么问题,有什么建议吗?

我注意到两件事我认为可能是原因:

注意:我正在复制和粘贴 public 键(将它们转换为字符串并返回 public 键对象 - 可能是个问题...)

注意 2:对于较长的字符串,通过 8 字节大小的套接字传输字节可能会出现问题

解密前必须阅读全部cipher-data。在 EchoServer:

private void receiveEncryptedTextToPlainText(PrivateKey serverPrivateKey) throws UnsupportedEncodingException, IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
    byte[] data = new byte[8];
    ByteArrayOutputStream cipherData = new ByteArrayOutputStream();
    @SuppressWarnings("unused")
    int numBytes;
    while ((numBytes = in.read(data)) != -1) {
        cipherData.write(data, 0, numBytes);
    }
    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    cipher.init(Cipher.DECRYPT_MODE, serverPrivateKey);
    byte[] decryptedBytes = cipher.doFinal(cipherData.toByteArray());
    String text = new String(decryptedBytes, StandardCharsets.UTF_8);
    System.out.println("Server has recieved the plain text: " + text);
    stop();
}