/**
   * Perform an NTLMv2 session key check
   *
   * @param md4hash String
   * @param challenge byte[]
   * @param type3Msg Type3NTLMMessage
   * @return boolean
   */
  protected final boolean checkNTLMv2SessionKey(
      String md4hash, byte[] challenge, Type3NTLMMessage type3Msg) {
    if (getLogger().isDebugEnabled()) getLogger().debug(("Perform an NTLMv2 session key check."));
    // Create the value to be encrypted by appending the server challenge and client challenge
    // and applying an MD5 digest
    byte[] nonce = new byte[16];
    System.arraycopy(challenge, 0, nonce, 0, 8);
    System.arraycopy(type3Msg.getLMHash(), 0, nonce, 8, 8);

    MessageDigest md5 = null;
    byte[] v2challenge = new byte[8];

    try {
      //  Create the MD5 digest
      md5 = MessageDigest.getInstance("MD5");

      //  Apply the MD5 digest to the nonce
      md5.update(nonce);
      byte[] md5nonce = md5.digest();

      //  We only want the first 8 bytes
      System.arraycopy(md5nonce, 0, v2challenge, 0, 8);
    } catch (NoSuchAlgorithmException ex) {
      // Log the error
      getLogger().error(ex);
    }

    // Generate the local encrypted password using the MD5 generated challenge
    byte[] p21 = new byte[21];
    byte[] md4byts = m_md4Encoder.decodeHash(md4hash);
    System.arraycopy(md4byts, 0, p21, 0, 16);

    // Generate the local hash of the password
    byte[] localHash = null;

    try {
      localHash = m_encryptor.doNTLM1Encryption(p21, v2challenge);
    } catch (NoSuchAlgorithmException ex) {
      // Log the error
      getLogger().error(ex);
    }

    // Validate the password
    byte[] clientHash = type3Msg.getNTLMHash();

    if (clientHash != null && localHash != null && clientHash.length == localHash.length) {
      int i = 0;

      while (i < clientHash.length && clientHash[i] == localHash[i]) {
        i++;
      }

      if (i == clientHash.length) {
        if (getLogger().isDebugEnabled()) getLogger().debug(("Hashed password check successful."));
        return true;
      }
    }
    if (getLogger().isDebugEnabled()) getLogger().debug(("Password check failed."));
    return false;
  }
  /**
   * Perform an NTLMv1 hashed password check
   *
   * @param md4hash String
   * @param challenge byte[]
   * @param type3Msg Type3NTLMMessage
   * @param checkLMHash boolean
   * @return boolean
   */
  protected final boolean checkNTLMv1(
      String md4hash, byte[] challenge, Type3NTLMMessage type3Msg, boolean checkLMHash) {
    if (getLogger().isDebugEnabled())
      getLogger().debug(("Perform an NTLMv1 hashed password check."));

    // Generate the local encrypted password using the challenge that was sent to the client
    byte[] p21 = new byte[21];
    byte[] md4byts = m_md4Encoder.decodeHash(md4hash);
    System.arraycopy(md4byts, 0, p21, 0, 16);

    // Generate the local hash of the password using the same challenge
    byte[] localHash = null;

    try {
      localHash = m_encryptor.doNTLM1Encryption(p21, challenge);
    } catch (NoSuchAlgorithmException ex) {
    }

    // Validate the password
    byte[] clientHash = checkLMHash ? type3Msg.getLMHash() : type3Msg.getNTLMHash();

    if (clientHash != null && localHash != null && clientHash.length == localHash.length) {
      int i = 0;

      while (i < clientHash.length && clientHash[i] == localHash[i]) {
        i++;
      }

      if (i == clientHash.length) {
        if (getLogger().isDebugEnabled()) getLogger().debug(("Hashed passwords match."));
        return true;
      }
    }

    if (getLogger().isDebugEnabled()) getLogger().debug(("Hashed passwords do not match."));
    return false;
  }
  /**
   * Perform an NTLMv2 check
   *
   * @param md4hash String
   * @param challenge byte[]
   * @param type3Msg Type3NTLMMessage
   * @return boolean
   */
  protected final boolean checkNTLMv2(String md4hash, byte[] challenge, Type3NTLMMessage type3Msg) {
    if (getLogger().isDebugEnabled()) getLogger().debug(("Perform an NTLMv2 check."));
    boolean ntlmv2OK = false;
    boolean lmv2OK = false;

    try {
      // Generate the v2 hash using the challenge that was sent to the client
      byte[] v2hash =
          m_encryptor.doNTLM2Encryption(
              m_md4Encoder.decodeHash(md4hash), type3Msg.getUserName(), type3Msg.getDomain());

      // Get the NTLMv2 blob sent by the client and the challenge that was sent by the server
      NTLMv2Blob v2blob = new NTLMv2Blob(type3Msg.getNTLMHash());

      // Calculate the HMAC of the received blob and compare
      byte[] srvHmac = v2blob.calculateHMAC(challenge, v2hash);
      byte[] clientHmac = v2blob.getHMAC();

      if (clientHmac != null && srvHmac != null && clientHmac.length == srvHmac.length) {
        int i = 0;

        while (i < clientHmac.length && clientHmac[i] == srvHmac[i]) {
          i++;
        }

        if (i == clientHmac.length) {
          if (getLogger().isDebugEnabled())
            getLogger().debug(("HMAC matches the client, user authenticated."));
          ntlmv2OK = true;
        }
      }

      // NTLMv2 check failed, try the LMv2 value

      if (ntlmv2OK == false) {
        byte[] lmv2 = type3Msg.getLMHash();
        byte[] clChallenge = v2blob.getClientChallenge();

        if (lmv2 != null && lmv2.length == 24 && clChallenge != null && clChallenge.length == 8) {
          // Check that the LM hash contains the client challenge as the last 8 bytes

          int i = 0;

          while (i < clChallenge.length && lmv2[i + 16] == clChallenge[i]) i++;

          if (i == clChallenge.length) {
            // Calculate the LMv2 value

            byte[] lmv2Hmac = v2blob.calculateLMv2HMAC(v2hash, challenge, clChallenge);

            // Check if the LMv2 HMAC matches

            i = 0;

            while (i < lmv2Hmac.length && lmv2[i] == lmv2Hmac[i]) i++;

            if (i == lmv2Hmac.length) {
              if (getLogger().isDebugEnabled())
                getLogger().debug(("LMv2 HMAC matches the client, user authenticated."));

              // return true;
              lmv2OK = true;
            }
          }
        }
      }
    } catch (Exception ex) {
      if (getLogger().isDebugEnabled()) getLogger().debug(ex);
    }

    // Check if either of the NTLMv2 checks passed

    if (ntlmv2OK || lmv2OK) return true;
    return false;
  }