Extremely slow built-in AES encryption with Java

眉间皱痕 提交于 2019-12-05 18:24:13

SecureRandom is slow even in PRNG mode and can even block when not enough entropy is available.

I recommend sourcing the random IV once and incrementing it between iterations similar to CTR mode. Or just use CTR mode.

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

Prints:

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

At least 30 times faster on my machine.

Indeed SecureRandom as used is notorious slow, and even blocking. It is the bottle neck here. So encrypt a larger buffer, several messages, when feasible.

Otherwise there are still some minor things to consider:

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

There are better ways to deal with byte arrays using an overloaded doFinal. Then the code is cleaned up, removes arraycopy here.

Also I would use try-with-resources for closing sockets and other things on irregularities (exceptions, timeouts).

Do you need security or do you need AES? A block cipher sounds like a bad choice as it inflates you data by from 19 to 48 bytes.

The accepted answer gives you two recommendations, where one is AFAIK a security disaster: Incrementing the counter does hardly anything useful in CBC mode.

The other recommendation, namely using the counter mode is AFAIK fine. It effectively turns a block cipher into a stream cipher and allows you to send only 16+19 bytes. Most probably, you can use fewer than 16 bytes for the counter.

Another inefficiency comes from the cipher initialization in a loop. IIRC it costs more than the encryption of your two blocks.

The data is very small (19 bytes), it can be infinitely many and it is unknown in advance, in which intervals they arrive at me.

Nonetheless, you can process it more efficiently. Read all bytes you get at once. When it's just 19 bytes, then encrypt and send it. In case, it's less, continue reading. In case, it's more, then encrypt it all and send. This way you can be more efficient... and even a dead slow SecureRandom can't be a problem as you need just one IV for a large block (the longer your processing took, the more data you get at once).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!