Зашифрованное соединение с сокетом java с использованием RSA (исключение IllegalArgumentException: недопустимый символ base64 10)

#java #sockets #encryption #encoding #rsa

#java #сокеты #шифрование #кодирование #rsa

Вопрос:

Итак, я создаю приложение Java, которое использует безопасное соединение RSA для доставки данных (на данный момент я вручную кодирую методы шифрования / дешифрования, если есть библиотека или лучший способ сделать это, не стесняйтесь писать), я делаю это впервые, поэтому я нене знаю, что большая часть темы.

Это класс, который я использую как на стороне сервера, так и на стороне клиента для шифрования / дешифрования:

 public class RSAEncryption extends AsymmetricalEncryption {

    public RSAEncryption() {
        super("RSA");
    }

    @Override
    public byte[] encrypt(String data) {
        Cipher cipher = null;
        byte[] encryptedData = new byte[0];
        try {
            cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, super.sharedKey);
            encryptedData = cipher.doFinal(data.getBytes());
            encryptedData = Base64.getEncoder().encode(encryptedData);
        } catch (Exception e) {
            System.err.println("Error on Cipher initialization "   e);
        }
        return encryptedData;
    }

    @Override
    public byte[] decrypt(byte[] data) {
        Cipher cipher = null;
        byte[] encryptedData = null;
        try {
            cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, super.privateKey);
            encryptedData = cipher.doFinal(Base64.getDecoder().decode(data));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encryptedData;
    }

}
  

