mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

david_page
08.46.2007 888bcc238bdc446633ef058d899af173232022ba
Issue 466 partial

CryptoManager encryption

Allow key size different from block size for block ciphers that support it (e.g., Blowfish as in Blowfish/CFB/NoPadding", 128) and block ciphers modes that use no initialization vector (e.g., ECB as in "DES/ECB/PKCS5Padding", 64). Note that this is accomplished without adding an initialization vector parameter to the encryption API. Instead, if the Cipher.getBlockSize is non-zero, the Cipher object is first instantiated with that size initialization vector, and if that fails, initialized without an initialization vector.
2 files modified
655 ■■■■■ changed files
opends/src/server/org/opends/server/types/CryptoManager.java 603 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/types/CryptoManagerTestCase.java 52 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/CryptoManager.java
@@ -87,11 +87,11 @@
  // The secure random number generator used for key generation,
  // initialization vector PRNG seed...
  private final SecureRandom secureRandom = new SecureRandom();
  private static final SecureRandom secureRandom = new SecureRandom();
  // The random number generator used for initialization vector
  // production.
  private final Random pseudoRandom
  private static final Random pseudoRandom
          = new Random(secureRandom.nextLong());
  // The map from encryption key ID to KeyEntry (cache).
@@ -644,294 +644,358 @@
  }
  /**
   * This class corresponds to the encryption key entry in ADS. It is
   * used for the local cache of key entries requested by
   * CryptoManager users.
   * used in the local cache of key entries that have been requested
   * by CryptoManager clients.
   */
  private static class KeyEntry
  {
    /**
     * Construct an instance of KeyEntry using the specified
     * parameters.
     * Retrieve a KeyEntry from the KeyEntry Map based on cipher
     * transformation name and key length. If the parameters are valid
     * but a corresponding entry does not exist in the supplied map,
     * create the entry and add it to the map.
     *
     * @param keyID The unique identifier of this cipher
     *  transformation/key pair.
     * @param transformation The cipher transformation name.
     * @param encryptionKey The cipher key.
     * @param initializationVectorLength The length of the required
     * initialization vector or 0 if none is required.
     * @param map The KeyEntry Map containing the key.
     *
     * @param transformation The cipher transformation.
     *
     * @param keyLengthBits The cipher key length in bits.
     *
     * @return The key entry corresponding to the parameters.
     *
     * @throws CryptoManagerException If there is a problem
     * instantiating a Cipher object in order to validate the supplied
     * parameters when creating a new entry.
     */
    public KeyEntry( byte[] keyID,
                     String transformation,
                     byte[] encryptionKey,
                     int initializationVectorLength){
      this.keyID = new byte[keyID.length];
      System.arraycopy(keyID, 0, this.keyID, 0, keyID.length);
      this.transformation = new String(transformation);
      this.encryptionKey = new byte[encryptionKey.length];
      System.arraycopy(encryptionKey, 0,
                       this.encryptionKey, 0, encryptionKey.length);
      this.initializationVectorLength = initializationVectorLength;
    }
    /**
     * The unique identifier of this cipher transformation/key pair.
     */
    public final byte[] keyID;
    /**
     * The cipher transformation name.
     */
    public final String transformation;
    /**
     * The cipher key.
     */
    public final byte[] encryptionKey;
    /**
     * The initializationVectorLength (may be 0).
     */
    public final int initializationVectorLength;
  }
  /**
   * Retrieves an encryption mode cipher using the specified
   * algorithm.
   *
   * @param  cipherTransformation  The algorithm/mode/padding to use
   *         for the cipher.
   *
   * @param  keyLength  The length in bits of the encryption key this
   *         method will generate. Note the specified key length must
   *         be compatible with the transformation.
   *
   * @param  keyIdentifier  The key identifier this method assigns to
   *         the cipher transformation/key/initializationVectorLength.
   *
   * @return  An encryption mode cipher using the preferred algorithm.
   *
   * @throws  NoSuchAlgorithmException  If the requested algorithm is
   *                                    not supported or is
   *                                    unavailable.
   *
   * @throws  NoSuchPaddingException  If the requested padding
   *                                  mechanism is not supported or is
   *                                  unavailable.
   *
   * @throws  InvalidKeyException  If the provided key is not
   *                               appropriate for use with the
   *                               requested cipher algorithm.
   *
   * @throws  InvalidAlgorithmParameterException
   *               If an internal problem occurs as a result of the
   *               initialization vector used.
   *
   * @throws  CryptoManagerException If there is a problem managing
   *          or generating an encryption key.
   */
  private Cipher getEncryptionCipher(String cipherTransformation,
                                     int keyLength,
                                     byte[] keyIdentifier)
         throws NoSuchAlgorithmException, NoSuchPaddingException,
                InvalidKeyException,
                InvalidAlgorithmParameterException,
                CryptoManagerException
  {
    Validator.ensureNotNull(cipherTransformation, keyIdentifier);
    public static KeyEntry getKeyEntry(
            final Map<ByteArray,KeyEntry> map,
            final String transformation,
            final int keyLengthBits)
            throws CryptoManagerException {
    KeyEntry keyEntry = null;
    for (Map.Entry<ByteArray, KeyEntry> keyEntryRow
            : keyEntryMap.entrySet()) {
      KeyEntry keyEntryIterator = keyEntryRow.getValue();
      if (keyEntryIterator.transformation.equals(cipherTransformation)
              && keyEntryIterator.encryptionKey.length == keyLength) {
        assert Arrays.equals(keyEntryRow.getKey().array(),
                keyEntryIterator.keyID);
        keyEntry = keyEntryIterator;
      // search for existing key satisfying request
      for (Map.Entry<ByteArray, KeyEntry> i: map.entrySet()) {
        KeyEntry entry = i.getValue();
        if (! entry.fIsCompromised
                && entry.fTransformation.equals(transformation)
                && entry.fKey.length * Byte.SIZE == keyLengthBits) {
          assert Arrays.equals(i.getKey().array(), entry.fKeyID);
          keyEntry = entry;
        break;
      }
    }
    final Cipher cipher = Cipher.getInstance(cipherTransformation);
    if (null == keyEntry) {
      // generate a new key
      if (0 != keyLength % Byte.SIZE) {
        if (0 != keyLengthBits % Byte.SIZE) {
        throw new CryptoManagerException(
                // TODO: i18n
                Message.raw("keyLength parameter must be evenly " +
                        "divisible by %d.", Byte.SIZE));
      }
        // TODO: Does ADS monitoring thread keep map updated with keys
        // produced at other sites? Otherwise, search ADS for suitable
        // key.
        // generate a new key
      final byte[] keyID = uuidToBytes(UUID.randomUUID());
      byte[] secretKey = new byte[keyLength/Byte.SIZE];
      secureRandom.nextBytes(secretKey);
      final int ivLength = (0 == cipher.getBlockSize())
                           ? 0 : keyLength;
      keyEntry = new KeyEntry(keyID, cipherTransformation,
                              secretKey, ivLength);
        final byte[] key = new byte[keyLengthBits / Byte.SIZE];
        secureRandom.nextBytes(key);
        keyEntry = new KeyEntry(keyID, transformation, key,
                                /* compute IV length */ -1);
        map.put(new ByteArray(keyID), keyEntry);
        // TODO: publish key to ADS. (mark key "blocked" in map until
        // registered?)
    }
    // E.g., AES/CBC/PKCS5Padding -> AES
    final int separatorIndex = cipherTransformation.indexOf('/');
    final String cipherAlgorithm = (0 < separatorIndex)
            ? cipherTransformation.substring(0, separatorIndex)
            : cipherTransformation;
    if (0 < keyEntry.initializationVectorLength) {
      final byte[] initializationVector
            = new byte[keyEntry.initializationVectorLength/Byte.SIZE];
      pseudoRandom.nextBytes(initializationVector);
      cipher.init(Cipher.ENCRYPT_MODE,
           new SecretKeySpec(keyEntry.encryptionKey, cipherAlgorithm),
           new IvParameterSpec(initializationVector));
      return keyEntry;
    }
    else {
      cipher.init(Cipher.ENCRYPT_MODE,
          new SecretKeySpec(keyEntry.encryptionKey, cipherAlgorithm));
    }
    try {
      System.arraycopy(keyEntry.keyID, 0, keyIdentifier, 0,
                                               keyIdentifier.length);
    }
    catch (Exception ex) {
      throw new CryptoManagerException(
                    // TODO: i18n
                    Message.raw("Error copying out key identifier."),
                    ex);
    }
    keyEntryMap.put(new ByteArray(keyEntry.keyID), keyEntry);
    return cipher;
  }
  /**
   * Retrieves a decryption mode cipher using the algorithm and
   * encryption key specified by the key identifier and the supplied
   * initialization vector.
     * Given a key identifier, return the associated key entry.
   *
   * @param  keyEntry The keyEntry containing the key to be used for
   *         decryption.
     * @param map  The local cache of key entries.
   *
   * @param  initializationVector The initialization vector to be used
   *         for decryption.
     * @param keyID  The key identifier.
   *
   * @return  A decryption mode cipher using the specified parameters.
   *
   * @throws  NoSuchAlgorithmException  If the requested algorithm is
   *                                    not supported or is
   *                                    unavailable.
   *
   * @throws  NoSuchPaddingException  If the requested padding
   *                                  mechanism is not supported or is
   *                                  unavailable.
   *
   * @throws  InvalidKeyException  If the provided key is not
   *                               appropriate for use with the
   *                               requested cipher algorithm.
   *
   * @throws  InvalidAlgorithmParameterException
   *               If an internal problem occurs as a result of the
   *               initialization vector used.
   *
   * @throws  CryptoManagerException If an invalid keyID is supplied.
     * @return  The key entry associated with the key identifier.
   */
  private Cipher getDecryptionCipher(KeyEntry keyEntry,
                                     byte[] initializationVector)
         throws NoSuchAlgorithmException, NoSuchPaddingException,
                InvalidKeyException,
                InvalidAlgorithmParameterException,
                CryptoManagerException
  {
    Validator.ensureNotNull(keyEntry);
    Validator.ensureTrue(0 == keyEntry.initializationVectorLength
                            || null != initializationVector);
    final Cipher cipher = Cipher.getInstance(keyEntry.transformation);
    public static KeyEntry getKeyEntry(Map<ByteArray,KeyEntry> map,
                                       byte[] keyID) {
      return map.get(new ByteArray(keyID));
      /* TODO: Does ADS monitorying thread keep map updated with keys
         produced at other sites? If not, fetch from ADS and update
         map (assuming a legitimate key ID, the key should exist in
         ADS because this routine is called for decryption). */
    }
    /**
     * Construct an instance of KeyEntry using the specified
     * parameters. This constructor may be used for both locally
     * generated (new) keys and keys imported from ADS. The parameters
     * are validated by using them to create and initialize a Cipher
     * object.
     *
     * @param keyID  The unique identifier of this cipher
     *  transformation/key pair.
     *
     * @param transformation  The cipher transformation name.
     *
     * @param key  The cipher key.
     *
     * @param ivLengthBits  The length in bits of a mandatory
     * initialization vector or 0 if none is required. Set this
     * parameter to -1 when generating a new encryption key and this
     * method will attempt to compute the proper value by first using
     * the cipher block size and then, if the cipher block size is
     * non-zero, using 0 (i.e., no initialization vector).
     *
     * @throws  CryptoManagerException If there is a problem
     * instantiating a Cipher object in order to validate the supplied
     * parameters when creating a new entry.
     */
    public KeyEntry( final byte[] keyID, final String transformation,
                     final byte[] key, final int ivLengthBits)
            throws CryptoManagerException {
      // copy arguments
      this.fKeyID = new byte[keyID.length];
      System.arraycopy(keyID, 0, this.fKeyID, 0, keyID.length);
      this.fTransformation = new String(transformation);
      this.fKey = new byte[key.length];
      System.arraycopy(key, 0,
                       this.fKey, 0, key.length);
      this.fIVLengthBits = ivLengthBits;
      // validate the entry.
      getCipher(Cipher.ENCRYPT_MODE, null);
    }
    /**
     * The unique identifier of this cipher transformation/key pair.
     *
     * @return The unique identifier of this cipher transformation/key
     * pair.
     */
    public byte[] getKeyID() {
      return fKeyID;
    }
    /**
     * The cipher transformation name.
     *
     * @return The cipher transformation name.
     */
    public String getTransformation() {
      return fTransformation;
    }
    /**
     * The cipher key.
     *
     * @return The cipher key.
     */
    public byte[] getKey() {
      return fKey;
    }
    /**
     * The initialization vector length in bits: 0 is a stream cipher
     * or a block cipher that does not use an IV (e.g., ECB); or a
     * positive integer, typically the block size of the cipher.
     *
     * @return The initialization vector length.
     */
    public int getIVLength() {
      assert 0 <= fIVLengthBits : "The field was not initialized.";
      return fIVLengthBits;
    }
    /**
     * This method produces and initialized Cipher based on this
     * KeyEntry's state and the method parameters.
     *
     * @param mode  Either Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE.
     *
     * @param initializationVector  For Cipher.DECRYPT_MODE, supply
     * any initialzation vector used in the corresponding encryption
     * cipher. May be null.
     *
     * @return  The initialized cipher object.
     *
     * @throws  CryptoManagerException In case of a problem creating
     * or initializing the requested cipher object. Possible causes
     * include NoSuchAlgorithmException, NoSuchPaddingException,
     * InvalidKeyException, and InvalidAlgorithmParameterException.
     */
    public Cipher getCipher(final int mode,
                            final byte[] initializationVector)
            throws CryptoManagerException {
      Validator.ensureTrue(Cipher.ENCRYPT_MODE == mode
              || Cipher.DECRYPT_MODE == mode);
      Validator.ensureTrue(Cipher.ENCRYPT_MODE != mode
              || null == initializationVector);
      Validator.ensureTrue(-1 != fIVLengthBits
              || Cipher.ENCRYPT_MODE == mode);
      Validator.ensureTrue(null == initializationVector
              || initializationVector.length * Byte.SIZE
                                                    == fIVLengthBits);
      Cipher cipher;
      try {
        cipher = Cipher.getInstance(fTransformation);
      }
      catch (GeneralSecurityException ex) {
        // NoSuchAlgorithmException, NoSuchPaddingException
        throw new CryptoManagerException(
                // TODO: i18n
                Message.raw("Invalid cipher transformation %s.",
                        fTransformation), ex);
      }
      try {
        if (-1 == fIVLengthBits) {
          /* Unknown initialization vector length on encryption. This
             method will first try the cipher block size, then, if
             that is non-zero and rejected, retry without an
             initialization vector. */
          fIVLengthBits = cipher.getBlockSize() * Byte.SIZE;
        }
    // E.g., AES/CBC/PKCS5Padding -> AES
    final int separatorIndex = keyEntry.transformation.indexOf('/');
        final int separatorIndex = fTransformation.indexOf('/');
    final String cipherAlgorithm = (0 < separatorIndex)
            ? keyEntry.transformation.substring(0, separatorIndex)
            : keyEntry.transformation;
                ? fTransformation.substring(0, separatorIndex)
                : fTransformation;
    if (0 < keyEntry.initializationVectorLength) {
      cipher.init(Cipher.DECRYPT_MODE,
           new SecretKeySpec(keyEntry.encryptionKey, cipherAlgorithm),
           new IvParameterSpec(initializationVector));
        if (0 < fIVLengthBits) {
          try {
            byte[] iv;
            if (Cipher.ENCRYPT_MODE == mode
                    && null == initializationVector) {
              iv = new byte[fIVLengthBits / Byte.SIZE];
              pseudoRandom.nextBytes(iv);
    }
    else {
      cipher.init(Cipher.DECRYPT_MODE,
          new SecretKeySpec(keyEntry.encryptionKey, cipherAlgorithm));
              iv = initializationVector;
            }
            cipher.init(mode,
                   new SecretKeySpec(fKey, cipherAlgorithm),
                   new IvParameterSpec(iv));
          }
          catch (InvalidAlgorithmParameterException ex) {
            if (Cipher.ENCRYPT_MODE == mode) {
              /* Some block cipher modes (e.g., ECB) and all stream
                 ciphers do not use an initialization vector. Set
                 length to 0 and retry below */
              fIVLengthBits = 0;
            }
            else {
              throw ex;
            }
          }
        }
        if (0 == fIVLengthBits) {
          cipher.init(mode, new SecretKeySpec(fKey, cipherAlgorithm));
        }
      }
      catch (GeneralSecurityException ex) {
        // InvalidKeyException, InvalidAlgorithmParameterException
        throw new CryptoManagerException(
                // TODO: i18n
                Message.raw("Error initializing cipher."), ex);
    }
    return cipher;
  }
    /**
     * Mark a key entry as compromised. The entry will no longer be
     * eligible for use as an encryption key.
     */
    public void setIsCompromised() {
      // TODO: called from ADS monitoring thread. Lock entry?
      fIsCompromised = true;
    }
    // state
    private final byte[] fKeyID;
    private final String fTransformation;
    private final byte[] fKey;
    private int fIVLengthBits;
    private boolean fIsCompromised = false;
  }
  /**
   * Encrypts the data in the provided byte array using the preferred
   * cipher transformation.
   *
   * @param  data  The data to be encrypted.
   * @param  data  The plain-text data to be encrypted.
   *
   * @return  A byte array containing the encrypted representation of
   *          the provided data.
   *
   * @throws  GeneralSecurityException  If a problem occurs while
   *                                    attempting to encrypt the
   *                                    data.
   *          encrypting the data.
   *
   * @throws  CryptoManagerException  If a problem occurs managing the
   *          encryption key.
   *          encryption key or producing the cipher.
   */
  public byte[] encrypt(byte[] data)
         throws GeneralSecurityException,
                CryptoManagerException
         throws GeneralSecurityException, CryptoManagerException
  {
    return encrypt(preferredCipherTransformation,
                   preferredCipherTransformationKeyLength, data);
  }
  /**
   * Encrypts the data in the provided byte array using the requested
   * cipher algorithm.
   *
   * @param  cipherTransformation  The algorithm to use to encrypt the
   *                          data.
   * @param  cipherTransformation  The algorithm/mode/padding to use
   *         for the cipher.
   *
   * @param  keyLength  The length of the encrytion key to use.
   * @param  keyLength  The length in bits of the encryption key this
   *         method is to use. Note the specified key length and
   *         transformation must be compatible.
   *
   * @param  data  The data to be encrypted.
   * @param  data  The plain-text data to be encrypted.
   *
   * @return  A byte array containing the encrypted representation of
   *          the provided data.
   *
   * @throws  GeneralSecurityException  If a problem occurs while
   *                                    attempting to encrypt the
   *                                    data.
   *          encrypting the data.
   *
   * @throws  CryptoManagerException  If a problem occurs managing the
   *          encryption key.
   *          encryption key or producing the cipher.
   */
  public byte[] encrypt(String cipherTransformation, int keyLength,
                        byte[] data)
         throws GeneralSecurityException, CryptoManagerException
  {
    Validator.ensureNotNull(cipherTransformation, data);
    Validator.ensureNotNull(cipherTransformation, keyLength, data);
    final byte[] keyID = new byte[16];// FIXME: key id length constant
    final Cipher cipher = getEncryptionCipher(cipherTransformation,
                                              keyLength, keyID);
    final KeyEntry keyEntry = KeyEntry.getKeyEntry(keyEntryMap,
          cipherTransformation, keyLength);
    final Cipher cipher
            = keyEntry.getCipher(Cipher.ENCRYPT_MODE, null);
    final byte[] keyID = keyEntry.getKeyID();
    final byte[] iv = cipher.getIV();
    final int prologueLength
            = keyID.length + ((null == iv) ? 0 : iv.length);
@@ -947,64 +1011,58 @@
  }
  /**
   * Encrypts the data in the provided byte array using the preferred
   * cipher algorithm.
   * Writes encrypted data to the provided output stream using the
   * preferred cipher transformation.
   *
   * @param  outputStream The output stream to be wrapped by the
   *         returned output stream.
   *         returned cipher output stream.
   *
   * @return  A byte array containing the encrypted representation of
   *          the provided data.
   *
   * @throws  GeneralSecurityException  If a problem occurs while
   *                                    attempting to encrypt the
   *                                    data.
   * @return  The output stream wrapped with a CipherOutputStream.
   *
   * @throws  CryptoManagerException  If a problem occurs managing the
   *          encryption key.
   *          encryption key or producing the cipher.
   */
  public CipherOutputStream getCipherOutputStream(
          OutputStream outputStream)
          throws GeneralSecurityException, CryptoManagerException
          OutputStream outputStream) throws CryptoManagerException
  {
    return getCipherOutputStream(preferredCipherTransformation,
            preferredCipherTransformationKeyLength, outputStream);
  }
  /**
   * Encrypts the data in the provided output stream using the
   * Writes encrypted data to the provided output stream using the
   * requested cipher transformation.
   *
   * @param  cipherTransformation  The algorithm to use to encrypt the
   *                          data.
   * @param  cipherTransformation  The algorithm/mode/padding to use
   *         for the cipher.
   *
   * @param  keyLength  The length of the encrytion key to use.
   * @param  keyLength  The length in bits of the encryption key this
   *         method will generate. Note the specified key length must
   *         be compatible with the transformation.
   *
   * @param  outputStream The output stream to be wrapped by the
   *         returned output stream.
   *         returned cipher output stream.
   *
   * @return  A byte array containing the encrypted representation of
   *          the provided data.
   *
   * @throws  GeneralSecurityException  If a problem occurs while
   *                                    attempting to encrypt the
   *                                    data.
   * @return  The output stream wrapped with a CipherOutputStream.
   *
   * @throws  CryptoManagerException  If a problem occurs managing the
   *          encryption key.
   *          encryption key or producing the cipher.
   */
  public CipherOutputStream getCipherOutputStream(
          String cipherTransformation, int keyLength,
          OutputStream outputStream)
         throws GeneralSecurityException, CryptoManagerException
         throws CryptoManagerException
  {
    final byte[] keyID = new byte[16];// FIXME: key id length constant
    final Cipher cipher = getEncryptionCipher(cipherTransformation,
                                              keyLength, keyID);
    Validator.ensureNotNull(cipherTransformation, keyLength,
            outputStream);
    final KeyEntry keyEntry = KeyEntry.getKeyEntry(keyEntryMap,
          cipherTransformation, keyLength);
    final Cipher cipher
            = keyEntry.getCipher(Cipher.ENCRYPT_MODE, null);
    final byte[] keyID = keyEntry.getKeyID();
    try {
      outputStream.write(keyID);
      if (null != cipher.getIV()) {
@@ -1014,12 +1072,12 @@
    catch (IOException ioe) {
      throw new CryptoManagerException(
        // TODO: i18n
        Message.raw("Exception when writing CryptoManager prologue."),
        ioe);
    }
    return new CipherOutputStream(outputStream, cipher);
              Message.raw("Exception when writing CryptoManager" +
                      " prologue."), ioe);
  }
    return new CipherOutputStream(outputStream, cipher);
  }
  /**
@@ -1027,18 +1085,17 @@
   * specified by the key identifier prologue to the data.
   * cipher.
   *
   * @param  data  The data to be decrypted.
   * @param  data  The cipher-text data to be decrypted.
   *
   * @return  A byte array containing the cleartext representation of
   * @return  A byte array containing the clear-text representation of
   *          the provided data.
   *
   * @throws  GeneralSecurityException  If a problem occurs while
   *                                    attempting to decrypt the
   *                                    data.
   *          encrypting the data.
   *
   * @throws  CryptoManagerException  If a problem occurs reading the
   *          key identifier or initialization vector from the data
   *          prologue.
   *          prologue, or using these values to initialize a Cipher.
   */
  public byte[] decrypt(byte[] data)
         throws GeneralSecurityException,
@@ -1052,19 +1109,21 @@
      throw new CryptoManagerException(
              // TODO: i18n
              Message.raw(
         "Exception when reading key identifier from data prologue."),
              ex);
                      "Exception when reading key identifier from" +
                              " data prologue."), ex);
    }
    KeyEntry keyEntry = keyEntryMap.get(new ByteArray(keyID));
    KeyEntry keyEntry = KeyEntry.getKeyEntry(keyEntryMap, keyID);
    if (null == keyEntry) {
      throw new CryptoManagerException(
              // TODO: i18N
             Message.raw("Invalid key identifier in data prologue."));
              Message.raw("Invalid key identifier in data" +
                      " prologue."));
    }
    byte[] iv = null;
    if (0 < keyEntry.initializationVectorLength) {
      iv = new byte[keyEntry.initializationVectorLength/Byte.SIZE];
    if (0 < keyEntry.getIVLength()) {
      iv = new byte[keyEntry.getIVLength()/Byte.SIZE];
      try {
        System.arraycopy(data, keyID.length, iv, 0, iv.length);
      }
@@ -1076,7 +1135,7 @@
      }
    }
    final Cipher cipher = getDecryptionCipher(keyEntry, iv);
    final Cipher cipher = keyEntry.getCipher(Cipher.DECRYPT_MODE, iv);
    final int prologueLength
            = keyID.length + ((null == iv) ? 0 : iv.length);
    return cipher.doFinal(data, prologueLength,
@@ -1094,31 +1153,12 @@
   *
   * @return The CiperInputStream instantiated as specified.
   *
   * @throws  NoSuchAlgorithmException  If the requested algorithm is
   *                                    not supported or is
   *                                    unavailable.
   *
   * @throws  NoSuchPaddingException  If the requested padding
   *                                  mechanism is not supported or is
   *                                  unavailable.
   *
   * @throws  InvalidKeyException  If the provided key is not
   *                               appropriate for use with the
   *                               requested cipher algorithm.
   *
   * @throws  InvalidAlgorithmParameterException
   *               If an internal problem occurs as a result of the
   *               initialization vector used.
   *
   * @throws  CryptoManagerException If there is a problem reading the
   *          key ID or initialization vector from the input stream.
   *          key ID or initialization vector from the input stream,
   *          or using these values to inititalize a Cipher.
   */
  public CipherInputStream getCipherInputStream(
                                            InputStream inputStream)
          throws NoSuchAlgorithmException, NoSuchPaddingException,
                 InvalidKeyException,
                 InvalidAlgorithmParameterException,
                 CryptoManagerException
          InputStream inputStream) throws CryptoManagerException
  {
    KeyEntry keyEntry;
    byte[] iv = null;
@@ -1131,22 +1171,21 @@
                        " identifier from data prologue."));
      }
      keyEntry = keyEntryMap.get(new ByteArray(keyID));
      keyEntry = KeyEntry.getKeyEntry(keyEntryMap, keyID);
      if (null == keyEntry) {
        throw new CryptoManagerException(
                // TODO: i18N
             Message.raw("Invalid key identifier in data prologue."));
      }
      if (0 < keyEntry.initializationVectorLength) {
        iv = new byte[keyEntry.initializationVectorLength/Byte.SIZE];
      if (0 < keyEntry.getIVLength()) {
        iv = new byte[keyEntry.getIVLength() / Byte.SIZE];
        if (iv.length != inputStream.read(iv)) {
          throw new CryptoManagerException(
                  // TODO: i18n
                  Message.raw("Stream underflow when reading" +
                      " initialization vector from data prologue."));
        }
      }
    }
    catch (IOException ioe) {
@@ -1158,7 +1197,7 @@
    }
    return new CipherInputStream(inputStream,
                          getDecryptionCipher(keyEntry, iv));
                         keyEntry.getCipher(Cipher.DECRYPT_MODE, iv));
  }
