private IMac getMac(char[] password) throws MalformedKeyringException {
    if (!properties.containsKey("salt")) {
      throw new MalformedKeyringException("no salt");
    }
    byte[] salt = Util.toBytesFromString(properties.get("salt"));
    IMac mac = MacFactory.getInstance(properties.get("mac"));
    if (mac == null) {
      throw new MalformedKeyringException("no such mac: " + properties.get("mac"));
    }
    int keylen = mac.macSize();
    int maclen = 0;
    if (!properties.containsKey("maclen")) {
      throw new MalformedKeyringException("no MAC length");
    }
    try {
      maclen = Integer.parseInt(properties.get("maclen"));
    } catch (NumberFormatException nfe) {
      throw new MalformedKeyringException("bad MAC length");
    }

    HashMap pbAttr = new HashMap();
    pbAttr.put(IPBE.PASSWORD, password);
    pbAttr.put(IPBE.SALT, salt);
    pbAttr.put(IPBE.ITERATION_COUNT, ITERATION_COUNT);
    IRandom kdf = PRNGFactory.getInstance("PBKDF2-HMAC-SHA");
    kdf.init(pbAttr);

    byte[] dk = new byte[keylen];
    try {
      kdf.nextBytes(dk, 0, keylen);
    } catch (LimitReachedException shouldNotHappen) {
      throw new Error(shouldNotHappen.toString());
    }

    HashMap macAttr = new HashMap();
    macAttr.put(IMac.MAC_KEY_MATERIAL, dk);
    macAttr.put(IMac.TRUNCATED_SIZE, new Integer(maclen));
    try {
      mac.init(macAttr);
    } catch (InvalidKeyException shouldNotHappen) {
      throw new Error(shouldNotHappen.toString());
    }
    return mac;
  }
  /**
   * Initialise this instance with the designated set of attributes.
   *
   * <p>The possible attributes for a <code>UST</code> are:
   *
   * <ul>
   *   <li>{@link #CONFIDENTIALITY}: a {@link java.lang.Boolean} that indicates if Confidentiality
   *       Protection service is to be activated for messages processed with this instance.
   *   <li>{@link #INTEGRITY}: a {@link java.lang.Boolean} that indicates if Integrity Protection
   *       service is to be activated for messages processed with this instance.
   *   <li>{@link #KEYSTREAM}: a {@link java.lang.String} that indicates the algorithm name of the
   *       underlying keystream generators used with this instance. Currently the only allowed
   *       values are: {@link Registry#UMAC_PRNG} and {@link Registry#ICM_PRNG}.
   *   <li>{@link #INDEX_LENGTH}: a {@link java.lang.Integer} that is only needed if the {@link
   *       Registry#ICM_PRNG} is chosen as the keystream generator algorithm. This value is the
   *       count in bytes of the segment index portion of an <code>ICMGenerator</code>.
   *   <li>{@link #CIPHER}: a {@link java.lang.String} that indicates the algorithm name of the
   *       underlying symmetric key block cipher to use with the designated keystream generators. If
   *       this value is undefined, then the default cipher algorithm for the selected keystream
   *       generator algorithm shall be used (which is {@link Registry#RIJNDAEL_CIPHER} for both
   *       keystream generator algorithms).
   *   <li>{@link IBlockCipher#CIPHER_BLOCK_SIZE}: a {@link java.lang.Integer} that indicates the
   *       block-size to use with the designated symmetric key block cipher algorithm. If this value
   *       is undefined, then the default block size for the chosen cipher is used.
   *   <li>{@link #TAG_LENGTH}: a {@link java.lang.Integer} that indicates the length of the
   *       resulting authentication tag, if/when the Integrity Protection service is activated.
   *   <li>{@link #KEY_MATERIAL}: a byte array containing the user-supplied key material needed to
   *       seed the internal keystream generator.
   * </ul>
   *
   * @param attributes the map of attributes to use for this instance.
   */
  public void init(Map attributes) {
    synchronized (lock) {
      String keystreamName = (String) attributes.get(KEYSTREAM);
      if (keystreamName == null) {
        throw new IllegalArgumentException(KEYSTREAM);
      }

      keystream = PRNGFactory.getInstance(keystreamName);
      kAttributes.clear(); // prepare for new values
      cpAttributes.clear();
      ipAttributes.clear();

      // find out which cipher algorithm to use
      String underlyingCipher = (String) attributes.get(CIPHER);
      if (underlyingCipher != null) {
        cpAttributes.put(ICMGenerator.CIPHER, underlyingCipher);
        ipAttributes.put(ICMGenerator.CIPHER, underlyingCipher);
        if (keystream instanceof ICMGenerator) {
          kAttributes.put(ICMGenerator.CIPHER, underlyingCipher);
        } else if (keystream instanceof UMacGenerator) {
          kAttributes.put(UMacGenerator.CIPHER, underlyingCipher);
        } else {
          throw new IllegalArgumentException(KEYSTREAM);
        }
      }

      // did she specify which block size to use it in?
      Integer blockSize = (Integer) attributes.get(IBlockCipher.CIPHER_BLOCK_SIZE);
      if (blockSize != null) {
        kAttributes.put(IBlockCipher.CIPHER_BLOCK_SIZE, blockSize);
        cpAttributes.put(IBlockCipher.CIPHER_BLOCK_SIZE, blockSize);
        ipAttributes.put(IBlockCipher.CIPHER_BLOCK_SIZE, blockSize);
      }

      // get the key material.
      byte[] key = (byte[]) attributes.get(KEY_MATERIAL);
      if (key == null) {
        throw new IllegalArgumentException(KEY_MATERIAL);
      }

      keysize = key.length;
      if (keystream instanceof ICMGenerator) {
        // for an ICMGenerator-based UST, the key material is effectively
        // twice the underlying cipher's desired/needed key size: half of
        // these bytes is the key material per se for the cipher, and the
        // other half shall be used as the "offset" for the ICMGenerator.

        // ensure length is > 0 and is even
        int limit = key.length;
        if (limit < 2) {
          throw new IllegalArgumentException(KEY_MATERIAL);
        } else if ((limit & 0x01) != 0) {
          throw new IllegalArgumentException(KEY_MATERIAL);
        }
        limit /= 2;
        byte[] cipherKey = new byte[limit];
        byte[] offset = new byte[limit];
        System.arraycopy(key, 0, cipherKey, 0, limit);
        System.arraycopy(key, limit, offset, 0, limit);
        kAttributes.put(IBlockCipher.KEY_MATERIAL, cipherKey);
        kAttributes.put(ICMGenerator.OFFSET, offset);
      } else {
        // if we're here then it's a UMacGenerator and the key is used as is
        kAttributes.put(IBlockCipher.KEY_MATERIAL, key);
      }

      // get the index length. it only makes sense for the ICMGenerator
      // for the UMacGenerator it's always 1
      Integer ndxLength = (Integer) attributes.get(INDEX_LENGTH);
      if (ndxLength != null) {
        if (keystream instanceof ICMGenerator) {
          kAttributes.put(ICMGenerator.SEGMENT_INDEX_LENGTH, ndxLength);
          // max index length is 2 ^ segment-length-in-bits - 1
          maxIndex = BigInteger.valueOf(2L).pow(8 * ndxLength.intValue()).subtract(BigInteger.ONE);
        } else if (ndxLength.intValue() != 1) {
          throw new IllegalArgumentException(INDEX_LENGTH);
        } else {
          maxIndex = BigInteger.valueOf(255L);
        }
      } else if (keystream instanceof ICMGenerator) { // we need this value
        throw new IllegalArgumentException(INDEX_LENGTH);
      } else {
        maxIndex = BigInteger.valueOf(255L);
      }

      // the keystream with index 0 is our source for keying material
      // in this implementation we shall use index 0 to compute the key
      // material for the hash function
      if (keystream instanceof ICMGenerator) {
        kAttributes.put(ICMGenerator.SEGMENT_INDEX, BigInteger.ZERO);
      } else {
        kAttributes.put(UMacGenerator.INDEX, new Integer(0));
      }

      // we have everything we need. init the internal keystream generator
      keystream.init(kAttributes);

      index = BigInteger.valueOf(-1L);

      // find out what security services to provide
      Boolean confidentiality = (Boolean) attributes.get(CONFIDENTIALITY);
      if (confidentiality == null) { // by default we dont provide it
        wantConfidentiality = false;
      } else {
        wantConfidentiality = confidentiality.booleanValue();
      }

      if (wantConfidentiality) {
        cpStream = PRNGFactory.getInstance(keystreamName);
      }

      Boolean integrity = (Boolean) attributes.get(INTEGRITY);
      if (integrity == null) { // by default we do provide it
        wantIntegrity = true;
      } else {
        wantIntegrity = integrity.booleanValue();
      }

      if (wantIntegrity) {
        // make sure we have the other generator to provide our prefix
        if (cpStream == null) {
          cpStream = PRNGFactory.getInstance(keystreamName);
        }

        ipStream = PRNGFactory.getInstance(keystreamName);
        // only when integrity protection service is desired do we look for
        // a tag length property
        Integer tagLength = (Integer) attributes.get(TAG_LENGTH);
        if (tagLength == null) {
          throw new IllegalArgumentException(TAG_LENGTH);
        }

        macAttributes.put(TMMH16.TAG_LENGTH, tagLength);
        macLength = tagLength.intValue();
      }

      ready = false;
    }
  }