java.security.InvalidKeyException 在使用 RSA 的 Java AES 密钥交换期间抛出

java.security.InvalidKeyException thrown during Java AES key exchange using RSA

我正在尝试在 Java 中制作一个 client/server 程序,允许服务器将使用 AES 加密的消息发送到客户端。现在,我在创建密钥交换协议时遇到了问题。此密钥交换当前的工作方式是:

  1. 客户端生成 RSA public/private 密钥对
  2. 客户端向服务器发送他的 RSA public 密钥
  3. 服务器使用客户端的 RSA public 密钥生成并加密 AES 密钥
  4. 服务器向客户端发送加密的 AES 密钥
  5. 双方现在都有正确的 AES 密钥,所有消息都可以使用 AES 加密

但是,每次进入第三步时,我都无法使用客户端的 RSA public 密钥加密生成的 AES 密钥,因为出现此错误:

java.security.InvalidKeyException: No installed provider supports this key: javax.crypto.spec.SecretKeySpec
    at java.base/javax.crypto.Cipher.chooseProvider(Cipher.java:919)
    at java.base/javax.crypto.Cipher.init(Cipher.java:1275)
    at java.base/javax.crypto.Cipher.init(Cipher.java:1212)
    at test.Server.<init>(Server.java:50)
    at test.Start.main(Start.java:11)

因此,我无法完成我正在尝试执行的 AES 密钥交换。

Server.java用来做服务器端的事,Client.java用来做客户端的事。我的 Server.java 文件如下所示:

public class Server {
    private ServerSocket serverSocket; // Server socket
    private Socket socket; // Socket
    private BufferedReader in; // Reading from stream
    private PrintWriter out; // Writing to stream
    private Key key; // AES key used for encryption

    // Constructor
    public Server() {
        // Initialize the server socket
        try {
            // Setup connections
            serverSocket = new ServerSocket(12345);
            socket = serverSocket.accept();
            out = new PrintWriter(socket.getOutputStream(), true);
            in = new BufferedReader(newInputStreamReader(socket.getInputStream()));

            // Receive the client's public RSA key
            byte[] encodedClientKey = Base64.getDecoder().decode(in.readLine());
            Key clientRSAKey = new SecretKeySpec(encodedClientKey, 0, encodedClientKey.length, "RSA");

            // Generate AES key
            KeyGenerator aesKeyGen = KeyGenerator.getInstance("AES");
            aesKeyGen.init(256);
            key = aesKeyGen.generateKey();

            // Encrypt the AES key using the client's RSA public key
            Cipher c = Cipher.getInstance("RSA");
            c.init(Cipher.ENCRYPT_MODE, clientRSAKey);
            byte[] encryptedAESKey = c.doFinal(key.getEncoded());

            // Send the encrypted AES key to the client
            sendUnencrypted(Base64.getEncoder().encodeToString(encryptedAESKey));
        } catch (IOException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
            | IllegalBlockSizeException | BadPaddingException e) {
            e.printStackTrace();
        }
    }

    // Receive an unencrypted message
    public String receiveUnencrypted() {
        try {
            // Wait until the stream is ready to be read
            while (true)
                if (in.ready())
                    break;

            return in.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    // Send an unencrypted message
    public void sendUnencrypted(String message) {
        out.println(message);
        out.flush();
    }

    // Send an encrypted message
    public void send(String message) {
        try {
            // Encrypt the message
            Cipher c = Cipher.getInstance("AES");
            c.init(Cipher.ENCRYPT_MODE, key);
            String encoded = Base64.getEncoder().encodeToString(message.getBytes("utf-8"));
            byte[] encrypted = c.doFinal(encoded.getBytes());
            String encryptedString = Base64.getEncoder().encodeToString(encrypted);

            // Send the encrypted message
            out.println(encryptedString);
            out.flush();
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException
            | BadPaddingException | UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
}

我的 Client.java 文件如下所示:

public class Client {
    private Socket socket; // Socket
    private BufferedReader in; // Reading from stream
    private PrintWriter out; // Writing to stream
    private Key key; // AES key

    // Constructor
    public Client() {
        try {
            // Create streams to server
            socket = new Socket("127.0.0.1", 12345);
            out = new PrintWriter(socket.getOutputStream(), true);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            // Generate an RSA key pair
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
            keyGen.initialize(2048);
            KeyPair kp = keyGen.generateKeyPair();

            // Send out our public key to the server
            byte[] publicKey = kp.getPublic().getEncoded();
            sendUnencrypted(Base64.getEncoder().encodeToString(publicKey));

            // Recieve and decrypt the AES key sent from the server
            String encryptedKey = receiveUnencrypted();
            Cipher c = Cipher.getInstance("RSA");
            c.init(Cipher.DECRYPT_MODE, kp.getPrivate());
            byte[] AESKey = c.doFinal(encryptedKey.getBytes());
            key = new SecretKeySpec(AESKey, 0, AESKey.length, "AES");
        } catch (IOException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
                | IllegalBlockSizeException | BadPaddingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    // Receive an unencrypted message
    public String receiveUnencrypted() {
        try {
            // Wait until the stream is ready to be read
            while (true)
                if (in.ready())
                    break;

            return in.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    // Send an unencrypted message
    public void sendUnencrypted(String message) {
        out.println(message);
        out.flush();
    }

    // Receive an encrypted message
    public String receive() {
        try {
            // Wait until the stream is ready to be read
            while (true)
                if (in.ready())
                    break;

            // Obtain the encrypted message
            String encrypted = in.readLine();

            // Decrypt and return the message
            Cipher c = Cipher.getInstance("AES");
            c.init(Cipher.DECRYPT_MODE, key);
            byte[] decoded = Base64.getDecoder().decode(encrypted);
            String utf8 = new String(c.doFinal(decoded));
            String plaintext = new String(Base64.getDecoder().decode(utf8));

            // Return the message
            return plaintext;
        } catch (IOException | InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
                | IllegalBlockSizeException | BadPaddingException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Start.java用于初始化服务器和客户端。

package test;

import java.util.Scanner;

public class Start {
    public static void main(String args[]) {
        Scanner scan = new Scanner(System.in);
        System.out.println("1.) Create data server.\n2.) Connect to data server.\nPlease select an option: ");
        int option = scan.nextInt();
        if (option == 1) {  // Setup a server if they choose option one
            Server s = new Server();
            s.send("Hello");
        } else if (option == 2) {  // Setup a client if they choose option two
            Client c = new Client();
            System.out.println(c.receive());
        }

        // Close scanner
        scan.close();
    }
}

首先,您不能使用 SecretKeySpec 来恢复 RSA public 密钥。在 Server 的构造函数中,更改

Key clientRSAKey = new SecretKeySpec(encodedClientKey, 0, encodedClientKey.length, "RSA");

Key clientRSAKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(encodedClientKey));

其次,您需要对base64编码的加密密钥进行解码。在您的 Client 构造函数中,更改

String encryptedKey = receiveUnencrypted();

byte[] encryptedKey = Base64.getDecoder().decode(receiveUnencrypted());

最后,在您的 Client 构造函数中,更改

byte[] AESKey = c.doFinal(encryptedKey.getBytes());

byte[] AESKey = c.doFinal(encryptedKey);