opends/tests/unit-tests-testng/src/server/org/opends/server/types/CryptoManagerTestCase.java
@@ -45,8 +45,6 @@
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import javax.crypto.Cipher;
/**
 This class tests the CryptoManager.
 */
@@ -129,7 +127,7 @@
    final CryptoManager cm = DirectoryServer.getCryptoManager();
    final String secretMessage = "1234";
    final byte[] cipherText = cm.encrypt("Blowfish/CFB/NoPadding", 64,
    final byte[] cipherText = cm.encrypt("Blowfish/CFB/NoPadding", 128,
            secretMessage.getBytes());
    assertEquals(-1, (new String(cipherText)).indexOf(secretMessage));
@@ -262,4 +260,52 @@
    is.close();
    assertEquals(new String(plainText), secretMessage);
  }
  /**
   Tests a simple encryption-decryption cycle.
   @throws Exception If an exceptional condition arises.
   */
  @Test
  public void testEncryptDecryptSuccessZZ() throws Exception {
    final CryptoManager cm = DirectoryServer.getCryptoManager();
    final String secretMessage = "1234";
    final byte[] cipherText = cm.encrypt("DES/ECB/PKCS5Padding", 64,
            secretMessage.getBytes());
    assertEquals(-1, (new String(cipherText)).indexOf(secretMessage));
    final byte[] plainText = cm.decrypt(cipherText);
    assertEquals((new String(plainText)), secretMessage);
  }
  /**
   Tests a simple cipher stream encryption-decryption cycle.
   @throws Exception If an exceptional condition arises.
   */
  @Test
  public void testCipherEncryptDecryptSuccessZZ() throws Exception {
    final CryptoManager cm = DirectoryServer.getCryptoManager();
    final String secretMessage = "56789";
    final File tempFile
            = File.createTempFile(cm.getClass().getName(), null);
    tempFile.deleteOnExit();
    OutputStream os = new FileOutputStream(tempFile);
    os = cm.getCipherOutputStream("DES/ECB/PKCS5Padding", 64, os);
    os.write(secretMessage.getBytes());
    os.close();
    // TODO: check tempfile for plaintext.
    InputStream is = new FileInputStream(tempFile);
    is = cm.getCipherInputStream(is);
    byte[] plainText = new byte[secretMessage.getBytes().length];
    assertEquals(is.read(plainText), secretMessage.getBytes().length);
    assertEquals(is.read(), -1);
    is.close();
    assertEquals(new String(plainText), secretMessage);
  }
}