/**
  * Takes ciphertexts and decrypts it
  *
  * @param data correctly padded data
  * @return
  * @throws CryptoException
  */
 public byte[] decrypt(byte[] data) throws CryptoException {
   try {
     byte[] plaintext;
     if (useExplicitIv) {
       decryptIv = new IvParameterSpec(Arrays.copyOf(data, decryptCipher.getBlockSize()));
     }
     if (tlsContext.getMyConnectionEnd() == ConnectionEnd.CLIENT) {
       decryptCipher.init(
           Cipher.DECRYPT_MODE,
           new SecretKeySpec(serverWriteKey, bulkCipherAlg.getJavaName()),
           decryptIv);
     } else {
       decryptCipher.init(
           Cipher.DECRYPT_MODE,
           new SecretKeySpec(clientWriteKey, bulkCipherAlg.getJavaName()),
           decryptIv);
     }
     if (useExplicitIv) {
       plaintext =
           decryptCipher.doFinal(
               Arrays.copyOfRange(data, decryptCipher.getBlockSize(), data.length));
     } else {
       plaintext = decryptCipher.doFinal(data);
       decryptIv =
           new IvParameterSpec(
               Arrays.copyOfRange(data, data.length - decryptCipher.getBlockSize(), data.length));
     }
     return plaintext;
   } catch (BadPaddingException
       | IllegalBlockSizeException
       | InvalidAlgorithmParameterException
       | InvalidKeyException
       | UnsupportedOperationException ex) {
     throw new CryptoException(ex);
   }
 }
  public TlsRecordBlockCipher(TlsContext tlsContext)
      throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
          InvalidAlgorithmParameterException {
    this.tlsContext = tlsContext;
    ProtocolVersion protocolVersion = tlsContext.getProtocolVersion();
    CipherSuite cipherSuite = tlsContext.getSelectedCipherSuite();
    if (protocolVersion == ProtocolVersion.TLS11
        || protocolVersion == ProtocolVersion.TLS12
        || protocolVersion == ProtocolVersion.DTLS10
        || protocolVersion == ProtocolVersion.DTLS12) {
      useExplicitIv = true;
    }
    bulkCipherAlg = BulkCipherAlgorithm.getBulkCipherAlgorithm(cipherSuite);
    CipherAlgorithm cipherAlg = AlgorithmResolver.getCipher(cipherSuite);
    int keySize = cipherAlg.getKeySize();
    encryptCipher = Cipher.getInstance(cipherAlg.getJavaName());
    decryptCipher = Cipher.getInstance(cipherAlg.getJavaName());

    MacAlgorithm macAlg = AlgorithmResolver.getMacAlgorithm(cipherSuite);
    readMac = Mac.getInstance(macAlg.getJavaName());
    writeMac = Mac.getInstance(macAlg.getJavaName());

    int secretSetSize = (2 * keySize) + readMac.getMacLength() + writeMac.getMacLength();

    if (!useExplicitIv) {
      secretSetSize += encryptCipher.getBlockSize() + decryptCipher.getBlockSize();
    }

    byte[] masterSecret = tlsContext.getMasterSecret();
    byte[] seed = tlsContext.getServerClientRandom();

    PRFAlgorithm prfAlgorithm =
        AlgorithmResolver.getPRFAlgorithm(
            tlsContext.getProtocolVersion(), tlsContext.getSelectedCipherSuite());
    byte[] keyBlock =
        PseudoRandomFunction.compute(
            prfAlgorithm,
            masterSecret,
            PseudoRandomFunction.KEY_EXPANSION_LABEL,
            seed,
            secretSetSize);

    LOGGER.debug("A new key block was generated: {}", ArrayConverter.bytesToHexString(keyBlock));

    int offset = 0;
    byte[] clientMacWriteSecret =
        Arrays.copyOfRange(keyBlock, offset, offset + readMac.getMacLength());
    offset += readMac.getMacLength();
    LOGGER.debug(
        "Client MAC write Secret: {}", ArrayConverter.bytesToHexString(clientMacWriteSecret));

    byte[] serverMacWriteSecret =
        Arrays.copyOfRange(keyBlock, offset, offset + writeMac.getMacLength());
    offset += writeMac.getMacLength();
    LOGGER.debug(
        "Server MAC write Secret:  {}", ArrayConverter.bytesToHexString(serverMacWriteSecret));

    clientWriteKey = Arrays.copyOfRange(keyBlock, offset, offset + keySize);
    offset += keySize;
    LOGGER.debug("Client write key: {}", ArrayConverter.bytesToHexString(clientWriteKey));

    serverWriteKey = Arrays.copyOfRange(keyBlock, offset, offset + keySize);
    offset += keySize;
    LOGGER.debug("Server write key: {}", ArrayConverter.bytesToHexString(serverWriteKey));

    byte[] clientWriteIv, serverWriteIv;
    if (useExplicitIv) {
      clientWriteIv = new byte[encryptCipher.getBlockSize()];
      RandomHelper.getRandom().nextBytes(clientWriteIv);
      serverWriteIv = new byte[decryptCipher.getBlockSize()];
      RandomHelper.getRandom().nextBytes(serverWriteIv);
    } else {
      clientWriteIv = Arrays.copyOfRange(keyBlock, offset, offset + encryptCipher.getBlockSize());
      offset += encryptCipher.getBlockSize();
      LOGGER.debug("Client write IV: {}", ArrayConverter.bytesToHexString(clientWriteIv));
      serverWriteIv = Arrays.copyOfRange(keyBlock, offset, offset + decryptCipher.getBlockSize());
      offset += decryptCipher.getBlockSize();
      LOGGER.debug("Server write IV: {}", ArrayConverter.bytesToHexString(serverWriteIv));
    }

    if (tlsContext.getMyConnectionEnd() == ConnectionEnd.CLIENT) {
      encryptIv = new IvParameterSpec(clientWriteIv);
      decryptIv = new IvParameterSpec(serverWriteIv);
      encryptKey = new SecretKeySpec(clientWriteKey, bulkCipherAlg.getJavaName());
      decryptKey = new SecretKeySpec(serverWriteKey, bulkCipherAlg.getJavaName());
      encryptCipher.init(Cipher.ENCRYPT_MODE, encryptKey, encryptIv);
      decryptCipher.init(Cipher.DECRYPT_MODE, decryptKey, decryptIv);
      readMac.init(new SecretKeySpec(serverMacWriteSecret, macAlg.getJavaName()));
      writeMac.init(new SecretKeySpec(clientMacWriteSecret, macAlg.getJavaName()));
    } else {
      decryptIv = new IvParameterSpec(clientWriteIv);
      encryptIv = new IvParameterSpec(serverWriteIv);
      // todo check if this correct???
      encryptCipher.init(
          Cipher.ENCRYPT_MODE,
          new SecretKeySpec(serverWriteKey, bulkCipherAlg.getJavaName()),
          encryptIv);
      decryptCipher.init(
          Cipher.DECRYPT_MODE,
          new SecretKeySpec(clientWriteKey, bulkCipherAlg.getJavaName()),
          decryptIv);
      readMac.init(new SecretKeySpec(clientMacWriteSecret, macAlg.getJavaName()));
      writeMac.init(new SecretKeySpec(serverMacWriteSecret, macAlg.getJavaName()));
    }

    if (offset != keyBlock.length) {
      throw new CryptoException("Offset exceeded the generated key block length");
    }

    // mac has to be put into one or more blocks, depending on the MAC/block
    // length
    // additionally, there is a need for one explicit IV block
    minimalEncryptedRecordLength =
        ((readMac.getMacLength() / decryptCipher.getBlockSize()) + 2)
            * decryptCipher.getBlockSize();
  }