简体   繁体   English

使用Java进行极慢的内置AES加密

[英]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. 我有很多非常小的数据(19字节)需要加密并通过tcp以加密格式发送到远程服务器。 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. 这意味着我的原始(未加密)数据的数据速率为74 kByte / s。 The data rate only increases negligibly if I omit sending over TCP (then it's about 100kByte/s). 如果我省略通过TCP发送(那么它大约是100kByte / s),数据速率只会忽略不计。 I have read about data rates that are around 20MByte/s or even higher. 我读过大约20MByte / s甚至更高的数据速率。 I have a laptop with Windows 10 and i5 processor. 我有一台装有Windows 10和i5处理器的笔记本电脑。 I would be grateful for any help. 我将不胜感激任何帮助。 As I said, I just need to transfer a lot of small data packets (19 Byte) encrypted. 正如我所说,我只需要传输大量加密的小数据包(19字节)。

SecureRandom is slow even in PRNG mode and can even block when not enough entropy is available. 即使在PRNG模式下, SecureRandom也很慢,甚至可以在没有足够的熵可用时阻止。

I recommend sourcing the random IV once and incrementing it between iterations similar to CTR mode. 我建议使用随机IV一次并在类似于CTR模式的迭代之间递增它。 Or just use CTR mode. 或者只使用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;
        }
    }
}

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. 我的机器至少快30倍。

Indeed SecureRandom as used is notorious slow, and even blocking. 事实上,使用的SecureRandom是臭名昭着的,甚至阻塞。 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 . 有更好的方法来使用重载的doFinal处理字节数组。 Then the code is cleaned up, removes arraycopy here. 然后清理代码,在这里删除arraycopy

Also I would use try-with-resources for closing sockets and other things on irregularities (exceptions, timeouts). 此外,我会使用try-with-resources关闭套接字和其他违规行为(异常,超时)。

Do you need security or do you need AES? 您需要安全性还是需要AES? A block cipher sounds like a bad choice as it inflates you data by from 19 to 48 bytes. 分组密码听起来不错,因为它会将数据从19到48字节膨胀。

The accepted answer gives you two recommendations, where one is AFAIK a security disaster: Incrementing the counter does hardly anything useful in CBC mode . 接受的答案为您提供了两条建议,其中一条是AFAIK安全灾难:增加计数器在CBC模式下几乎没有任何用处。

The other recommendation, namely using the counter mode is AFAIK fine. 另一个建议,即使用计数器模式是AFAIK罚款。 It effectively turns a block cipher into a stream cipher and allows you to send only 16+19 bytes. 它有效地将块密码转换为流密码,并允许您仅发送16 + 19个字节。 Most probably, you can use fewer than 16 bytes for the counter. 最有可能的是,您可以使用少于16个字节的计数器。

Another inefficiency comes from the cipher initialization in a loop. 另一个低效率来自循环中的密码初始化。 IIRC it costs more than the encryption of your two blocks. IIRC的成本高于两个街区的加密成本。

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. 数据非常小(19个字节),它可以是无限多的,并且事先是未知的,它们到达我的间隔。

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. 当它只有19个字节时,然后加密并发送它。 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). 通过这种方式,您可以提高效率......即使是死SecureRandom慢的SecureRandom也不会成为问题,因为您需要一个大块的IV(处理时间越长,一次获得的数据就越多)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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