Чрезвычайно медленное встроенное шифрование AES с Java - PullRequest
0 голосов
/ 10 декабря 2018

У меня много очень маленьких данных (19 байт), которые необходимо зашифровать и отправить на удаленный сервер через tcp в зашифрованном формате.Я использую код ниже, чтобы сделать это.

package aesclient;

import java.io.OutputStream;
import java.net.Socket;

import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AESClient {
    static byte[] plaintext = new byte[] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53};
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1", 1337); // connecting to server on localhost
            OutputStream outputStream = socket.getOutputStream();
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            String s_key = "Random09" + "Random09"; // 16 Byte = 128 Bit Key
            byte[] b_key = s_key.getBytes();
            SecretKeySpec sKeySpec = new SecretKeySpec(b_key, "AES");
            SecureRandom random = SecureRandom.getInstanceStrong();
            byte[] IV = new byte[16]; // initialization vector
            int num = 10000;
            long start = System.nanoTime();
            for (int i = 0; i < num; ++i) {
                random.nextBytes(IV);
                IvParameterSpec ivSpec = new IvParameterSpec(IV);
                cipher.init(Cipher.ENCRYPT_MODE, sKeySpec, ivSpec);
                byte[] msg = new byte[16 + 32];
                System.arraycopy(IV, 0, msg, 0, IV.length);
                byte[] encrypted = cipher.doFinal(plaintext);
                System.arraycopy(encrypted, 0, msg, IV.length, encrypted.length);
                outputStream.write(msg);
                outputStream.flush();      
            }
            long end = System.nanoTime();
            long duration = end - start;
            double drate = ((double)plaintext.length*(double)num)/((double)duration/1000000000);
            System.out.println("Verschlüsselung:\n" + num + " mal 19 Bytes in " + ((double)duration/1000000000) + " s\nData Rate = " + drate/1000.0 + " kBytes/s");
        }
        catch (Exception e) {
            System.err.println(e.getMessage());
        }
    } 
}

Мне интересно, почему это очень медленно.Я получаю вывод, подобный этому:

Verschlüsselung:
10000 mal 19 Bytes in 2.566016627 s
Data Rate = 74.04472675694785 kBytes/s

, что означает, что у меня скорость передачи данных 74 кБайт / с исходных (незашифрованных) данных.Скорость передачи данных незначительно возрастает, если я пропускаю отправку по TCP (тогда она составляет около 100 КБ / с).Я читал о скорости передачи данных, которая составляет около 20 МБ / с или даже выше.У меня ноутбук с Windows 10 и процессором i5.Буду благодарен за любую помощь.Как я уже сказал, мне просто нужно передать много небольших пакетов данных (19 байт) в зашифрованном виде.

Ответы [ 3 ]

0 голосов
/ 10 декабря 2018

Действительно SecureRandom в том виде, в котором он используется, печально известен медленной и даже блокирующей работой.Это горлышко бутылки здесь.Поэтому, если возможно, зашифруйте больший буфер, несколько сообщений.

В противном случае необходимо учесть еще несколько незначительных моментов:

        OutputStream outputStream = socket.getOutputStream();
        int bufSize = Math.min(socket.getSendBufferSize(), 1024);
        outputStream = new BufferedOutputStream(sock, bufSize);

        byte[] b_key = s_key.getBytes(StandardCharsets.ISO_8859_1);

        byte[] msg = new byte[16 + 32];
        for (int i = 0; i < num; ++i) {
            random.nextBytes(IV);
            IvParameterSpec ivSpec = new IvParameterSpec(IV);
            cipher.init(Cipher.ENCRYPT_MODE, sKeySpec, ivSpec);
            System.arraycopy(IV, 0, msg, 0, IV.length);
            byte[] encrypted = cipher.doFinal(plaintext);
            System.arraycopy(encrypted, 0, msg, IV.length, encrypted.length);
            outputStream.write(msg);
        }
        outputStream.flush();      

Существуют более эффективные способы работы с байтовыми массивами с использованием перегруженных doFinal .Затем код очищается, удаляет arraycopy здесь.

Также я бы использовал try-with-resources для закрытия сокетов и других вещей, связанных с нарушениями (исключения, тайм-ауты).

0 голосов
/ 12 декабря 2018

Вам нужна охрана или вам нужен AES?Блочный шифр звучит как плохой выбор, поскольку он раздувает ваши данные от 19 до 48 байт.

Принятый ответ дает вам две рекомендации, одна из которых AFAIK - катастрофа безопасности: увеличение счетчика вряд ли поможет Режим CBC .

Другая рекомендация, а именно использование режима счетчика, это AFAIK штраф.Он эффективно превращает блочный шифр в потоковый и позволяет отправлять только 16 + 19 байт.Скорее всего, вы можете использовать менее 16 байтов для счетчика.

Другая неэффективность связана с инициализацией шифрования в цикле.IIRC стоит дороже, чем шифрование ваших двух блоков.

Данные очень маленькие (19 байт), их может быть бесконечно много, и заранее неизвестно, через какие промежутки времени они приходят ко мне.

Тем не менее, вы можете обрабатывать его более эффективно.Прочитайте все байты, которые вы получите одновременно.Когда это всего 19 байтов, тогда зашифруйте и отправьте его.В случае, если это меньше, продолжайте читать.В случае, если это больше, то зашифруйте все это и отправьте.Таким образом, вы можете быть более эффективными ... и даже очень медленный SecureRandom не может быть проблемой, так как вам нужен всего один IV для большого блока (чем больше времени занимает обработка, тем больше данных вы получаете одновременно).

0 голосов
/ 10 декабря 2018

SecureRandom медленный даже в режиме PRNG и может даже блокироваться, когда недостаточно энтропии.

Я рекомендую один случайный источник ИВ и увеличивать его между итерациями, аналогичными режиму CTR.Или просто используйте режим CTR.

public class Test {
    static byte[] plaintext = new byte[] { 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51,
            0x52, 0x53 };

    public static void main(String[] args) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CTR/PKCS5PADDING");
            String s_key = "Random09" + "Random09"; // 16 Byte = 128 Bit Key
            byte[] b_key = s_key.getBytes();
            SecretKeySpec sKeySpec = new SecretKeySpec(b_key, "AES");
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
            byte[] IV = new byte[16]; // initialization vector
            random.nextBytes(IV);
            int num = 10000;
            long start = System.nanoTime();
            for (int i = 0; i < num; ++i) {
                IvParameterSpec ivSpec = new IvParameterSpec(IV);
                cipher.init(Cipher.ENCRYPT_MODE, sKeySpec, ivSpec);
                byte[] msg = new byte[16 + 32];
                System.arraycopy(IV, 0, msg, 0, IV.length);
                byte[] encrypted = cipher.doFinal(plaintext);
                System.arraycopy(encrypted, 0, msg, IV.length, encrypted.length);
                increment(IV);
            }
            long end = System.nanoTime();
            long duration = end - start;
            double drate = ((double) plaintext.length * (double) num) / ((double) duration / 1000000000);
            System.out.println("Verschlüsselung:\n" + num + " mal 19 Bytes in " + ((double) duration / 1000000000) + " s\nData Rate = " + drate
                    / 1000.0 + " kBytes/s");
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
    }

    private static void increment(byte[] iv) {
        for (int i=0; i<4; ++i) {
            if (++iv[i] != 0)
                break;
        }
    }
}

Печать:

Verschlüsselung:
10000 mal 19 Bytes in 0.0331898 s
Data Rate = 5724.650344382912 kBytes/s

Как минимум в 30 раз быстрее на моей машине.

...