/** Save the current content of this cipher. */
 void save() {
   processedSave = processed;
   sizeOfAADSave = sizeOfAAD;
   aadBufferSave = ((aadBuffer == null || aadBuffer.size() == 0) ? null : aadBuffer.toByteArray());
   if (gctrPAndC != null) gctrPAndC.save();
   if (ghashAllToS != null) ghashAllToS.save();
   if (ibuffer != null) {
     ibufferSave = ibuffer.toByteArray();
   }
 }
 /**
  * Resets the cipher object to its original state. This is used when doFinal is called in the
  * Cipher class, so that the cipher can be reused (with its original key and iv).
  */
 void reset() {
   if (aadBuffer == null) {
     aadBuffer = new ByteArrayOutputStream();
   } else {
     aadBuffer.reset();
   }
   if (gctrPAndC != null) gctrPAndC.reset();
   if (ghashAllToS != null) ghashAllToS.reset();
   processed = 0;
   sizeOfAAD = 0;
   if (ibuffer != null) {
     ibuffer.reset();
   }
 }
 int getBufferedLength() {
   if (ibuffer == null) {
     return 0;
   } else {
     return ibuffer.size();
   }
 }
  // Feed the AAD data to GHASH, pad if necessary
  void processAAD() {
    if (aadBuffer != null && aadBuffer.size() > 0) {
      byte[] aad = aadBuffer.toByteArray();
      sizeOfAAD = aad.length;
      aadBuffer = null;

      int lastLen = aad.length % AES_BLOCK_SIZE;
      if (lastLen != 0) {
        ghashAllToS.update(aad, 0, aad.length - lastLen);
        byte[] padded = expandToOneBlock(aad, aad.length - lastLen, lastLen);
        ghashAllToS.update(padded);
      } else {
        ghashAllToS.update(aad);
      }
    }
  }
  /**
   * Initializes the cipher in the specified mode with the given key and iv.
   *
   * @param decrypting flag indicating encryption or decryption
   * @param algorithm the algorithm name
   * @param key the key
   * @param iv the iv
   * @param tagLenBytes the length of tag in bytes
   * @exception InvalidKeyException if the given key is inappropriate for initializing this cipher
   */
  void init(boolean decrypting, String algorithm, byte[] keyValue, byte[] ivValue, int tagLenBytes)
      throws InvalidKeyException {
    if (keyValue == null || ivValue == null) {
      throw new InvalidKeyException("Internal error");
    }

    // always encrypt mode for embedded cipher
    this.embeddedCipher.init(false, algorithm, keyValue);
    this.subkeyH = new byte[AES_BLOCK_SIZE];
    this.embeddedCipher.encryptBlock(new byte[AES_BLOCK_SIZE], 0, this.subkeyH, 0);

    this.iv = ivValue.clone();
    preCounterBlock = getJ0(iv, subkeyH);
    byte[] j0Plus1 = preCounterBlock.clone();
    increment32(j0Plus1);
    gctrPAndC = new GCTR(embeddedCipher, j0Plus1);
    ghashAllToS = new GHASH(subkeyH);

    this.tagLenBytes = tagLenBytes;
    if (aadBuffer == null) {
      aadBuffer = new ByteArrayOutputStream();
    } else {
      aadBuffer.reset();
    }
    processed = 0;
    sizeOfAAD = 0;
    if (decrypting) {
      ibuffer = new ByteArrayOutputStream();
    }
  }
 /** Restores the content of this cipher to the previous saved one. */
 void restore() {
   processed = processedSave;
   sizeOfAAD = sizeOfAADSave;
   if (aadBuffer != null) {
     aadBuffer.reset();
     if (aadBufferSave != null) {
       aadBuffer.write(aadBufferSave, 0, aadBufferSave.length);
     }
   }
   if (gctrPAndC != null) gctrPAndC.restore();
   if (ghashAllToS != null) ghashAllToS.restore();
   if (ibuffer != null) {
     ibuffer.reset();
     ibuffer.write(ibufferSave, 0, ibufferSave.length);
   }
 }
 /**
  * Continues a multi-part update of the Additional Authentication Data (AAD), using a subset of
  * the provided buffer. If this cipher is operating in either GCM or CCM mode, all AAD must be
  * supplied before beginning operations on the ciphertext (via the {@code update} and {@code
  * doFinal} methods).
  *
  * <p>NOTE: Given most modes do not accept AAD, default impl for this method throws
  * IllegalStateException.
  *
  * @param src the buffer containing the AAD
  * @param offset the offset in {@code src} where the AAD input starts
  * @param len the number of AAD bytes
  * @throws IllegalStateException if this cipher is in a wrong state (e.g., has not been
  *     initialized), does not accept AAD, or if operating in either GCM or CCM mode and one of the
  *     {@code update} methods has already been called for the active encryption/decryption
  *     operation
  * @throws UnsupportedOperationException if this method has not been overridden by an
  *     implementation
  * @since 1.8
  */
 void updateAAD(byte[] src, int offset, int len) {
   if (aadBuffer != null) {
     aadBuffer.write(src, offset, len);
   } else {
     // update has already been called
     throw new IllegalStateException("Update has been called; no more AAD data");
   }
 }
  /**
   * Performs decryption operation for the last time.
   *
   * <p>NOTE: For cipher feedback modes which does not perform special handling for the last few
   * blocks, this is essentially the same as <code>encrypt(...)</code>. Given most modes do not do
   * special handling, the default impl for this method is to simply call <code>decrypt(...)</code>.
   *
   * @param in the input buffer with the data to be decrypted
   * @param inOfs the offset in <code>cipher</code>
   * @param len the length of the input data
   * @param out the buffer for the decryption result
   * @param outOfs the offset in <code>plain</code>
   * @return the number of bytes placed into the <code>out</code> buffer
   */
  int decryptFinal(byte[] in, int inOfs, int len, byte[] out, int outOfs)
      throws IllegalBlockSizeException, AEADBadTagException, ShortBufferException {
    if (len < tagLenBytes) {
      throw new AEADBadTagException("Input too short - need tag");
    }
    if (out.length - outOfs < ((ibuffer.size() + len) - tagLenBytes)) {
      throw new ShortBufferException("Output buffer too small");
    }
    processAAD();
    if (len != 0) {
      ibuffer.write(in, inOfs, len);
    }

    // refresh 'in' to all buffered-up bytes
    in = ibuffer.toByteArray();
    inOfs = 0;
    len = in.length;
    ibuffer.reset();

    byte[] tag = new byte[tagLenBytes];
    // get the trailing tag bytes from 'in'
    System.arraycopy(in, len - tagLenBytes, tag, 0, tagLenBytes);
    len -= tagLenBytes;

    if (len > 0) {
      doLastBlock(in, inOfs, len, out, outOfs, false);
    }

    byte[] lengthBlock = getLengthBlock(sizeOfAAD * 8, processed * 8);
    ghashAllToS.update(lengthBlock);

    byte[] s = ghashAllToS.digest();
    byte[] sOut = new byte[s.length];
    GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
    gctrForSToTag.doFinal(s, 0, s.length, sOut, 0);
    for (int i = 0; i < tagLenBytes; i++) {
      if (tag[i] != sOut[i]) {
        throw new AEADBadTagException("Tag mismatch!");
      }
    }
    return len;
  }
  /**
   * Performs decryption operation.
   *
   * <p>The input cipher text <code>in</code>, starting at <code>inOfs</code> and ending at <code>
   * (inOfs + len - 1)</code>, is decrypted. The result is stored in <code>out</code>, starting at
   * <code>outOfs</code>.
   *
   * @param in the buffer with the input data to be decrypted
   * @param inOfs the offset in <code>in</code>
   * @param len the length of the input data
   * @param out the buffer for the result
   * @param outOfs the offset in <code>out</code>
   * @exception ProviderException if <code>len</code> is not a multiple of the block size
   * @return the number of bytes placed into the <code>out</code> buffer
   */
  int decrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
    if ((len % blockSize) != 0) {
      throw new ProviderException("Internal error in input buffering");
    }
    processAAD();

    if (len > 0) {
      // store internally until decryptFinal is called because
      // spec mentioned that only return recovered data after tag
      // is successfully verified
      ibuffer.write(in, inOfs, len);
    }
    return 0;
  }