/**
   * initialise the cipher.
   *
   * @param forEncryption if true the cipher is initialised for encryption, if false for decryption.
   * @param params the key and other data required by the cipher.
   * @exception IllegalArgumentException if the params argument is inappropriate.
   */
  public void init(boolean forEncryption, CipherParameters params) throws IllegalArgumentException {
    this.forEncryption = forEncryption;

    reset();

    if (params instanceof ParametersWithRandom) {
      ParametersWithRandom p = (ParametersWithRandom) params;

      padding.init(p.getRandom());

      cipher.init(forEncryption, p.getParameters());
    } else {
      padding.init(null);

      cipher.init(forEncryption, params);
    }
  }
  /**
   * Process the last block in the buffer. If the buffer is currently full and padding needs to be
   * added a call to doFinal will produce 2 * getBlockSize() bytes.
   *
   * @param out the array the block currently being held is copied into.
   * @param outOff the offset at which the copying starts.
   * @return the number of output bytes copied to out.
   * @exception DataLengthException if there is insufficient space in out for the output or we are
   *     decrypting and the input is not block size aligned.
   * @exception IllegalStateException if the underlying cipher is not initialised.
   * @exception InvalidCipherTextException if padding is expected and not found.
   */
  public int doFinal(byte[] out, int outOff)
      throws DataLengthException, IllegalStateException, InvalidCipherTextException {
    int blockSize = cipher.getBlockSize();
    int resultLen = 0;

    if (forEncryption) {
      if (bufOff == blockSize) {
        if ((outOff + 2 * blockSize) > out.length) {
          reset();

          throw new OutputLengthException("output buffer too short");
        }

        resultLen = cipher.processBlock(buf, 0, out, outOff);
        bufOff = 0;
      }

      padding.addPadding(buf, bufOff);

      resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen);

      reset();
    } else {
      if (bufOff == blockSize) {
        resultLen = cipher.processBlock(buf, 0, buf, 0);
        bufOff = 0;
      } else {
        reset();

        throw new DataLengthException("last block incomplete in decryption");
      }

      try {
        resultLen -= padding.padCount(buf);

        System.arraycopy(buf, 0, out, outOff, resultLen);
      } finally {
        reset();
      }
    }

    return resultLen;
  }