简体   繁体   中英

Extremely slow built-in AES encryption with Java

I have a lot of very small data (19 Bytes) that need to be encrypted and sent to a remote server via tcp in encrypted format. I am using the code below to do this.

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

I am wondering why it is extremely slow. I get an output like this:

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

which means I have a data rate of 74 kByte/s of the original (unencrypted) data. The data rate only increases negligibly if I omit sending over TCP (then it's about 100kByte/s). I have read about data rates that are around 20MByte/s or even higher. I have a laptop with Windows 10 and i5 processor. I would be grateful for any help. As I said, I just need to transfer a lot of small data packets (19 Byte) encrypted.

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).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM