我有很多非常小的数据(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 kByte / s(未加密) 数据。如果我忽略通过TCP发送,则数据速率只会微不足道地增加 (当时约为100kByte / s)。我已经读过有关数据速率的信息 20MByte / s甚至更高。我有一台装有Windows 10和i5处理器的笔记本电脑。 我将不胜感激。正如我所说,我只需要转移很多 加密的小数据包(19字节)的数量。
答案 0 :(得分:5)
SecureRandom
即使在PRNG模式下也很慢,并且在没有足够的熵可用时甚至会阻塞。
我建议一次随机IV,并在类似于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倍。
答案 1 :(得分:2)
实际上,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来关闭套接字和其他异常情况(异常,超时)。
答案 2 :(得分:0)
您需要安全性还是需要AES?分组密码听起来像是一个错误的选择,因为它会将您的数据从19字节扩大到48字节。
已接受的答案为您提供了两个建议,其中一个是AFAIK安全性灾难:在CBC mode中增加计数器几乎没有任何用处。
另一个建议,即使用计数器模式是AFAIK很好。它有效地将分组密码转换为流密码,并允许您仅发送16 + 19字节。很有可能,您可以使用少于16个字节的计数器。
另一个效率低下的原因是循环中的密码初始化。 IIRC的成本比加密两个区块还要高。
数据非常小(19字节),可以无限多,并且事先未知,它们以什么间隔到达我。
尽管如此,您仍可以更有效地处理它。读取一次获得的所有字节。如果只有19个字节,则进行加密并发送。以防万一,请继续阅读。以防万一,然后将其全部加密并发送。这样一来,您可以提高效率...甚至SecureRandom
速度极慢也不会成为问题,因为您只需要一个IV来处理一个大块(处理时间越长,一次获取的数据就越多)。