@Override
  public byte[] decryptDeterministically(final byte[] ciphertext, final byte[] associatedData)
      throws GeneralSecurityException {
    if (ciphertext.length < AesUtil.BLOCK_SIZE + outputPrefix.length) {
      throw new GeneralSecurityException("Ciphertext too short.");
    }
    if (!isPrefix(outputPrefix, ciphertext)) {
      throw new GeneralSecurityException("Decryption failed (OutputPrefix mismatch).");
    }

    Cipher aesCtr = EngineFactory.CIPHER.getInstance("AES/CTR/NoPadding");

    byte[] expectedIv =
        Arrays.copyOfRange(
            ciphertext, outputPrefix.length, AesUtil.BLOCK_SIZE + outputPrefix.length);

    byte[] ivForJavaCrypto = expectedIv.clone();
    ivForJavaCrypto[8] &= (byte) 0x7F; // 63th bit from the right
    ivForJavaCrypto[12] &= (byte) 0x7F; // 31st bit from the right

    aesCtr.init(
        Cipher.DECRYPT_MODE,
        new SecretKeySpec(this.aesCtrKey, "AES"),
        new IvParameterSpec(ivForJavaCrypto));

    byte[] ctrCiphertext =
        Arrays.copyOfRange(ciphertext, AesUtil.BLOCK_SIZE + outputPrefix.length, ciphertext.length);
    byte[] decryptedPt = aesCtr.doFinal(ctrCiphertext);
    if (ctrCiphertext.length == 0 && decryptedPt == null && SubtleUtil.isAndroid()) {
      // On Android KitKat (19) and Lollipop (21), Cipher.doFinal returns a null pointer when the
      // ciphertext is empty, instead of an empty plaintext. Here we attempt to fix this bug. This
      // is safe because if the plaintext is not empty, the next integrity check would reject it.
      decryptedPt = new byte[0];
    }
    byte[] computedIv = s2v(associatedData, decryptedPt);

    if (com.google.crypto.tink.subtle.Bytes.equal(expectedIv, computedIv)) {
      return decryptedPt;
    } else {
      throw new AEADBadTagException("Integrity check failed.");
    }
  }
s2v per https://tools.ietf.org/html/rfc5297

@param s
@return s2v(si)
@throws GeneralSecurityException
java_src/src/main/java/com/google/crypto/tink/subtle/AesSiv.java