/**
   * Check the length of an RSA key modulus/exponent to make sure it is not too short or long. Some
   * impls have their own min and max key sizes that may or may not match with a system defined
   * value.
   *
   * @param modulusLen the bit length of the RSA modulus.
   * @param exponent the RSA exponent
   * @param minModulusLen if > 0, check to see if modulusLen is at least this long, otherwise
   *     unused.
   * @param maxModulusLen caller will allow this max number of bits. Allow the smaller of the
   *     system-defined maximum and this param.
   * @throws InvalidKeyException if any of the values are unacceptable.
   */
  public static void checkKeyLengths(
      int modulusLen, BigInteger exponent, int minModulusLen, int maxModulusLen)
      throws InvalidKeyException {

    if ((minModulusLen > 0) && (modulusLen < (minModulusLen))) {
      throw new InvalidKeyException("RSA keys must be at least " + minModulusLen + " bits long");
    }

    // Even though our policy file may allow this, we don't want
    // either value (mod/exp) to be too big.

    int maxLen = Math.min(maxModulusLen, MAX_MODLEN);

    // If a RSAPrivateKey/RSAPublicKey, make sure the
    // modulus len isn't too big.
    if (modulusLen > maxLen) {
      throw new InvalidKeyException("RSA keys must be no longer than " + maxLen + " bits");
    }

    // If a RSAPublicKey, make sure the exponent isn't too big.
    if (restrictExpLen
        && (exponent != null)
        && (modulusLen > MAX_MODLEN_RESTRICT_EXP)
        && (exponent.bitLength() > MAX_RESTRICTED_EXPLEN)) {
      throw new InvalidKeyException(
          "RSA exponents can be no longer than "
              + MAX_RESTRICTED_EXPLEN
              + " bits "
              + " if modulus is greater than "
              + MAX_MODLEN_RESTRICT_EXP
              + " bits");
    }
  }
  // Uses supplied hash algorithm
  static byte[] derive(
      char[] chars, byte[] salt, int ic, int n, int type, String hashAlgo, int blockLength) {

    // Add in trailing NULL terminator.  Special case:
    // no terminator if password is "\0".
    int length = chars.length * 2;
    if (length == 2 && chars[0] == 0) {
      chars = new char[0];
      length = 0;
    } else {
      length += 2;
    }

    byte[] passwd = new byte[length];
    for (int i = 0, j = 0; i < chars.length; i++, j += 2) {
      passwd[j] = (byte) ((chars[i] >>> 8) & 0xFF);
      passwd[j + 1] = (byte) (chars[i] & 0xFF);
    }
    byte[] key = new byte[n];

    try {
      MessageDigest sha = MessageDigest.getInstance(hashAlgo);

      int v = blockLength;
      int u = sha.getDigestLength();
      int c = roundup(n, u) / u;
      byte[] D = new byte[v];
      int s = roundup(salt.length, v);
      int p = roundup(passwd.length, v);
      byte[] I = new byte[s + p];

      Arrays.fill(D, (byte) type);
      concat(salt, I, 0, s);
      concat(passwd, I, s, p);

      byte[] Ai;
      byte[] B = new byte[v];
      byte[] tmp = new byte[v];

      int i = 0;
      for (; ; i++, n -= u) {
        sha.update(D);
        sha.update(I);
        Ai = sha.digest();
        for (int r = 1; r < ic; r++) Ai = sha.digest(Ai);
        System.arraycopy(Ai, 0, key, u * i, Math.min(n, u));
        if (i + 1 == c) break;
        concat(Ai, B, 0, B.length);
        BigInteger B1;
        B1 = new BigInteger(1, B).add(BigInteger.ONE);

        for (int j = 0; j < I.length; j += v) {
          BigInteger Ij;
          int trunc;

          if (tmp.length != v) tmp = new byte[v];
          System.arraycopy(I, j, tmp, 0, v);
          Ij = new BigInteger(1, tmp);
          Ij = Ij.add(B1);
          tmp = Ij.toByteArray();
          trunc = tmp.length - v;
          if (trunc >= 0) {
            System.arraycopy(tmp, trunc, I, j, v);
          } else if (trunc < 0) {
            Arrays.fill(I, j, j + (-trunc), (byte) 0);
            System.arraycopy(tmp, 0, I, j + (-trunc), tmp.length);
          }
        }
      }
    } catch (Exception e) {
      throw new RuntimeException("internal error: " + e);
    }
    return key;
  }