Класс RSAencryption асимметричный encryption, потому что я хотел бы в будущем реализовать разные протоколы шифрования.
Это соединение на стороне сервера:

 public class Connection extends Thread {

    private Socket connection;
    private ObjectInputStream ois;
    private ObjectOutputStream oos;
    private AsymmetricalEncryption encryption;

    public Connection(Socket connection) {
        try {
            this.connection = connection;
            this.ois = new ObjectInputStream(connection.getInputStream());
            this.oos = new ObjectOutputStream(connection.getOutputStream());
            encryption = new RSAEncryption();
            // sending the public key
            oos.writeUTF(encryption.encodeBase64PublicKey(encryption.publicKey));
            oos.flush();
            // read sharedKey
            String encodedKey = ois.readUTF();
            encryption.setSharedKey(encryption.decodeBase64PublicKey(encodedKey, "RSA"));
            this.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Connection(Socket connection, String encryptionMethod) {
        try {
            //this.connection = connection;
            this.ois = new ObjectInputStream(connection.getInputStream());
            this.oos = new ObjectOutputStream(connection.getOutputStream());
            encryption = AsymmetricalEncryptionFactory.getAsymmetricalEncryption(encryptionMethod);
            // sending the public key
            oos.writeUTF(encryption.encodeBase64PublicKey(encryption.publicKey));
            oos.flush();
            // read sharedKey
            String encodedKey = ois.readUTF();
            encryption.setSharedKey(encryption.decodeBase64PublicKey(encodedKey, encryptionMethod));
            this.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        boolean isRunning = true;
        while(isRunning){
            try {
                byte[] encryptedStringRequest = ois.readAllBytes();
                System.out.println("Encrypted Request: "   encryptedStringRequest);
                byte[] stringRequest = encryption.decrypt(encryptedStringRequest);
                System.out.println("Request: "   new String(stringRequest));
                System.out.println("When you fix the bug remeber to decomment the following two lines of Connection");
                //Request request = Request.parse(this, new JSONObject(stringRequest));
                //RequestQueue.RequestQueue().enqueue(request);
            } catch (Exception e) {
                e.printStackTrace();
                isRunning = false;
            }
        }
    }

    public void response(Response response){
        try {
            byte[] encryptedStringResponse = encryption.encrypt(response.toJSONString().getBytes());
            oos.write(encryptedStringResponse);
            oos.flush();
        } catch (IOException e) {
            System.err.println("Error during response sending "  e);
        }
    }

}
  

Это соединение на стороне клиента:

 public class Connection {

    private Socket connection;
    private ObjectInputStream ois;
    private ObjectOutputStream oos;
    private AsymmetricalEncryption encryption;

    public Connection(Socket connection) {
        try {
            this.connection = connection;
            this.oos = new ObjectOutputStream(connection.getOutputStream());
            this.ois = new ObjectInputStream(connection.getInputStream());
            encryption = new RSAEncryption();
            // sending the public key
            oos.writeUTF(encryption.encodeBase64PublicKey(encryption.publicKey));
            oos.flush();
            // read sharedKey
            String encodedKey = ois.readUTF();
            encryption.setSharedKey(encryption.decodeBase64PublicKey(encodedKey, "RSA"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Connection(Socket connection, String encryptionMethod) {
        try {
            //this.connection = connection;
            this.ois = new ObjectInputStream(connection.getInputStream());
            this.oos = new ObjectOutputStream(connection.getOutputStream());
            encryption = AsymmetricalEncryptionFactory.getAsymmetricalEncryption(encryptionMethod);
            // sending the public key
            oos.writeUTF(encryption.encodeBase64PublicKey(encryption.publicKey));
            oos.flush();
            // read sharedKey
            String encodedKey = ois.readUTF();
            encryption.setSharedKey(encryption.decodeBase64PublicKey(encodedKey, encryptionMethod));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Response request(Request request){
        try {
            byte[] encryptedStringResponse = encryption.encrypt(request.toJSONString());
            oos.write(encryptedStringResponse);
            oos.flush();
            byte[] response =  ois.readAllBytes();
            response = encryption.decrypt(response);
            return new Response(new JSONObject(new String(response)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}
  

На данный момент я не знаю, почему, но когда я отправляю данные с клиента на сервер, сервер сообщает, что установлено новое соединение, но зависает, ничего не делая, в то время как на стороне клиента говорится, что данные были отправлены.

Комментарии:

1. В encrypt методе зашифрованный текст преобразуется в строку с кодировкой символов. Это искажает данные. Кроме того, открытый текст кодируется в Base64 перед шифрованием, что не имеет особого смысла. Кодирование Base64 должно выполняться после шифрования, что также лучше подходит для decrypt метода (где зашифрованный текст декодируется по Base64 перед расшифровкой).

2. Итак, я должен отправить byte[] вместо string и переместить шифрование перед кодированием, верно? Знаете ли вы какой-либо другой лучший способ установления безопасных соединений в java?

3. Спасибо, но я уже устранил проблему и начал использовать byte[] encryptedStringRequest = (byte[]) ois.readObject(); на сервере и oos.writeObject(encryptedStringResponse); oos.reset(); на клиенте, в любом случае в этом проекте может быть много разных клиентов, закодированных на разных языках, которые используют разные фреймворки, например nodejs, в текущем состоянии использование ObjectOutputStream не будет работать с разными языками, должен ли я использовать BufferedReader вместо?

Ответ №1:

Лучший способ сделать это — использовать TLS. В Java вы можете создавать серверные и клиентские фабрики сокетов с помощью SSLContext .

Было бы неэтично пытаться разработать свою собственную криптосистему и применять ее к любым данным, которые другие люди доверяют вам защищать. Но если вы делаете это для развлечения, в качестве учебного упражнения и не собираетесь выполнять с ним какую-либо реальную работу, вы можете максимально использовать свои усилия, пытаясь узнать, как работают настоящие криптосистемы.

RSA работает относительно медленно, и объем данных, которые могут быть зашифрованы в каждой операции, ограничен выбранным вами размером ключа RSA. Его лучше всего использовать для шифрования другого ключа для симметричного шифра, такого как AES. Именно так RSA используется в любой широко принятой схеме шифрования. Затем большие объемы данных приложения могут быть быстро зашифрованы с помощью симметричного шифрования с использованием ключа, обмениваемого через RSA.

Комментарии:

1. Хорошо, проблема в том, что клиенты, которые подключаются к моему серверу, могут быть закодированы на разных языках, обычно javascript с node, используя ваш подход, могу ли я это сделать?

2. @AndreaDattero Да, каждая платформа будет поддерживать TLS, поэтому вам не нужно самостоятельно внедрять протокол для каждой клиентской платформы.

3. И сам TLS является примером, который использует криптографию с открытым ключом, такую как RSA, для обмена закрытым ключом для использования в симметричном шифре, таком как AES.