【问题标题】:AES/CBC Decryption not working as expectedAES/CBC 解密未按预期工作
【发布时间】:2021-02-18 04:03:56
【问题描述】:

我在 Java 中使用 AES/CBC/PKCS5Padding 发布了一些解密数据。 我正在加密两个值 A 和 B,然后是文件中的数据。加密值按所述顺序写入文件中。 当解密各个片段的字节位置正确(通过调试确认)并且解密函数的输入正确时,没有填充问题。

加密代码:

byte[] iv = {..........};

IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");

cipher.init(Cipher.ENCRYPT_MODE, fileKey, ivParameterSpec);

byte[] encryptedA = cipher.update(A);

byte[] encryptedB = cipher.update(B);

while( true){        
         
    if( blocks > 1 ) {
        encrypted = cipher.update(data);
    }
    else {
        encrypted = cipher.doFinal(data);
    }
    blocks--; 
    //write bytes to file
}
      

加密时,我可以看到 Cipher 中的向量在每次 update() 后按预期更新(最后一个密文是后续更新的向量。例如,encryptedA 是我调用 update( B)

解密代码

Cipher cipherB = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
cipherB.init(Cipher.DECRYPT_MODE, fileKey, ivParameterSpecB);

byte[] decryptedA = cipherB.update(encryptedA);
byte[] decryptedB = cipherB.update(encryptedB);

while( true){

      if(blocks > 1 ) {
            decrypted = cipherB.update(encryptedBytes);
      }
      else {
            decrypted = cipherB.doFinal(encryptedBytes);
      } 
       blocks--; 
       //write bytes to file  
}

此时发生的事情非常奇怪。

第一次调用 cipherB.update(encryptedA) 什么都不做。它返回一个空数组并且不更新密码内的向量。 第二次调用 cipherB.update(encryptedB) 返回我对上一次调用的期望值( cipherB.update(encryptedA) 是原始值:A)和将向量设置为 encryptedA 的值

你能发现我的方法有什么问题吗?使用默认 SunJCE 提供程序时,AES/CBC/PKCS5Padding 是否存在任何已知问题?

更新: 在阅读了一些 cmets 之后,让我补充一些额外的说明

  1. 条件是围绕用于加密有效负载的块。第一个条件很简单,while(true),第二个条件是 if(blockCount > 1)。每个循环都有一个块计数器减少。代码更新

  2. 如果加密/解密中省略了A和B,则文件数据被正确解密

  3. 我尝试解密加密的直接结果 例如:

    cipherB.update(cipher.update(A))
    

但我仍然得到相同的空数组而不是 A

  1. 在通过运行 cipherB.update(encryptedB) 返回 B 后,我不能依赖运行两次更新,出现问题并且文件数据解密受到密码中向量的影响。 我得到的数据是这样的

    (12 个随机字节)Lorem Ipsum 等

【问题讨论】:

  • 如果AB 以及encryptedA 实际上由一个完整的块(16 个字节)组成,那么这确实是一种奇怪的行为,但这应该不是问题。或者至少我没有看到这种行为有问题,因为你总是可以调用下一个update,这样做是一个很好的做法,以便进行防御性编码。底层 JCE 将来可能会发生变化,您应该编写代码,不要做太多假设。
  • @ArtjomB。问题是conditionother condition 是他的while 循环可能取决于被解密的数据。在极端情况下,对 update() 的调用可能根本不返回任何内容(例如,使用 AEAD 密码)并且只有对 doFinal() 的调用返回任何内容。我怀疑 OP 需要不同的设计。
  • @ArtjomB。谢谢,请看我更新的问题。第4点
  • 如果encryptedAencryptedB 是完整块:第一个cipherB.update(encryptedA) 不返回任何内容,因为在解密期间至少一个完整块 保留在缓冲区中。这是由于填充(在 CBC 的上下文中):最后一个块 可能 包含填充(最多为整个块的大小),直到下一个 update 才决定/doFinal。出于同样的原因,后续的cipherB.update(encryptedB) 只返回与encryptedA 相关的明文,encryptedB 保留在缓冲区中等。
  • 正如我所说,您不能依赖 update 调用的特定行为。尝试查看 CipherInputStream 和 CipherOutputStream。由于您正在写入文件,这应该是一个替代 API,您可以从流中读取任意数量的字节。 (我仍然不确定为什么这对您来说是个问题。)

标签: java encryption aes cbc-mode


【解决方案1】:

AES/CBC/PKCS5Padding 模式适用于块,因此更新将只返回“填充”块,而 doFinal 将返回其余部分。 AES 使用 128 位块,因此 update 方法仅返回 16 字节的倍数。还有一个带有填充的最后一个块。所以你的假设 cipherB.update(cipher.update(A)) 在这种情况下是行不通的。

我并没有真正遵循您在if(blocks > 1 ) 条件下试图实现的目标

您可以使用以下代码来处理密码块(简化版):

  byte[] decrypted = null; 
  byte[] buffer = new byte[BUFFER_SIZE];
  InputStream in = ..;
  for (int bytesRead=in.read(buffer); bytesRead>=0; bytesRead=in.read(buffer)) {
    decrypted = cipher.update(buffer, 0, bytesRead);
    // process the chunk
  }
  decrypted = cipher.doFinal();
  // process the chunk

这样,你是否处理单个块都没有关系。

update方法直接返回加密或解密的块时,也有“流密码”或模式,无论输入大小如何,例如AES/CTR模式或Salsa20密码

【讨论】:

    【解决方案2】:

    明文和密文chunk不是一一对应的,需要在一个byte[]中截取完整的输出,自己解包。

    【讨论】:

    • 谢谢,请看我更新的答案。第三点
    猜你喜欢
    • 2017-12-27
    • 2013-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-26
    • 2017-10-11
    • 1970-01-01
    相关资源
    最近更新 更多