在多线程Java应用程序中,我们使用AES-256对磁盘进行文件加密和解密。请注意,多个线程可以同时调用不同文件的加密和解密方法。
加密:
Cipher encrypter = Cipher.getInstance(algorithm, new BouncyCastleProvider());
IvParameterSpec ivSpec = getIvParamSpec(encrypter.getBlockSize());
encrypter.init(Cipher.ENCRYPT_MODE, key, ivSpec);
//..encrypt the data
解密:
Cipher decrypter = Cipher.getInstance(algorithm, new BouncyCastleProvider());
IvParameterSpec ivSpec = readIvParamSpec(decrypter.getBlockSize(), is);
decrypter.init(Cipher.DECRYPT_MODE, key, ivSpec);
//.. decrypt the data
在我们的理解中,最好使用随机IV进行加密而不是静态/固定IV。为此,我们使用SecureRandom API生成IV。随机IV在开始时保存在加密文件中。
SecureRandom random = new SecureRandom();
byte[] iv = new byte[ivSizeBytes];
random.nextBytes(iv);
new IvParameterSpec(iv);
我的问题是,由于我每次加密使用随机IV,我是否需要为所有调用调用Cipher.getInstance()和Cipher.Init()?为了提高性能,这些只能在类初始化期间调用一次,然后重用单个密码实例来加密和解密数据吗?
提前致谢!
答案 0 :(得分:5)
是的,您需要为每个线程使用不同的Cipher
实例,因为它们是有状态的。如果你不这样做,那么线程就可以打破其他线程的密文。
假设我们有两个线程t1
和t2
,它们要加密两个明文p1_1 | p1_2
和p2_1 | p2_1
(在块边界上拆分)。我们以CBC为例:
time........................................................................
root 1. init with IV
t1 2. E(p1_1 XOR IV) = c1_1 4. E(p1_2 XOR c2_1) = c1_2
t2 3. E(p2_1 XOR c1_1) = c2_1 5. E(p2_2 XOR c1_2) = c2_2
c1_1
没问题,但c2_1
不正常,因为来自t1
的状态用于启动加密。好像加密是用c1_1
作为IV初始化的。
此示例仅适用于CBC模式,但其他操作模式类似。如果我们假设加密只发生 块,那么你可以以线程安全的方式使用ECB模式,但这只是一种幻觉,因为你不能确定实现只处理内部状态是块状的而不是按字节的。
答案 1 :(得分:4)
我的问题是,由于我每次加密使用随机IV,我是否需要为所有呼叫调用Cipher.getInstance()和Cipher.Init()?
只要您不在线程之间共享密码实例,就可以重复使用它们。正如Artjom所提到的,Cipher实例是有状态的。它们既存储IV,也存储最后一个密文(用作CBC模式加密的下一个矢量),也可能存储一些缓冲的明文。将该状态与来自不同线程的输入混合将导致混乱。
由于每个文件加密需要一个新的随机数,因此您需要在调用init
方法后再次调用doFinal
。 Init并不是那么重量级;可以采取一些性能的一件事是AES的子密钥推导,但通常这不是一个大问题。
请注意,虽然执行加密和解密可能是相对较重的操作,但实例本身包含的状态非常少,getInstance()
和init
是相对轻量级的操作。因此,创建一些Cipher
个实例 - 可能使用相同的密钥 - 对于多个线程来说是好的。
重新创建BouncyCastleProvider
多次 是一个非常糟糕的主意,即使它可能会在下面使用某种单例。但基本上你不需要仅Java Bouncy Castle实现。 Oracle可以使用AES-NI内在函数,它将直接在兼容的处理器上使用AES-NI指令集。这将在Bouncy Castle周围运行 - 期望加速大约7到13倍(!)。