尝试发送使用 AES 加密的对象时出现问题

Problem while trying to send an object encrypted with AES

我在尝试通过 CipherOutputStream 发送对象以便使用 AES 加密并使用 CipherInputStream 接收它以便解密时遇到问题。

问题是服务器无法接收对象:

Client > INFO > created ObjectOutputStream
Client > INFO > sent a Person object through ObjectOutputStream
Server > INFO > created cipherInputStream
Server > INFO > created ObjectInputStream

(在此之前阻塞:)

Person p = (Person) objectInputStream.readObject();
System.out.println("Server > INFO > received a Person object: " + p.toString());

虽然如果我直接使用 ObjectOutputStream 和 ObjectInputStream 而不是客户端中的 CipherOutputStream 和服务器中的 CipherInputStream 那么它会正确接收它(我知道因为我尝试过)。

您是否有任何替代方法建议发送 AES 加密对象或解决问题的想法?

提前致谢。

主要

public class Main {
    public static void main (String[] args) {
        Server s = new Server();

        Client c = new Client(s.serverSocket.getInetAddress().getHostName());
    }
}

客户

import javax.crypto.*;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.security.*;
import java.util.Arrays;

public class Client {
    private final int port = 3535;

    private PublicKey publicKey;
    private PrivateKey privateKey;

    private SecretKey aesKey;

    public Client(String hostname) {
        try {
            Socket socket = new Socket(hostname, port);
            System.out.println("Client > INFO > Connected to server: " + socket.toString());

            KeyPair keyPair = Cryptography.rsaKey();
            if(keyPair == null) {
                System.out.println("Client > ERROR > keyPair is null!");
                return;
            }

            publicKey = keyPair.getPublic();
            privateKey = keyPair.getPrivate();

            //System.out.println("Client > publicKey > " + publicKey.toString());
            //System.out.println("Client > privateKey created");

            aesKey = Cryptography.aesKey();
            if(aesKey == null) {
                System.out.println("Client > ERROR > aesKey is null!");
                return;
            }

            System.out.println("Client > INFO > aesKey > " + Arrays.toString(aesKey.getEncoded()));;

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // Receiving the RSA - PublicKey
                        ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                        System.out.println("Client > INFO > created objectInputStream: " + objectInputStream.toString());

                        PublicKey publicKey1 = (PublicKey) objectInputStream.readObject();
                        System.out.println("Client > INFO > publicKey received from server: " + publicKey1.toString());

                        // Creating a Cipher object
                        Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");

                        // Initializing the Cipher object for encrypting
                        rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey1);

                        byte[] encryptedKey = rsaCipher.doFinal(aesKey.getEncoded());
                        //System.out.println("Client > INFO > sending encrypted aesKey: " + Arrays.toString(encryptedKey));

                        // Sending the AES - SecretKey encrypted with the RSA - PublicKey
                        DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());

                        dataOutputStream.writeInt(encryptedKey.length);
                        dataOutputStream.flush();

                        dataOutputStream.write(encryptedKey);
                        dataOutputStream.flush();

                        // Creating an ObjectOutputStream over a CipherOutputStream
                        Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
                        aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);

                        Person p = new Person();
                        p.setName("John");
                        p.setSurname("Smith");

                        CipherOutputStream cipherOutputStream = new CipherOutputStream(socket.getOutputStream(), aesCipher);

                        ObjectOutputStream objectOutputStream = new ObjectOutputStream(cipherOutputStream);
                        System.out.println("Client > INFO > created ObjectOutputStream");

                        objectOutputStream.writeObject(p);
                        objectOutputStream.flush();
                        System.out.println("Client > INFO > sent a Person object through ObjectOutputStream");
                    } catch (IOException | ClassNotFoundException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
                        e.printStackTrace();
                    }
                }
            }).start();

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

服务器

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.*;
import java.util.Arrays;

public class Server {
    private final int port = 3535;

    public ServerSocket serverSocket;

    private PublicKey publicKey;
    private PrivateKey privateKey;

