title: Reading AES/GCM encoded data in chunks with BouncyCastle in Java
body: <p>I am trying to figure out how to read data that was encoded with AES/GCM/NoPadding. The data that I am working with will be arbitrarily large, and I'm hoping to read it in chunks, but I'm having difficulties figuring out how that would be accomplished. Here is an example of where I'm at right now:</p>
  
  <pre><code>@Test
  public void chunkDecrypt() throws Exception {
      key = MessageDigest.getInstance("MD5").digest("som3C0o7p@s5".getBytes());
      iv = Hex.decode("EECE34808EF2A9ACE8DF72C9C475D751");
      byte[] ciphertext = Hex
              .decode("EF26839493BDA6DA6ABADD575262713171F825F2F477FDBB53029BEADB41928EA5FB46737D7A94D5BE74B6049008443664F0E0D883943D0EFBEA09DB");
  
      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
      cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
  
      byte[] fullDecryptedPlainText = cipher.doFinal(ciphertext);
      assertThat(new String(fullDecryptedPlainText),
              is("The quick brown fox jumps over the lazy dogs"));
  
      byte[] first32 = Arrays.copyOfRange(ciphertext, 0, 32);
      byte[] final28 = Arrays.copyOfRange(ciphertext, 32, 60);
      byte[] decryptedChunk = new byte[32];
  
      int num = cipher.update(first32, 0, 32, decryptedChunk);
      assertThat(num, is(16));
      assertThat(new String(decryptedChunk, 0, 16), is("The quick brown "));
  
      num = cipher.update(first32, 0, 32, decryptedChunk);
      assertThat(num, is(32));
      assertThat(new String(decryptedChunk, 0, 16), is("fox jumps over t"));
  
      num = cipher.update(final28, 0, 24, decryptedChunk);
      assertThat(num, is(44));
      assertThat(new String(decryptedChunk, 0, 12), is("he lazy dogs"));
  }
  </code></pre>
  
  <p>Note that I get past the first assert no problem, so the data can be decoded in a single go. Also, the next two sets of asserts (decoding the first 32 bytes in 16-byte chunks) work "correctly" but I arrived at this formula through trial-and-error. There are a few things about them that I don't understand:</p>
  
  <ul>
  <li><p>Even though I am reading in 16-byte chunks, all of my numbers seem to need to be multiples of 32. If I change to the following code, then the first call to cipher.update() fails with a return value of 0.</p>
  
  <pre><code>byte[] first16 = Arrays.copyOfRange(ciphertext, 0, 16);
  byte[] decryptedChunk = new byte[16];
  
  int num = cipher.update(first16, 0, 16, decryptedChunk);
  </code></pre></li>
  <li><p>If I change back to 32 on the input side, but I work with a 16-byte output buffer, then the first call succeeds and returns the expected data, but the second call to cipher.update() throws ArrayIndexOutOfBoundsException.</p>
  
  <pre><code>byte[] first32 = Arrays.copyOfRange(ciphertext, 0, 32);
  byte[] decryptedChunk = new byte[16];
  
  int num = cipher.update(first32, 0, 32, decryptedChunk);
  num = cipher.update(first32, 0, 32, decryptedChunk);
  </code></pre></li>
  <li><p>So, if I change the code back to my original example (decryptedChunk is sized at 32 bytes) then the third call to cipher.update() returns a value of 16 (meaning what???) and decryptedChunk contains garbage data.</p></li>
  <li><p>I also tried replacing the last call to cipher.update() with a call to cipher.doFinal() instead:</p>
  
  <pre><code>decryptedChunk = cipher.doFinal(final28);
  assertThat(new String(decryptedChunk, 0, 12), is("he lazy dogs"));
  </code></pre></li>
  </ul>
  
  <p>But this fails with a BadPaddingException (mac check in GCM failed).</p>
  
  <p>Any suggestions?</p>
  
  <hr>
  
  <h2>Update with solution</h2>
  
  <p>After playing around some with the suggested code from Ebbe M. Pedersen, I have been able to put together the following solution:</p>
  
  <pre><code>@Test
  public void chunkDecrypt() throws Exception {
      byte[] key = MessageDigest.getInstance("MD5").digest("som3C0o7p@s5".getBytes());
      byte[] iv = Hex.decode("EECE34808EF2A9ACE8DF72C9C475D751");
      byte[] ciphertext = Hex
              .decode("EF26839493BDA6DA6ABADD575262713171F825F2F477FDBB53029BEADB41928EA5FB46737D7A94D5BE74B6049008443664F0E0D883943D0EFBEA09DB");
  
      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
      cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
  
      int chunkSize = 16;
      byte[] inBuffer = new byte[chunkSize];
      int outBufferSize = ((chunkSize + 15) / 16) * 16;
      byte[] outBuffer = new byte[outBufferSize];
  
      for (int i = 0; i &lt; ciphertext.length; i += chunkSize) {
          int thisChunkSize = Math.min(chunkSize, ciphertext.length - i);
          System.arraycopy(ciphertext, i, inBuffer, 0, thisChunkSize);
          int num = cipher.update(inBuffer, 0, thisChunkSize, outBuffer);
          if (num &gt; 0) {
              logger.debug("update #" + ((i / chunkSize) + 1) + " - data &lt;"
                      + new String(outBuffer, 0, num) + "&gt;");
          }
      }
      int num = cipher.doFinal(inBuffer, chunkSize, 0, outBuffer);
      logger.debug("doFinal - data &lt;" + new String(outBuffer, 0, num) + "&gt;");
  }
  </code></pre>
  
  <p>This works properly for any value of <code>chunkSize</code> that I have selected. I have marked that answer as accepted. Thank you all for the help.</p>