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

huangapple go评论79阅读模式
英文:

Problem while trying to send an object encrypted with AES

问题

以下是您提供的代码的翻译部分:

// Client
public class Client {
    // ...
    public Client(String hostname) {
        try {
            Socket socket = new Socket(hostname, port);
            // ...

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // ...

                        // 创建使用 CipherOutputStream 的 ObjectOutputStream
                        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);
                        // ...

                        objectOutputStream.writeObject(p);
                        objectOutputStream.flush();
                        // ...
                    } catch (IOException | ClassNotFoundException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
                        e.printStackTrace();
                    }
                }
            }).start();

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

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

            // ...

            new Thread(new Runnable() {
                @Override
                public void run() {
                    Socket socket;

                    try {
                        socket = finalServerSocket.accept();

                        // ...

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

                        // 创建使用 CipherInputStream 的 ObjectInputStream
                        CipherInputStream cipherInputStream = new CipherInputStream(socket.getInputStream(), aesCipher);
                        // ...

                        ObjectInputStream objectInputStream = new ObjectInputStream(cipherInputStream);
                        // ...

                        Person p = (Person) objectInputStream.readObject();
                        // ...
                    } catch (IOException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | ClassNotFoundException ex) {
                        ex.printStackTrace();
                    }
                }
            }).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// Person
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 + '\'' +
                '}';
    }
}

(注意:由于代码较长,此处仅提供部分翻译内容。如有必要,您可以继续翻译其余部分。)

英文:

I'm having a problem trying to send an object through a CipherOutputStream in order to encrypt it with AES and receive it with a CipherInputStream in order to decrypt it.

The problem is that the server is unable to receive the object:

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

(It blocks before this:)

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

While if instead of the CipherOutputStream in the client, and the CipherInputStream in the server I had directly used the ObjectOutputStream and the ObjectInputStream then it would have received it correctly (I know because I tried it).

Do you have any alternative methods to suggest to send AES encrypted objects or an idea of ​​how to solve the problem?

Thanks in advance.

Main

public class Main {
public static void main (String[] args) {
Server s = new Server();
Client c = new Client(s.serverSocket.getInetAddress().getHostName());
}
}

Client

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();
}
}
}

Server

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();
}
}
}

Person

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 + '\'' +
'}';
}
}

Cryptography

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;
}
}

答案1

得分: 1

首先:感谢您提供的精彩示例,这正是我项目所需要的 问题出现在尝试使用AES加密发送对象时。

您代码中的问题在客户端,因为您忘记关闭 ObjectOutputStream。在您的刷新操作之后添加一行代码:

objectOutputStream.writeObject(p);
objectOutputStream.flush();
// 新增的一行
objectOutputStream.close();

这样示例将按预期运行:

...
Server > INFO > 创建了 cipherInputStream
Server > INFO > 创建了 ObjectInputStream
Server > INFO > 收到一个 Person 对象:Person{name='John', surname='Smith'}

编辑 1:

正如您在问题中所写,完整的工作流程在不使用 CipherOutput/InputStream 的情况下运行,因此行为异常的原因在于 CipherOutputStream。

请查看 Javadocs(https://docs.oracle.com/javase/7/docs/api/javax/crypto/CipherOutputStream.html#flush()),您会找到以下内容:

通过强制写出已由封装的加密对象处理的任何已缓冲输出字节,来刷新此输出流。由封装的加密器缓冲并等待它处理的任何字节 将不会被写出

例如,如果封装的加密器是块加密器,并且使用 write 方法之一写入的总字节数小于加密块的大小,则不会写出任何字节

作为解决方案(根据数据量的多少),我建议在客户端在序列化后将数据进行加密并保存为 byte[],然后将加密数据传递给 dataOutputStream.write,服务器端也进行相反的操作。这样一来,您就不需要关闭 objectOutputStream,套接字仍然保持开放状态以进行下一次传输。

英文:

Firstly: thanks for the fine example that I needed for my project already 问题出现在尝试使用AES加密发送对象时。

The problem in your code is on the Client-side because you missed closing the ObjectOutputStream. Adding one line of code after your flushing:

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

let the example run like expected:

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

Edit 1:

As you wrote in your question the complete workflow runs WITHOUT using CipherOutput/InputStream so the reason for the behavior is in the
CipherOutputStream.

Please see the Javadocs (https://docs.oracle.com/javase/7/docs/api/javax/crypto/CipherOutputStream.html#flush()) and you find this:

Flushes this output stream by forcing any buffered output bytes that have already been processed by the encapsulated cipher
object to be written out. Any bytes buffered by the encapsulated cipher and waiting to be processed by it will not be written out.

For example, if the encapsulated cipher is a block cipher, and the total number of bytes written using one of the write methods is
less than the cipher's block size, no bytes will be written out.

As solution (depends on the amount of data) I would recommend to encrypt the data (after serialization) in memory on Client side and pass the encrypted data as byte[] to dataOutputStream.write and vice versa on Server side. That way you don't need to close the objectOutputStream and the socket is still open for next transmission.

huangapple
  • 本文由 发表于 2020年8月25日 16:52:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/63575301.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定