    public Server() {
        try {
            serverSocket = new ServerSocket(port);

            KeyPair keyPair = Cryptography.rsaKey();
            if(keyPair == null) {
                System.out.println("Server > ERROR > keyPair is null!");
                return;
            }

            publicKey = keyPair.getPublic();
            privateKey = keyPair.getPrivate();

            System.out.println("Server > publicKey > " + publicKey.toString());
            System.out.println("Server > privateKey created");

            ServerSocket finalServerSocket = serverSocket;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Socket socket;

                    try {
                        socket = finalServerSocket.accept();

                        System.out.println("Server > INFO > A client connected: " + socket.toString());

                        // Sending the RSA - PublicKey
                        ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
                        System.out.println("Server > INFO > created objectOutputStream: " + objectOutputStream.toString());

                        objectOutputStream.writeObject(publicKey);
                        objectOutputStream.flush();

                        // Receiving the encrypted AES - SecretKey
                        DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());

                        int length = dataInputStream.readInt();
                        System.out.println("Server > INFO > received encrypted aesKey's length: " + length);
                        byte[] encryptedKey;
                        if(length > 0) {
                            encryptedKey = new byte[length];
                            dataInputStream.readFully(encryptedKey, 0, encryptedKey.length);
                        } else {
                            System.out.println("Server > ERROR > length received is <= 0");
                            return;
                        }

                        System.out.println("Server > INFO > received encrypted aesKey: " + Arrays.toString(encryptedKey));

                        // Decrypting the encrypted AES - SecretKey
                        Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
                        rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
                        byte[] decryptedKey = rsaCipher.doFinal(encryptedKey);

                        // Converting the decrypted AES - SecretKey to a SecretKey
                        SecretKey aesKey = new SecretKeySpec(decryptedKey, 0, decryptedKey.length, "AES");

                        System.out.println("Server > INFO > converted decrypted aesKey: " + Arrays.toString(aesKey.getEncoded()));

                        Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
                        aesCipher.init(Cipher.DECRYPT_MODE, aesKey);

                        // Creating an ObjectInputStream over a CipherInputStream
                        CipherInputStream cipherInputStream = new CipherInputStream(socket.getInputStream(), aesCipher);
                        System.out.println("Server > INFO > created cipherInputStream");

                        ObjectInputStream objectInputStream = new ObjectInputStream(cipherInputStream);
                        System.out.println("Server > INFO > created ObjectInputStream");

                        Person p = (Person) objectInputStream.readObject();
                        System.out.println("Server > INFO > received a Person object: " + p.toString());
                    } catch (IOException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | ClassNotFoundException ex) {
                        ex.printStackTrace();
                    }
                }
            }).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private String surname;

    public Person() {}

    public void setName(String name) {
        this.name = name;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", surname='" + surname + '\'' +
                '}';
    }
}

密码学

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;

public class Cryptography {
    public static KeyPair rsaKey() {
        try {
            // Creating a Signature object
            Signature sign = Signature.getInstance("SHA256withRSA");

            // Creating KeyPair generator object
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");

            // Initializing the key pair generator
            keyPairGen.initialize(2048);

            // Generating the pair of keys
            return keyPairGen.generateKeyPair();
        } catch (NoSuchAlgorithmException ignored) {}

        return null;
    }

    public static SecretKey aesKey() {
        try {
            KeyGenerator generator = KeyGenerator.getInstance("AES");
            generator.init(128); // The AES key size in number of bits

            return generator.generateKey();
        } catch (NoSuchAlgorithmException ignored) {}

        return null;
    }
}

首先:感谢我已经为我的项目提供了很好的例子:-)

您的代码中的问题出在 Client-side 上,因为您错过了关闭 ObjectOutputStream。在刷新后添加一行代码:

objectOutputStream.writeObject(p);
objectOutputStream.flush();
// new line
objectOutputStream.close();

让示例 运行 符合预期:

...
Server > INFO > created cipherInputStream
Server > INFO > created ObjectInputStream
Server > INFO > received a Person object: Person{name='John', surname='Smith'}

编辑 1:

正如您在问题中所写的那样,完整的工作流程 运行s 没有使用 CipherOutput/InputStream 所以行为的原因在于 CipherOutputStream.

请参阅 Javadocs (https://docs.oracle.com/javase/7/docs/api/javax/crypto/CipherOutputStream.html#flush()),您会发现:

通过强制任何已被封装密码处理过的缓冲输出字节来刷新此输出流 要写出的对象。封装密​​码缓冲并等待其处理的任何字节将不会被写出

例如,如果封装的密码是块密码,并且使用其中一种写入方法写入的字节总数是 小于密码的块大小,不会写出任何字节

作为解决方案(取决于数据量),我建议在客户端对内存中的数据(序列化后)进行加密,并将加密数据作为 byte[] 传递给 dataOutputStream.write,反之亦然服务器端。这样你就不需要关闭 objectOutputStream 并且套接字仍然打开以进行下一次传输。