From a7a83f2fdcc1647611bf9cf09e75ea434b546b5d Mon Sep 17 00:00:00 2001
From: david_page <david_page@localhost>
Date: Mon, 17 Sep 2007 17:31:38 +0000
Subject: [PATCH] Add support for MAC key entry type. Similar to Cipher key entry; however, caller must maintain key identifier (string), e.g., in backup directory, in order to verify signature. TODO: investigate prefixing MAC signed data with key identifier, and suffixing with signature, for both byte[] and stream. This enhancement will require wrapping the Mac API.

---
 opends/src/server/org/opends/server/extensions/ConfigFileHandler.java                        |   22 
 opends/tests/unit-tests-testng/src/server/org/opends/server/types/CryptoManagerTestCase.java |  314 +++-----
 opends/src/server/org/opends/server/backends/jeb/BackupManager.java                          |   20 
 opends/src/server/org/opends/server/backends/SchemaBackend.java                              |   22 
 opends/src/server/org/opends/server/types/CryptoManager.java                                 | 1665 +++++++++++++++++++++++++++++------------------
 opends/src/server/org/opends/server/util/ServerConstants.java                                |    7 
 6 files changed, 1,181 insertions(+), 869 deletions(-)

diff --git a/opends/src/server/org/opends/server/backends/SchemaBackend.java b/opends/src/server/org/opends/server/backends/SchemaBackend.java
index 2b2a457..46ae527 100644
--- a/opends/src/server/org/opends/server/backends/SchemaBackend.java
+++ b/opends/src/server/org/opends/server/backends/SchemaBackend.java
@@ -4204,18 +4204,18 @@
     Mac           mac             = null;
     MessageDigest digest          = null;
     String        digestAlgorithm = null;
-    String        macAlgorithm    = null;
+    String        macKeyID    = null;
 
     if (hash)
     {
       if (signHash)
       {
-        macAlgorithm = cryptoManager.getPreferredMACAlgorithm();
-        backupProperties.put(BACKUP_PROPERTY_MAC_ALGORITHM, macAlgorithm);
-
         try
         {
-          mac = cryptoManager.getPreferredMACProvider();
+          macKeyID = cryptoManager.getMacEngineKeyEntryID();
+          backupProperties.put(BACKUP_PROPERTY_MAC_KEY_ID, macKeyID);
+
+          mac = cryptoManager.getMacEngine(macKeyID);
         }
         catch (Exception e)
         {
@@ -4225,7 +4225,7 @@
           }
 
           Message message = ERR_SCHEMA_BACKUP_CANNOT_GET_MAC.get(
-              macAlgorithm, stackTraceToSingleLineString(e));
+              macKeyID, stackTraceToSingleLineString(e));
           throw new DirectoryException(
                          DirectoryServer.getServerErrorResultCode(), message,
                          e);
@@ -4653,9 +4653,9 @@
     Mac mac = null;
     if (signedHash != null)
     {
-      String macAlgorithm =
-           backupInfo.getBackupProperty(BACKUP_PROPERTY_MAC_ALGORITHM);
-      if (macAlgorithm == null)
+      String macKeyID =
+           backupInfo.getBackupProperty(BACKUP_PROPERTY_MAC_KEY_ID);
+      if (macKeyID == null)
       {
         Message message = ERR_SCHEMA_RESTORE_UNKNOWN_MAC.get(backupID);
         throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
@@ -4664,12 +4664,12 @@
 
       try
       {
-        mac = DirectoryServer.getCryptoManager().getMACProvider(macAlgorithm);
+        mac = DirectoryServer.getCryptoManager().getMacEngine(macKeyID);
       }
       catch (Exception e)
       {
         Message message = ERR_SCHEMA_RESTORE_CANNOT_GET_MAC.get(
-            backupID, macAlgorithm);
+            backupID, macKeyID);
         throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                      message, e);
       }
diff --git a/opends/src/server/org/opends/server/backends/jeb/BackupManager.java b/opends/src/server/org/opends/server/backends/jeb/BackupManager.java
index c2eca97..9b8f9c7 100644
--- a/opends/src/server/org/opends/server/backends/jeb/BackupManager.java
+++ b/opends/src/server/org/opends/server/backends/jeb/BackupManager.java
@@ -168,18 +168,18 @@
     Mac           mac             = null;
     MessageDigest digest          = null;
     String        digestAlgorithm = null;
-    String        macAlgorithm    = null;
+    String        macKeyID    = null;
 
     if (hash)
     {
       if (signHash)
       {
-        macAlgorithm = cryptoManager.getPreferredMACAlgorithm();
-        backupProperties.put(BACKUP_PROPERTY_MAC_ALGORITHM, macAlgorithm);
-
         try
         {
-          mac = cryptoManager.getPreferredMACProvider();
+          macKeyID = cryptoManager.getMacEngineKeyEntryID();
+          backupProperties.put(BACKUP_PROPERTY_MAC_KEY_ID, macKeyID);
+
+          mac = cryptoManager.getMacEngine(macKeyID);
         }
         catch (Exception e)
         {
@@ -189,7 +189,7 @@
           }
 
           Message message = ERR_JEB_BACKUP_CANNOT_GET_MAC.get(
-              macAlgorithm, stackTraceToSingleLineString(e));
+              macKeyID, stackTraceToSingleLineString(e));
           throw new DirectoryException(
                DirectoryServer.getServerErrorResultCode(), message, e);
         }
@@ -924,15 +924,15 @@
     Mac           mac             = null;
     MessageDigest digest          = null;
     String        digestAlgorithm = null;
-    String        macAlgorithm    = null;
+    String        macKeyID        = null;
 
     if (signHash != null)
     {
-      macAlgorithm = backupProperties.get(BACKUP_PROPERTY_MAC_ALGORITHM);
+      macKeyID = backupProperties.get(BACKUP_PROPERTY_MAC_KEY_ID);
 
       try
       {
-        mac = cryptoManager.getMACProvider(macAlgorithm);
+        mac = cryptoManager.getMacEngine(macKeyID);
       }
       catch (Exception e)
       {
@@ -942,7 +942,7 @@
         }
 
         Message message = ERR_JEB_BACKUP_CANNOT_GET_MAC.get(
-            macAlgorithm, stackTraceToSingleLineString(e));
+            macKeyID, stackTraceToSingleLineString(e));
         throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                      message, e);
       }
diff --git a/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java b/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
index 53f47d4..9cd1262 100644
--- a/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
+++ b/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
@@ -2693,18 +2693,18 @@
     Mac           mac             = null;
     MessageDigest digest          = null;
     String        digestAlgorithm = null;
-    String        macAlgorithm    = null;
+    String        macKeyID    = null;
 
     if (hash)
     {
       if (signHash)
       {
-        macAlgorithm = cryptoManager.getPreferredMACAlgorithm();
-        backupProperties.put(BACKUP_PROPERTY_MAC_ALGORITHM, macAlgorithm);
-
         try
         {
-          mac = cryptoManager.getPreferredMACProvider();
+          macKeyID = cryptoManager.getMacEngineKeyEntryID();
+          backupProperties.put(BACKUP_PROPERTY_MAC_KEY_ID, macKeyID);
+
+          mac = cryptoManager.getMacEngine(macKeyID);
         }
         catch (Exception e)
         {
@@ -2714,7 +2714,7 @@
           }
 
           Message message = ERR_CONFIG_BACKUP_CANNOT_GET_MAC.get(
-              macAlgorithm, stackTraceToSingleLineString(e));
+              macKeyID, stackTraceToSingleLineString(e));
           throw new DirectoryException(
                          DirectoryServer.getServerErrorResultCode(), message,
                          e);
@@ -3183,9 +3183,9 @@
     Mac mac = null;
     if (signedHash != null)
     {
-      String macAlgorithm =
-           backupInfo.getBackupProperty(BACKUP_PROPERTY_MAC_ALGORITHM);
-      if (macAlgorithm == null)
+      String macKeyID =
+           backupInfo.getBackupProperty(BACKUP_PROPERTY_MAC_KEY_ID);
+      if (macKeyID == null)
       {
         Message message = ERR_CONFIG_RESTORE_UNKNOWN_MAC.get(backupID);
         throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
@@ -3194,12 +3194,12 @@
 
       try
       {
-        mac = DirectoryServer.getCryptoManager().getMACProvider(macAlgorithm);
+        mac = DirectoryServer.getCryptoManager().getMacEngine(macKeyID);
       }
       catch (Exception e)
       {
         Message message = ERR_CONFIG_RESTORE_CANNOT_GET_MAC.get(
-            backupID, macAlgorithm);
+            backupID, macKeyID);
         throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                      message, e);
       }
diff --git a/opends/src/server/org/opends/server/types/CryptoManager.java b/opends/src/server/org/opends/server/types/CryptoManager.java
index 786cf44..14b3e40 100644
--- a/opends/src/server/org/opends/server/types/CryptoManager.java
+++ b/opends/src/server/org/opends/server/types/CryptoManager.java
@@ -93,21 +93,28 @@
   private static final Random pseudoRandom
           = new Random(secureRandom.nextLong());
 
-  // The map from encryption key ID to SecretKeyEntry (cache).
-  private final HashMap<ByteArray, SecretKeyEntry> secretKeyEntryMap
-          = new HashMap<ByteArray, SecretKeyEntry>();
+  // The preferred message digest algorithm for the Directory Server.
+  private final String preferredDigestAlgorithm;
+
+  // The map from encryption key ID to MacKeyEntry (cache).
+  private final HashMap<KeyEntryID, MacKeyEntry> macKeyEntryCache
+          = new HashMap<KeyEntryID, MacKeyEntry>();
+
+  // The preferred MAC algorithm for the Directory Server.
+  private final String preferredMACAlgorithm;
+
+  // The preferred key length for the preferred MAC algorithm.
+  private final int preferredMACAlgorithmKeyLengthBits;
+
+  // The map from encryption key ID to CipherKeyEntry (cache).
+  private final HashMap<KeyEntryID, CipherKeyEntry>
+      cipherKeyEntryCache = new HashMap<KeyEntryID, CipherKeyEntry>();
 
   // The preferred cipher for the Directory Server.
   private final String preferredCipherTransformation;
 
   // The preferred key length for the preferred cipher.
-  private final int preferredCipherTransformationKeyLength;
-
-  // The preferred message digest algorithm for the Directory Server.
-  private final String preferredDigestAlgorithm;
-
-  // The preferred MAC algorithm for the Directory Server.
-  private final String preferredMACAlgorithm;
+  private final int preferredCipherTransformationKeyLengthBits;
 
   // The name of the local certificate to use for SSL.
   private final String sslCertNickname;
@@ -142,10 +149,11 @@
   {
     // TODO -- Get the defaults from the configuration rather than
     // hard-coding them.
+    preferredCipherTransformation = "AES/CBC/PKCS5Padding";
+    preferredCipherTransformationKeyLengthBits = 128;
     preferredDigestAlgorithm = "SHA-1";
     preferredMACAlgorithm    = "HmacSHA1";
-    preferredCipherTransformation = "AES/CBC/PKCS5Padding";
-    preferredCipherTransformationKeyLength = 128;
+    preferredMACAlgorithmKeyLengthBits = 128;
 
     sslCertNickname = cfg.getSSLCertNickname();
     sslEncryption   = cfg.isSSLEncryption();
@@ -173,11 +181,9 @@
 
     try
     {
-      // TODO: wrap like encryption
-      byte[] keyValue = new byte[16];
-      secureRandom.nextBytes(keyValue);
-      Mac mac = Mac.getInstance(preferredMACAlgorithm);
-      mac.init(new SecretKeySpec(keyValue, preferredMACAlgorithm));
+      MacKeyEntry.generateKeyEntry(null,
+              preferredMACAlgorithm,
+              preferredMACAlgorithmKeyLengthBits);
     }
     catch (Exception e)
     {
@@ -194,9 +200,9 @@
 
     try
     {
-      SecretKeyEntry.generateKeyEntry(null,
+      CipherKeyEntry.generateKeyEntry(null,
               preferredCipherTransformation,
-              preferredCipherTransformationKeyLength);
+              preferredCipherTransformationKeyLengthBits);
     }
     catch (Exception e)
     {
@@ -214,57 +220,6 @@
 
 
   /**
-   *  Converts a UUID string into a compact byte[16] representation.
-   *
-   *  @param  uuidString A string reprentation of a UUID.
-   *
-   *  @return  A new byte[16] containing the binary representation of
-   *  the UUID.
-   *
-   *  @throws  CryptoManagerException  If the uuidString argument does
-   *  not conform to the UUID string syntax.
-   */
-  private static byte[] uuidToBytes(String uuidString)
-          throws CryptoManagerException
-  {
-    UUID uuid;
-    try {
-      uuid = UUID.fromString(uuidString);
-    }
-    catch (Exception ex) {
-      throw new CryptoManagerException(
-              // TODO: i18n
-              Message.raw("Invalid string representation of a UUID."),
-              ex);
-    }
-    return uuidToBytes(uuid);
-  }
-
-
-  /**
-   *  Converts a UUID instance into a compact byte[16] representation.
-   *
-   *  @param  uuid A UUID instance.
-   *
-   *  @return  A new byte[16] containing the binary representation of
-   *  the UUID.
-   */
-  private static byte[] uuidToBytes(UUID uuid)
-  {
-    final byte[] uuidBytes = new byte[16];
-    long hiBytes = uuid.getMostSignificantBits();
-    long loBytes = uuid.getLeastSignificantBits();
-    for (int i = 7; i >= 0; --i) {
-      uuidBytes[i] = (byte)hiBytes;
-      hiBytes >>>= 8;
-      uuidBytes[8 + i] = (byte)loBytes;
-      loBytes >>>= 8;
-    }
-    return uuidBytes;
-  }
-
-
-  /**
    * Retrieves the name of the preferred message digest algorithm.
    *
    * @return  The name of the preferred message digest algorithm
@@ -444,188 +399,134 @@
 
 
   /**
-   * Retrieves the name of the preferred MAC algorithm.
+   * For the current preferred MAC algorithm and key length, return
+   * the identifier of the corresponding key entry. Note: the result
+   * (key identifier) might change across invocations, due to either
+   * of the perferred parameters changing, or because the original
+   * key was marked compromised and a replacement key generated.
    *
-   * @return  The name of the preferred MAC algorithm
+   * @return A String representation of the identifier of a key entry
+   * corresponding to the preferred MAC algorithm and key length.
+   *
+   * @throws CryptoManagerException In case one or more of the key
+   * parameters is invalid, or there is a problem instantiating the
+   * key entry in case it does not already exist.
    */
-  public String getPreferredMACAlgorithm()
+  public String getMacEngineKeyEntryID()
+          throws CryptoManagerException
   {
-    return preferredMACAlgorithm;
+    return getMacEngineKeyEntryID(preferredMACAlgorithm,
+            preferredMACAlgorithmKeyLengthBits);
   }
 
 
-
   /**
-   * Retrieves a MAC provider using the preferred algorithm.
+   * For the specified MAC algorithm and key length, return
+   * the identifier of the corresponding key entry. Note: the result
+   * (key identifier) might change across invocations, due to either
+   * of the perferred parameters changing, or because the original
+   * key was marked compromised and a replacement key generated.
    *
-   * @return  A MAC provider using the preferred algorithm.
+   * @param  macAlgorithm  The algorithm to use for the MAC engine.
    *
-   * @throws  NoSuchAlgorithmException  If the requested algorithm is
-   *                                    not supported or is
-   *                                    unavailable.
+   * @param  keyLengthBits  The key length in bits to use with the
+   *         specified algorithm.
    *
-   * @throws  InvalidKeyException  If the provided key is not
-   *                               appropriate for use with the
-   *                               requested MAC algorithm.
+   * @return A String representation of the identifier of a key entry
+   * corresponding to the specified MAC algorithm and key length.
+   *
+   * @throws CryptoManagerException In case one or more of the key
+   * parameters is invalid, or there is a problem instantiating the
+   * key entry in case it does not already exist.
    */
-  public Mac getPreferredMACProvider()
-         throws NoSuchAlgorithmException, InvalidKeyException
-  {
-    return getMACProvider(getPreferredMACAlgorithm());
+  public String getMacEngineKeyEntryID(final String macAlgorithm,
+                                       final int keyLengthBits)
+         throws CryptoManagerException {
+    Validator.ensureNotNull(macAlgorithm);
+
+    MacKeyEntry keyEntry = MacKeyEntry.getKeyEntry(this, macAlgorithm,
+                                                   keyLengthBits);
+    if (null == keyEntry) {
+      keyEntry = MacKeyEntry.generateKeyEntry(this, macAlgorithm,
+                                              keyLengthBits);
+    }
+
+    return keyEntry.getKeyID().getStringValue();
   }
 
 
+  /**
+   * For the specified key entry identifier, instantiate a MAC engine.
+   *
+   * @param keyEntryID The identifier of the key entry containing the
+   * desired MAC algorithm name and key length.
+   *
+   * @return The MAC engine instantiated with the parameters from the
+   * referenced key entry, or null if no such entry exists.
+   *
+   * @throws CryptoManagerException  In case the key entry identifier
+   * is invalid or there is a problem instatiating the MAC engine from
+   * the parameters in the referenced key entry.
+   */
+  public Mac getMacEngine(String keyEntryID)
+          throws CryptoManagerException
+  {
+    MacKeyEntry keyEntry;
+    try {
+      keyEntry = MacKeyEntry.getKeyEntry(this,
+              new KeyEntryID(keyEntryID));
+    }
+    catch (IllegalArgumentException ex) {
+      throw new CryptoManagerException(
+              // TODO: i18n
+              Message.raw("MAC key entry identifier \"%s\" is not" +
+                      " a valid UUID.", keyEntryID), ex);
+    }
+
+    if (null == keyEntry) return null;
+
+    return getMacEngine(keyEntry);
+  }
+
 
   /**
-   * Retrieves a MAC provider using the specified algorithm.
+   * This method produces an initialized MAC engine based on the
+   * supplied MacKeyEntry's state.
    *
-   * @param  macAlgorithm  The algorithm to use for the MAC provider.
+   * @param keyEntry The MacKeyEntry specifying the Mac properties.
    *
-   * @return  A MAC provider using the specified algorithm.
+   * @return  An initialized Mac object.
    *
-   * @throws  NoSuchAlgorithmException  If the requested algorithm is
-   *                                    not supported or is
-   *                                    unavailable.
-   *
-   * @throws  InvalidKeyException  If the provided key is not
-   *                               appropriate for use with the
-   *                               requested MAC algorithm.
+   * @throws CryptoManagerException  In case there was a error
+   * instantiating the Mac object.
    */
-  public Mac getMACProvider(String macAlgorithm)
-         throws NoSuchAlgorithmException, InvalidKeyException
+  private static Mac getMacEngine(MacKeyEntry keyEntry)
+          throws CryptoManagerException
   {
-    Mac mac = Mac.getInstance(macAlgorithm);
-    byte[] keyValue = new byte[16];
-    secureRandom.nextBytes(keyValue);
-    mac.init(new SecretKeySpec(keyValue, macAlgorithm));
+    Mac mac;
+    try {
+      mac = Mac.getInstance(keyEntry.getType());
+    }
+    catch (NoSuchAlgorithmException ex){
+      throw new CryptoManagerException(
+              // TODO: i18n
+              Message.raw("Invalid MAC algorithm specified: + %s",
+                      keyEntry.getType()), ex);
+    }
+
+    try {
+      mac.init(keyEntry.getKeySpec());
+    }
+    catch (InvalidKeyException ex) {
+      throw new CryptoManagerException(
+              // TODO: i18n
+              Message.raw("Invalid key specification supplied to" +
+                      " Mac object initialization"), ex);
+    }
 
     return mac;
   }
 
-
-
-  /**
-   * Retrieves a byte array containing a MAC based on the provided
-   * data, using the preferred MAC algorithm.
-   *
-   * @param  data  The data for which to generate the MAC.
-   *
-   * @return  A byte array containing the generated MAC.
-   *
-   * @throws  NoSuchAlgorithmException  If the requested algorithm is
-   *                                    not supported or is
-   *                                    unavailable.
-   */
-  public byte[] mac(byte[] data)
-         throws NoSuchAlgorithmException
-  {
-    return Mac.getInstance(preferredMACAlgorithm).doFinal(data);
-  }
-
-
-
-  /**
-   * Retrieves a byte array containing a MAC based on the provided
-   * data, using the requested MAC algorithm.
-   *
-   * @param  macAlgorithm  The algorithm to use for the MAC.
-   * @param  data          The data for which to generate the MAC.
-   *
-   * @return  A byte array containing the generated MAC.
-   *
-   * @throws  NoSuchAlgorithmException  If the requested algorithm is
-   *                                    not supported or is
-   *                                    unavailable.
-   */
-  public byte[] mac(String macAlgorithm, byte[] data)
-         throws NoSuchAlgorithmException
-  {
-    return Mac.getInstance(macAlgorithm).doFinal(data);
-  }
-
-
-
-  /**
-   * Retrieves a byte array containing a MAC based on the data read
-   * from the provided input stream, using the preferred MAC
-   * algorithm.  Data will be read until the end of the stream is
-   * reached.
-   *
-   * @param  inputStream  The input stream from which the data is to
-   *                      be read.
-   *
-   * @return  A byte array containing the generated MAC.
-   *
-   * @throws  IOException  If a problem occurs while reading data from
-   *                       the provided stream.
-   *
-   * @throws  NoSuchAlgorithmException  If the requested algorithm is
-   *                                    not supported or is
-   *                                    unavailable.
-   */
-  public byte[] mac(InputStream inputStream)
-         throws IOException, NoSuchAlgorithmException
-  {
-    Mac mac = Mac.getInstance(preferredMACAlgorithm);
-
-    byte[] buffer = new byte[8192];
-    while (true)
-    {
-      int bytesRead = inputStream.read(buffer);
-      if (bytesRead < 0)
-      {
-        break;
-      }
-
-      mac.update(buffer, 0, bytesRead);
-    }
-
-    return mac.doFinal();
-  }
-
-
-
-  /**
-   * Retrieves a byte array containing a MAC based on the data read
-   * from the provided input stream, using the preferred MAC
-   * algorithm.  Data will be read until the end of the stream is
-   * reached.
-   *
-   * @param  macAlgorithm  The algorithm to use for the MAC.
-   * @param  inputStream   The input stream from which the data is to
-   *                       be read.
-   *
-   * @return  A byte array containing the generated MAC.
-   *
-   * @throws  IOException  If a problem occurs while reading data from
-   *                       the provided stream.
-   *
-   * @throws  NoSuchAlgorithmException  If the requested algorithm is
-   *                                    not supported or is
-   *                                    unavailable.
-   */
-  public byte[] mac(String macAlgorithm, InputStream inputStream)
-         throws IOException, NoSuchAlgorithmException
-  {
-    Mac mac = Mac.getInstance(macAlgorithm);
-
-    byte[] buffer = new byte[8192];
-    while (true)
-    {
-      int bytesRead = inputStream.read(buffer);
-      if (bytesRead < 0)
-      {
-        break;
-      }
-
-      mac.update(buffer, 0, bytesRead);
-    }
-
-    return mac.doFinal();
-  }
-
-
-
   /**
    * Retrieves the name of the preferred cipher algorithm.
    *
@@ -638,376 +539,8 @@
 
 
   /**
-   * This class corresponds to the encryption key entry in ADS. It is
-   * used in the local cache of key entries that have been requested
-   * by CryptoManager clients.
-   */
-  private static class SecretKeyEntry
-  {
-    /**
-     * This method generates a key according to the key parameters,
-     * and creates a key entry and registers it in the supplied map.
-     *
-     * @param map  The SecretKeyEntry Map in which the key is to be
-     * stored. Pass null as the argument to this parameter in order to
-     * validate a proposed cipher transformation and key length.
-     *
-     * @param transformation  The cipher transformation for which the
-     * key is to be produced.
-     *
-     * @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 static SecretKeyEntry generateKeyEntry(
-            final Map<ByteArray, SecretKeyEntry> map,
-            final String transformation,
-            final int keyLengthBits)
-    throws CryptoManagerException {
-
-      SecretKeyEntry keyEntry;
-
-      // check for existing uncompromised key.
-      if (null != map) {
-        keyEntry = getKeyEntry(map, transformation, keyLengthBits);
-        if (null != keyEntry) {
-          return keyEntry;
-        }
-      }
-
-      // generate a new key
-      if (0 != keyLengthBits % Byte.SIZE) {
-        throw new CryptoManagerException(
-                // TODO: i18n
-                Message.raw("keyLengthBits argument must be evenly"
-                        + " divisible by %d.", Byte.SIZE));
-      }
-
-      // In case a transformation is supplied instead of an algorithm:
-      // E.g., AES/CBC/PKCS5Padding -> AES
-      final int separatorIndex = transformation.indexOf('/');
-      final String keyAlgorithm = (0 < separatorIndex)
-              ? transformation.substring(0, separatorIndex)
-              : transformation;
-      KeyGenerator keyGen;
-      try {
-        keyGen = KeyGenerator.getInstance(keyAlgorithm);
-      }
-      catch (NoSuchAlgorithmException ex) {
-        throw new CryptoManagerException(
-                // TODO: i18n
-                Message.raw("Unable to produce key generator from" +
-                        " cipher transformation argument %s",
-                        transformation), ex);
-      }
-      keyGen.init(keyLengthBits, secureRandom);
-      final byte[] key = keyGen.generateKey().getEncoded();
-
-      // generate the key entry
-      final byte[] keyID = uuidToBytes(UUID.randomUUID());
-      keyEntry = new SecretKeyEntry(keyID, transformation,
-              keyAlgorithm, key, /* compute IV length */ -1, false);
-
-      // Validate the entry. Pass the keyAlgorithm for the cipher
-      // transformation.
-      final Cipher cipher
-              = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
-      final byte[] iv = cipher.getIV();
-      keyEntry.setIVLengthBits(
-              (null == iv) ? 0 : iv.length * Byte.SIZE);
-
-      if (null != map) {
-        map.put(new ByteArray(keyID), keyEntry);
-        // TODO: publish key to ADS. (mark key "blocked" in map
-        // until registered?)
-      }
-
-      return keyEntry;
-    }
-
-
-    /**
-     * Initializes a secret key entry from the supplied parameters,
-     * validates it, and registers it in the supplied map. The
-     * anticipated use of this method is to import a key entry from
-     * ADS.
-     *
-     * @param map  The secret key map.
-     *
-     * @param keyID  The key identifier.
-     *
-     * @param transformation  The cipher transformation for which the
-     * key entry was produced.
-     *
-     * @param keyAlgorithm  The cipher algorithm for which the key was
-     * produced.
-     *
-     * @param key  The cipher key.
-     *
-     * @param ivLengthBits  The length of the initialization vector,
-     * which will be zero in the case of any stream cipher algorithm,
-     * or any block cipher algorithm for which the transformation mode
-     * does not use an initialization vector.
-     *
-     * @param isCompromised  Mark the key as compromised, so that it
-     * will not subsequently be used for encryption. The key must be
-     * maintained in order to decrypt existing ciphertext.
-     *
-     * @return  The key entry, if one was successfully produced.
-     *
-     * @throws CryptoManagerException  In case of an error in the
-     * parameters used to initialize or validate the key entry.
-     */
-    public static SecretKeyEntry setKeyEntry(
-            final Map<ByteArray, SecretKeyEntry> map,
-            final byte[] keyID,
-            final String transformation,
-            final String keyAlgorithm,
-            final byte[] key,
-            final int ivLengthBits,
-            final boolean isCompromised)
-            throws CryptoManagerException {
-      Validator.ensureNotNull(keyID, transformation, keyAlgorithm,
-              key);
-      Validator.ensureTrue(16 == keyID.length);
-      Validator.ensureTrue(0 <= ivLengthBits);
-
-      // Check map for existing key entry with the supplied keyID.
-      SecretKeyEntry keyEntry = getKeyEntry(map, keyID);
-      if (null != keyEntry) {
-        // TODO: compare keyEntry with supplied parameters to ensure
-        // equal.
-        return keyEntry;
-      }
-
-      // Instantiate new entry.
-      keyEntry = new SecretKeyEntry(keyID, transformation,
-              keyAlgorithm, key, ivLengthBits, isCompromised);
-
-      // Validate the entry. Pass the keyAlgorithm for the cipher
-      // transformation.
-      byte[] iv = null;
-      if (0 < ivLengthBits) {
-        iv = new byte[ivLengthBits * Byte.SIZE];
-        pseudoRandom.nextBytes(iv);
-      }
-      getCipher(keyEntry, Cipher.DECRYPT_MODE, iv);
-
-      map.put(new ByteArray(keyID), keyEntry);
-
-      return keyEntry;
-    }
-
-
-    /**
-     * Retrieve a SecretKeyEntry from the SecretKeyEntry Map based on
-     * cipher algorithm name and key length.
-     *
-     * @param map  The SecretKeyEntry Map in which the key is stored.
-     *
-     * @param transformation  The cipher algorithm for which the key
-     * was produced.
-     *
-     * @param keyLengthBits  The cipher key length in bits.
-     *
-     * @return  The key entry corresponding to the parameters, or null
-     * if no such entry exists.
-     */
-    public static SecretKeyEntry getKeyEntry(
-            final Map<ByteArray, SecretKeyEntry> map,
-            final String transformation,
-            final int keyLengthBits) {
-      Validator.ensureNotNull(map, transformation);
-      Validator.ensureTrue(0 < keyLengthBits);
-
-      SecretKeyEntry keyEntry = null;
-
-      // search for an existing key that satisfies the request
-      for (Map.Entry<ByteArray, SecretKeyEntry> i: map.entrySet()) {
-        SecretKeyEntry entry = i.getValue();
-        if (! entry.fIsCompromised
-                && entry.getTransformation().equals(transformation)
-                && entry.fKeyLengthBits == keyLengthBits) {
-          assert Arrays.equals(i.getKey().array(), entry.fKeyID);
-          keyEntry = entry;
-          break;
-        }
-      }
-
-      // TODO: if (null == keyEntry) Does ADS monitoring thread keep
-      // map updated with keys produced at other sites? Otherwise,
-      // search ADS for suitable key.
-
-      // TODO: if (null == keyEntry) consider generating key here.
-
-      if (null != keyEntry) {
-        Validator.ensureTrue(0 <= keyEntry.getIVLength(),
-                "SecretKeyEntry initialization is not complete.");
-      }
-
-      return keyEntry;
-    }
-
-
-    /**
-     * Given a key identifier, return the associated secret key entry
-     * from the supplied map. This method would typically be used by
-     * a decryption routine.
-     *
-     * @param map  The local cache of key entries.
-     *
-     * @param keyID  The key identifier.
-     *
-     * @return  The key entry associated with the key identifier.
-     */
-    public static SecretKeyEntry getKeyEntry(
-            Map<ByteArray, SecretKeyEntry> 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 SecretKeyEntry using the specified
-     * parameters. This constructor would typically be used for key
-     * entries imported from ADS, for which the full set of paramters
-     * is known, and for a new key entry, for which the initialization
-     * vector length might not yet be known, but which must be set
-     * before use.
-     *
-     * @param keyID  The unique identifier of this cipher
-     * transformation/key pair.
-     *
-     * @param transformation  The secret-key cipher transformation for
-     * which the key entry is to be produced.
-     *
-     * @param keyAlgorithm  The secret key cipher algorithm for which
-     * the key was produced.
-     *
-     * @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).
-     *
-     * @param isCompromised If the key
-     *
-     * @throws  CryptoManagerException If there is a problem
-     * instantiating a Cipher object in order to validate the supplied
-     * parameters when creating a new entry.
-     */
-    private SecretKeyEntry( final byte[] keyID,
-                            final String transformation,
-                            final String keyAlgorithm,
-                            final byte[] key,
-                            final int ivLengthBits,
-                            final boolean isCompromised)
-            throws CryptoManagerException {
-      Validator.ensureNotNull(keyID, transformation, key);
-      Validator.ensureTrue(16 == keyID.length); // FIXME: const for id
-
-      // copy arguments
-      this.fKeyID = new byte[keyID.length];
-      System.arraycopy(keyID, 0, this.fKeyID, 0, keyID.length);
-      this.fTransformation = new String(transformation);
-      this.fKeySpec = new SecretKeySpec(key, keyAlgorithm);
-      this.fKeyLengthBits = key.length * Byte.SIZE;
-      this.fIVLengthBits = ivLengthBits;
-      this.fIsCompromised = isCompromised;
-    }
-
-
-    /**
-     * The cipher transformation for which the key entry was created.
-     *
-     * @return The cipher transformation.
-     */
-    public String getTransformation() {
-      return fTransformation;
-    }
-
-    /**
-     * 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 secret key spec containing the secret key.
-     *
-     * @return The secret key spec containing the secret key.
-     */
-    public SecretKeySpec getKeySpec() {
-      return fKeySpec;
-    }
-
-
-    /**
-     * 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.
-     * <p>
-     * This method returns -1 if the object initialization has not
-     * been completed.
-     *
-     * @return The initialization vector length.
-     */
-    public int getIVLength() {
-      return fIVLengthBits;
-    }
-
-
-    /**
-     * Set the algorithm/key pair's required initialization vector
-     * length in bits. Typically, this will be the cipher's block
-     * size, or 0 for a stream cipher or a block cipher mode that does
-     * not use an initialization vector (e.g., ECB).
-     *
-     * @param ivLengthBits The initiazliation vector length in bits.
-     */
-    private void setIVLengthBits(int ivLengthBits) {
-      Validator.ensureTrue(-1 == fIVLengthBits && 0 <= ivLengthBits);
-      fIVLengthBits = ivLengthBits;
-    }
-
-
-    /**
-     * 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 SecretKeySpec fKeySpec;
-    private final int fKeyLengthBits;
-    private int fIVLengthBits;
-    private boolean fIsCompromised = false;
-  }
-
-
-  /**
-   * This method produces and initialized Cipher based on this
-   * SecretKeyEntry's state and the method parameters.
+   * This method produces an initialized Cipher based on the supplied
+   * CipherKeyEntry's state.
    *
    * @param keyEntry  The secret key entry containing the cipher
    * transformation and secret key for which to instantiate
@@ -1026,7 +559,7 @@
    * include NoSuchAlgorithmException, NoSuchPaddingException,
    * InvalidKeyException, and InvalidAlgorithmParameterException.
    */
-  private static Cipher getCipher(final SecretKeyEntry keyEntry,
+  private static Cipher getCipher(final CipherKeyEntry keyEntry,
                                   final int mode,
                                   final byte[] initializationVector)
           throws CryptoManagerException {
@@ -1034,30 +567,30 @@
             || Cipher.DECRYPT_MODE == mode);
     Validator.ensureTrue(Cipher.ENCRYPT_MODE != mode
             || null == initializationVector);
-    Validator.ensureTrue(-1 != keyEntry.getIVLength()
+    Validator.ensureTrue(-1 != keyEntry.getIVLengthBits()
             || Cipher.ENCRYPT_MODE == mode);
     Validator.ensureTrue(null == initializationVector
             || initializationVector.length * Byte.SIZE
-                                          == keyEntry.getIVLength());
+                                       == keyEntry.getIVLengthBits());
 
     Cipher cipher;
     try {
-      cipher = Cipher.getInstance(keyEntry.getTransformation());
+      cipher = Cipher.getInstance(keyEntry.getType());
     }
     catch (GeneralSecurityException ex) {
       // NoSuchAlgorithmException, NoSuchPaddingException
       throw new CryptoManagerException(
               // TODO: i18n
-              Message.raw("Invalid cipher transformation specified"
-                      + " %s.", keyEntry.getTransformation()), ex);
+              Message.raw("Invalid Cipher transformation specified:"
+                      + " %s.", keyEntry.getType()), ex);
     }
 
     try {
-      if (0 < keyEntry.getIVLength()) {
+      if (0 < keyEntry.getIVLengthBits()) {
           byte[] iv;
           if (Cipher.ENCRYPT_MODE == mode
                   && null == initializationVector) {
-            iv = new byte[keyEntry.getIVLength() / Byte.SIZE];
+            iv = new byte[keyEntry.getIVLengthBits() / Byte.SIZE];
             pseudoRandom.nextBytes(iv);
           }
           else {
@@ -1100,7 +633,7 @@
          throws GeneralSecurityException, CryptoManagerException
   {
     return encrypt(preferredCipherTransformation,
-                   preferredCipherTransformationKeyLength, data);
+            preferredCipherTransformationKeyLengthBits, data);
   }
 
 
@@ -1133,17 +666,17 @@
   {
     Validator.ensureNotNull(cipherTransformation, data);
 
-    SecretKeyEntry keyEntry = SecretKeyEntry.getKeyEntry(
-            secretKeyEntryMap, cipherTransformation, keyLengthBits);
+    CipherKeyEntry keyEntry = CipherKeyEntry.getKeyEntry(
+            this, cipherTransformation, keyLengthBits);
     if (null == keyEntry) {
-      keyEntry = SecretKeyEntry.generateKeyEntry(secretKeyEntryMap,
+      keyEntry = CipherKeyEntry.generateKeyEntry(this,
               cipherTransformation, keyLengthBits);
     }
 
     final Cipher cipher
             = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
 
-    final byte[] keyID = keyEntry.getKeyID();
+    final byte[] keyID = keyEntry.getKeyID().getByteValue();
     final byte[] iv = cipher.getIV();
     final int prologueLength
             = keyID.length + ((null == iv) ? 0 : iv.length);
@@ -1175,7 +708,7 @@
           OutputStream outputStream) throws CryptoManagerException
   {
     return getCipherOutputStream(preferredCipherTransformation,
-                preferredCipherTransformationKeyLength, outputStream);
+            preferredCipherTransformationKeyLengthBits, outputStream);
   }
 
 
@@ -1205,16 +738,16 @@
   {
     Validator.ensureNotNull(cipherTransformation, outputStream);
 
-    SecretKeyEntry keyEntry = SecretKeyEntry.getKeyEntry(
-            secretKeyEntryMap, cipherTransformation, keyLengthBits);
+    CipherKeyEntry keyEntry = CipherKeyEntry.getKeyEntry(
+            this, cipherTransformation, keyLengthBits);
     if (null == keyEntry) {
-      keyEntry = SecretKeyEntry.generateKeyEntry(secretKeyEntryMap,
+      keyEntry = CipherKeyEntry.generateKeyEntry(this,
               cipherTransformation, keyLengthBits);
     }
 
     final Cipher cipher
             = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
-    final byte[] keyID = keyEntry.getKeyID();
+    final byte[] keyID = keyEntry.getKeyID().getByteValue();
     try {
       outputStream.write(keyID);
       if (null != cipher.getIV()) {
@@ -1253,9 +786,12 @@
          throws GeneralSecurityException,
                 CryptoManagerException
   {
-    final byte[] keyID = new byte[16]; //FIXME: key length constant
+    KeyEntryID keyID;
     try {
-      System.arraycopy(data, 0, keyID, 0, keyID.length);
+      final byte[] keyIDBytes
+              = new byte[KeyEntryID.getByteValueLength()];
+      System.arraycopy(data, 0, keyIDBytes, 0, keyIDBytes.length);
+      keyID = new KeyEntryID(keyIDBytes);
     }
     catch (Exception ex) {
       throw new CryptoManagerException(
@@ -1265,20 +801,20 @@
                               " data prologue."), ex);
     }
 
-    SecretKeyEntry keyEntry
-            = SecretKeyEntry.getKeyEntry(secretKeyEntryMap, keyID);
+    CipherKeyEntry keyEntry = CipherKeyEntry.getKeyEntry(this, keyID);
     if (null == keyEntry) {
       throw new CryptoManagerException(
               // TODO: i18N
-              Message.raw("Invalid key identifier in data" +
-                      " prologue."));
+              Message.raw("Invalid or unknown key identifier in" +
+                      " data prologue."));
     }
 
     byte[] iv = null;
-    if (0 < keyEntry.getIVLength()) {
-      iv = new byte[keyEntry.getIVLength()/Byte.SIZE];
+    if (0 < keyEntry.getIVLengthBits()) {
+      iv = new byte[keyEntry.getIVLengthBits()/Byte.SIZE];
       try {
-        System.arraycopy(data, keyID.length, iv, 0, iv.length);
+        System.arraycopy(data, KeyEntryID.getByteValueLength(), iv, 0,
+                iv.length);
       }
       catch (Exception ex) {
         throw new CryptoManagerException(
@@ -1288,10 +824,10 @@
       }
     }
 
-    final Cipher cipher
-            = getCipher(keyEntry, Cipher.DECRYPT_MODE, iv);
-    final int prologueLength
-            = keyID.length + ((null == iv) ? 0 : iv.length);
+    final Cipher cipher = getCipher(keyEntry, Cipher.DECRYPT_MODE,
+            iv);
+    final int prologueLength = KeyEntryID.getByteValueLength()
+                                     + ((null == iv) ? 0 : iv.length);
     return cipher.doFinal(data, prologueLength,
                           data.length - prologueLength);
   }
@@ -1314,10 +850,10 @@
   public CipherInputStream getCipherInputStream(
           InputStream inputStream) throws CryptoManagerException
   {
-    SecretKeyEntry keyEntry;
+    CipherKeyEntry keyEntry;
     byte[] iv = null;
     try {
-      final byte[] keyID = new byte[16]; //FIXME: key length constant
+      final byte[] keyID = new byte[KeyEntryID.getByteValueLength()];
       if (keyID.length != inputStream.read(keyID)){
         throw new CryptoManagerException(
                 // TODO: i18n
@@ -1325,15 +861,16 @@
                         " identifier from data prologue."));
       }
 
-      keyEntry = SecretKeyEntry.getKeyEntry(secretKeyEntryMap, keyID);
+      keyEntry = CipherKeyEntry.getKeyEntry(this,
+              new KeyEntryID(keyID));
       if (null == keyEntry) {
         throw new CryptoManagerException(
                 // TODO: i18N
              Message.raw("Invalid key identifier in data prologue."));
       }
 
-      if (0 < keyEntry.getIVLength()) {
-        iv = new byte[keyEntry.getIVLength() / Byte.SIZE];
+      if (0 < keyEntry.getIVLengthBits()) {
+        iv = new byte[keyEntry.getIVLengthBits() / Byte.SIZE];
         if (iv.length != inputStream.read(iv)) {
           throw new CryptoManagerException(
                   // TODO: i18n
@@ -1571,6 +1108,868 @@
   }
 
   /**
+   * This class implements a utility interface to the unique
+   * identifier corresponding to a cryptographic key. For each key
+   * stored in an entry in ADS, the key identifier is the naming
+   * attribute of the entry. The external binary representation of the
+   * key entry identifier is compact, because it is typically stored
+   * as a prefix of encrypted data.
+   */
+  private static class KeyEntryID
+  {
+    /**
+     *  Constructs a KeyEntryID using a new unique identifier.
+     */
+    public KeyEntryID() {
+      fValue = UUID.randomUUID();
+    }
+
+    /**
+     * Construct a {@code KeyEntryID} from its {@code byte[]}
+     * representation.
+     *
+     * @param keyEntryID The {@code byte[]} representation of a
+     * {@code KeyEntryID}.
+     */
+    public KeyEntryID(final byte[] keyEntryID) {
+      Validator.ensureTrue(getByteValueLength() == keyEntryID.length);
+      long hiBytes = 0;
+      long loBytes = 0;
+      for (int i = 0; i < 8; ++i) {
+        hiBytes = (hiBytes << 8) | (keyEntryID[i] & 0xff);
+        loBytes = (loBytes << 8) | (keyEntryID[8 + i] & 0xff);
+      }
+      fValue = new UUID(hiBytes, loBytes);
+    }
+
+    /**
+     * Constructs a {@code KeyEntryID} from its {@code String}
+     * representation.
+     *
+     * @param  keyEntryID The {@code String} reprentation of a
+     * {@code KeyEntryID}.
+     *
+     * @throws  CryptoManagerException  If the argument does
+     * not conform to the {@code KeyEntryID} string syntax.
+     */
+    public KeyEntryID(final String keyEntryID)
+            throws CryptoManagerException {
+      try {
+        fValue = UUID.fromString(keyEntryID);
+      }
+      catch (Exception ex) {
+        throw new CryptoManagerException(
+                // TODO: i18n
+                Message.raw("Key entry identifier \"%s\" has" +
+                        " invalid syntax.", keyEntryID), ex);
+      }
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param keyEntryID  The {@code KeyEntryID} to copy.
+     */
+    public KeyEntryID(final KeyEntryID keyEntryID) {
+      fValue = new UUID(keyEntryID.fValue.getMostSignificantBits(),
+                        keyEntryID.fValue.getLeastSignificantBits());
+    }
+
+    /**
+     * Returns the compact {@code byte[]} representation of this
+     * {@code KeyEntryID}.
+     * @return The compact {@code byte[]} representation of this
+     * {@code KeyEntryID
+     */
+    public byte[] getByteValue(){
+      final byte[] uuidBytes = new byte[16];
+      long hiBytes = fValue.getMostSignificantBits();
+      long loBytes = fValue.getLeastSignificantBits();
+      for (int i = 7; i >= 0; --i) {
+        uuidBytes[i] = (byte)hiBytes;
+        hiBytes >>>= 8;
+        uuidBytes[8 + i] = (byte)loBytes;
+        loBytes >>>= 8;
+      }
+      return uuidBytes;
+    }
+
+    /**
+     * Returns the {@code String} representation of this
+     * {@code KeyEntryID}.
+     * @return The {@code String} representation of this
+     * {@code KeyEntryID}.
+     */
+    public String getStringValue() {
+      return fValue.toString();
+    }
+
+    /**
+     * Returns the length of the compact {@code byte[]} representation
+     * of a {@code KeyEntryID}.
+     *
+     * @return The length of the compact {@code byte[]} representation
+     * of a {@code KeyEntryID}.
+     */
+    public static int getByteValueLength() {
+      return 16;
+    }
+
+    /**
+     * Compares this object to the specified object. The result is
+     * true if and only if the argument is not null, is of type
+     * {@code KeyEntryID}, and has the same value (i.e., the
+     * {@code String} and {@code byte[]} representations are
+     * identical).
+     *
+     * @param obj The object to which to compare this instance.
+     *
+     * @return {@code true} if the objects are the same, {@code false}
+     * otherwise.
+     */
+    public boolean equals(final Object obj){
+      return obj instanceof KeyEntryID
+              && fValue.equals(((KeyEntryID) obj).fValue);
+    }
+
+    /**
+     * Returns a hash code for this {@code KeyEntryID}.
+     *
+     * @return a hash code value for this {@code KeyEntryID}.
+     */
+    public int hashCode() {
+      return fValue.hashCode();
+    }
+
+    // state
+    private final UUID fValue;
+  }
+
+
+  /**
+   * This class corresponds to the secret key portion if a secret
+   * key entry in ADS.
+   */
+  private static class SecretKeyEntry
+  {
+    /**
+     * Construct an instance of {@code SecretKeyEntry} using the
+     * specified parameters. This constructor is used for key
+     * generation.
+     *
+     * @param algorithm  The name of the secret key algorithm for
+     * which the key entry is to be produced.
+     *
+     * @param keyLengthBits  The length of the requested key in bits.
+     *
+     * @throws CryptoManagerException If there is a problem
+     * instantiating the key generator.
+     */
+    public SecretKeyEntry(String algorithm, int keyLengthBits)
+    throws CryptoManagerException {
+      KeyGenerator keyGen;
+      try {
+        keyGen = KeyGenerator.getInstance(algorithm);
+      }
+      catch (NoSuchAlgorithmException ex) {
+        throw new CryptoManagerException(
+                // TODO: i18n
+                Message.raw("Unable to produce key generator using" +
+                        " key algorithm argument %s",
+                        algorithm), ex);
+      }
+      keyGen.init(keyLengthBits, secureRandom);
+      final byte[] key = keyGen.generateKey().getEncoded();
+
+      this.fKeyID = new KeyEntryID();
+      this.fKeySpec = new SecretKeySpec(key, algorithm);
+      this.fKeyLengthBits = key.length * Byte.SIZE;
+      this.fIsCompromised = false;
+    }
+
+
+    /**
+     * Construct an instance of {@code SecretKeyEntry} using the
+     * specified parameters. This constructor would typically be used
+     * for key entries imported from ADS, for which the full set of
+     * paramters is known.
+     *
+     * @param keyID  The unique identifier of this algorithm/key pair.
+     *
+     * @param algorithm  The name of the secret key algorithm for
+     * which the key entry is to be produced.
+     *
+     * @param key  The secret key.
+     *
+     * @param isCompromised {@code false} if the key may be used
+     * for operations on new data, or {@code true} if the key is being
+     * retained only for use in validation.
+     */
+    public SecretKeyEntry(final KeyEntryID keyID,
+                          final String algorithm,
+                          final byte[] key,
+                          final boolean isCompromised) {
+      // copy arguments
+      this.fKeyID = new KeyEntryID(keyID);
+      this.fKeySpec = new SecretKeySpec(key, algorithm);
+      this.fKeyLengthBits = key.length * Byte.SIZE;
+      this.fIsCompromised = isCompromised;
+    }
+
+
+    /**
+     * The unique identifier of this algorithm/key pair.
+     *
+     * @return The unique identifier of this algorithm/key pair.
+     */
+    public KeyEntryID getKeyID() {
+      return fKeyID;
+    }
+
+
+    /**
+     * The secret key spec containing the secret key.
+     *
+     * @return The secret key spec containing the secret key.
+     */
+    public SecretKeySpec getKeySpec() {
+      return fKeySpec;
+    }
+
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Returns the length of the secret key in bits.
+     * @return the length of the secret key in bits.
+     */
+    public int getKeyLengthBits() {
+      return fKeyLengthBits;
+    }
+
+    /**
+     * Returns the status of the key.
+     * @return  {@code false} if the key may be used for operations on
+     * new data, or {@code true} if the key is being retained only for
+     * use in validation.
+     */
+    public boolean isCompromised() {
+      return fIsCompromised;
+    }
+
+    // state
+    private final KeyEntryID fKeyID;
+    private final SecretKeySpec fKeySpec;
+    private final int fKeyLengthBits;
+    private boolean fIsCompromised = false;
+  }
+
+  /**
+   * This class corresponds to the cipher key entry in ADS. It is
+   * used in the local cache of key entries that have been requested
+   * by CryptoManager clients.
+   */
+  private static class CipherKeyEntry extends SecretKeyEntry
+  {
+    /**
+     * This method generates a key according to the key parameters,
+     * and creates a key entry and registers it in the supplied map.
+     *
+     * @param  cryptoManager The CryptoManager instance for which the
+     * key is to be generated. Pass {@code null} as the argument to
+     * this parameter in order to validate a proposed cipher
+     * transformation and key length without publishing the key.
+     *
+     * @param transformation  The cipher transformation for which the
+     * key is to be produced.
+     *
+     * @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.
+     *
+     * @see CipherKeyEntry#getKeyEntry(CryptoManager, String, int)
+     */
+    public static CipherKeyEntry generateKeyEntry(
+            final CryptoManager cryptoManager,
+            final String transformation,
+            final int keyLengthBits)
+    throws CryptoManagerException {
+
+      final Map<KeyEntryID, CipherKeyEntry> map
+              = (null == cryptoManager)
+              ? null : cryptoManager.cipherKeyEntryCache;
+
+      CipherKeyEntry keyEntry = new CipherKeyEntry(transformation,
+              keyLengthBits);
+
+      // Validate the key entry.
+      final Cipher cipher
+              = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
+      final byte[] iv = cipher.getIV();
+      keyEntry.setIVLengthBits(
+              (null == iv) ? 0 : iv.length * Byte.SIZE);
+
+      if (null != map) {
+        map.put(keyEntry.getKeyID(), keyEntry);
+        // TODO: publish key in ADS. (mark key "blocked" in map
+        // until registered? OTOH, Key should be in local map prior to
+        // publication, since data could arrive from a remote OpenDS
+        // instance encrypted with the key any time after publication.
+        // OTOH, the key should be published in ADS before any use,
+        // since that is the persistent shared secret key repository.)
+      }
+
+      return keyEntry;
+    }
+
+
+    /**
+     * Initializes a secret key entry from the supplied parameters,
+     * validates it, and registers it in the supplied map. The
+     * anticipated use of this method is to import a key entry from
+     * ADS.
+     *
+     * @param cryptoManager  The CryptoManager instance.
+     *
+     * @param keyIDBytes  The key identifier.
+     *
+     * @param transformation  The cipher transformation for which the
+     * key entry was produced.
+     *
+     * @param keyAlgorithm  The cipher algorithm for which the key was
+     * produced.
+     *
+     * @param key  The cipher key.
+     *
+     * @param ivLengthBits  The length of the initialization vector,
+     * which will be zero in the case of any stream cipher algorithm,
+     * any block cipher algorithm for which the transformation mode
+     * does not use an initialization vector, and any HMAC algorithm.
+     *
+     * @param isCompromised  Mark the key as compromised, so that it
+     * will not subsequently be used for encryption. The key entry
+     * must be maintained in order to decrypt existing ciphertext.
+     *
+     * @return  The key entry, if one was successfully produced.
+     *
+     * @throws CryptoManagerException  In case of an error in the
+     * parameters used to initialize or validate the key entry.
+     */
+    public static CipherKeyEntry importCipherKeyEntry(
+            final CryptoManager cryptoManager,
+            final byte[] keyIDBytes,
+            final String transformation,
+            final String keyAlgorithm,
+            final byte[] key,
+            final int ivLengthBits,
+            final boolean isCompromised)
+            throws CryptoManagerException {
+      Validator.ensureNotNull(keyIDBytes, transformation,
+              keyAlgorithm, key);
+      Validator.ensureTrue(0 <= ivLengthBits);
+
+      final KeyEntryID keyID = new KeyEntryID(keyIDBytes);
+
+      // Check map for existing key entry with the supplied keyID.
+      CipherKeyEntry keyEntry = getKeyEntry(cryptoManager, keyID);
+      if (null != keyEntry) {
+        // TODO: compare keyEntry with supplied parameters to ensure
+        // equal.
+        return keyEntry;
+      }
+
+      // Instantiate new entry.
+      keyEntry = new CipherKeyEntry(keyID, transformation,
+              keyAlgorithm, key, ivLengthBits, isCompromised);
+
+      // Validate new entry.
+      byte[] iv = null;
+      if (0 < ivLengthBits) {
+        iv = new byte[ivLengthBits * Byte.SIZE];
+        pseudoRandom.nextBytes(iv);
+      }
+      getCipher(keyEntry, Cipher.DECRYPT_MODE, iv);
+
+      // Cache new entry.
+      cryptoManager.cipherKeyEntryCache.put(keyEntry.getKeyID(),
+              keyEntry);
+
+      return keyEntry;
+    }
+
+
+    /**
+     * Retrieve a CipherKeyEntry from the CipherKeyEntry Map based on
+     * the algorithm name and key length.
+     *
+     * @param cryptoManager  The CryptoManager instance with which the
+     * key entry is associated.
+     *
+     * @param transformation  The cipher transformation for which the
+     * key was produced.
+     *
+     * @param keyLengthBits  The cipher key length in bits.
+     *
+     * @return  The key entry corresponding to the parameters, or null
+     * if no such entry exists.
+     */
+    public static CipherKeyEntry getKeyEntry(
+            final CryptoManager cryptoManager,
+            final String transformation,
+            final int keyLengthBits) {
+      Validator.ensureNotNull(cryptoManager, transformation);
+      Validator.ensureTrue(0 < keyLengthBits);
+
+
+      CipherKeyEntry keyEntry = null;
+      // search for an existing key that satisfies the request
+      for (Map.Entry<KeyEntryID, CipherKeyEntry> i
+              : cryptoManager.cipherKeyEntryCache.entrySet()) {
+        CipherKeyEntry entry = i.getValue();
+        if (! entry.isCompromised()
+                && entry.getType().equals(transformation)
+                && entry.getKeyLengthBits() == keyLengthBits) {
+          keyEntry = entry;
+          break;
+        }
+      }
+
+      // TODO: if (null == keyEntry) Does ADS monitoring thread keep
+      // map updated with keys produced at other sites? Otherwise,
+      // search ADS for suitable key.
+
+      // TODO: if (null == keyEntry) consider generating key here.
+
+      return keyEntry;
+    }
+
+
+    /**
+     * Given a key identifier, return the associated cipher key entry
+     * from the supplied map. This method would typically be used by
+     * a decryption routine.
+     *
+     * @param cryptoManager  The CryptoManager instance with which the
+     * key entry is associated.
+     *
+     * @param keyID  The key identifier.
+     *
+     * @return  The key entry associated with the key identifier.
+     */
+    public static CipherKeyEntry getKeyEntry(
+            CryptoManager cryptoManager,
+            final KeyEntryID keyID) {
+      return cryptoManager.cipherKeyEntryCache.get(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). */
+    }
+
+    /**
+     In case a transformation is supplied instead of an algorithm:
+     E.g., AES/CBC/PKCS5Padding -> AES.
+
+     @param transformation The cipher transformation from which to
+     extract the cipher algorithm.
+
+     @return  The algorithm prefix of the Cipher transformation. If
+     the transformation is supplied as an algorithm-only (no mode or
+     padding), return the transformation as-is.
+     */
+    private static String keyAlgorithmFromTransformation(
+            String transformation){
+    final int separatorIndex = transformation.indexOf('/');
+      return (0 < separatorIndex)
+              ? transformation.substring(0, separatorIndex)
+              : transformation;
+    }
+
+    /**
+     * Construct an instance of {@code CipherKeyEntry} using the
+     * specified parameters. This constructor would typically be used
+     * for key generation.
+     *
+     * @param transformation  The name of the Cipher transformation
+     * for which the key entry is to be produced.
+     *
+     * @param keyLengthBits  The length of the requested key in bits.
+     *
+     * @throws CryptoManagerException If there is a problem
+     * instantiating the key generator.
+     */
+    private CipherKeyEntry(final String transformation,
+                           final int keyLengthBits)
+            throws CryptoManagerException {
+      // Generate a new key.
+      super(keyAlgorithmFromTransformation(transformation),
+              keyLengthBits);
+
+      // copy arguments.
+      this.fType = new String(transformation);
+      this.fIVLengthBits = -1; /* compute IV length */
+    }
+
+    /**
+     * Construct an instance of CipherKeyEntry using the specified
+     * parameters. This constructor would typically be used for key
+     * entries imported from ADS, for which the full set of paramters
+     * is known, and for a newly generated key entry, for which the
+     * initialization vector length might not yet be known, but which
+     * must be set prior to using the key.
+     *
+     * @param keyID  The unique identifier of this cipher
+     * transformation/key pair.
+     *
+     * @param transformation  The name of the secret-key cipher
+     * transformation for which the key entry is to be produced.
+     *
+     * @param keyAlgorithm  The name of the secret key cipher
+     * algorithm for which the key was produced.
+     *
+     * @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).
+     *
+     * @param isCompromised {@code false} if the key may be used
+     * for encryption, or {@code true} if the key is being retained
+     * only for use in decrypting existing data.
+     *
+     * @throws  CryptoManagerException If there is a problem
+     * instantiating a Cipher object in order to validate the supplied
+     * parameters when creating a new entry.
+     */
+    private CipherKeyEntry(final KeyEntryID keyID,
+                           final String transformation,
+                           final String keyAlgorithm,
+                           final byte[] key,
+                           final int ivLengthBits,
+                           final boolean isCompromised)
+            throws CryptoManagerException {
+      super(keyID, keyAlgorithm, key, isCompromised);
+
+      // copy arguments
+      this.fType = new String(transformation);
+      this.fIVLengthBits = ivLengthBits;
+    }
+
+
+    /**
+     * The cipher transformation for which the key entry was created.
+     *
+     * @return The cipher transformation.
+     */
+    public String getType() {
+      return fType;
+    }
+
+    /**
+     * Set the algorithm/key pair's required initialization vector
+     * length in bits. Typically, this will be the cipher's block
+     * size, or 0 for a stream cipher or a block cipher mode that does
+     * not use an initialization vector (e.g., ECB).
+     *
+     * @param ivLengthBits The initiazliation vector length in bits.
+     */
+    private void setIVLengthBits(int ivLengthBits) {
+      Validator.ensureTrue(-1 == fIVLengthBits && 0 <= ivLengthBits);
+      fIVLengthBits = ivLengthBits;
+    }
+
+    /**
+     * 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.
+     * <p>
+     * This method returns -1 if the object initialization has not
+     * been completed.
+     *
+     * @return The initialization vector length.
+     */
+    public int getIVLengthBits() {
+      return fIVLengthBits;
+    }
+
+    // state
+    private final String fType;
+    private int fIVLengthBits = -1;
+  }
+
+
+
+  /**
+   * This class corresponds to the MAC key entry in ADS. It is
+   * used in the local cache of key entries that have been requested
+   * by CryptoManager clients.
+   */
+  private static class MacKeyEntry extends SecretKeyEntry
+  {
+    /**
+     * This method generates a key according to the key parameters,
+     * creates a key entry, and optionally registers it in the
+     * supplied CryptoManager context.
+     *
+     * @param  cryptoManager The CryptoManager instance for which the
+     * key is to be generated. Pass {@code null} as the argument to
+     * this parameter in order to validate a proposed MAC algorithm
+     * and key length, but not publish the key entry.
+     *
+     * @param algorithm  The MAC algorithm for which the
+     * key is to be produced. This argument is required.
+     *
+     * @param keyLengthBits  The MAC key length in bits. The argument
+     * must be a positive integer evenly divisible by the value
+     * Byte.SIZE.
+     *
+     * @return The key entry corresponding to the parameters.
+     *
+     * @throws CryptoManagerException If there is a problem
+     * instantiating a Mac object in order to validate the supplied
+     * parameters when creating a new entry.
+     *
+     * @see MacKeyEntry#getKeyEntry(CryptoManager, String, int)
+     */
+    public static MacKeyEntry generateKeyEntry(
+            final CryptoManager cryptoManager,
+            final String algorithm,
+            final int keyLengthBits)
+    throws CryptoManagerException {
+      Validator.ensureNotNull(algorithm);
+
+      final Map<KeyEntryID, MacKeyEntry> map = (null == cryptoManager)
+              ? null : cryptoManager.macKeyEntryCache;
+
+      final MacKeyEntry keyEntry = new MacKeyEntry(algorithm,
+              keyLengthBits);
+
+      // Validate the key entry.
+      getMacEngine(keyEntry);
+
+      if (null != map) {
+        map.put(keyEntry.getKeyID(), keyEntry);
+        // TODO: publish key in ADS. (mark key "blocked" in map
+        // until registered? OTOH, Key should be in local map prior to
+        // publication, since data could arrive from a remote OpenDS
+        // instance encrypted with the key any time after publication.
+        // OTOH, the key should be published in ADS before any use,
+        // since that is the persistent shared secret key repository.)
+      }
+
+      return keyEntry;
+    }
+
+
+    /**
+     * Initializes a secret key entry from the supplied parameters,
+     * validates it, and registers it in the supplied map. The
+     * anticipated use of this method is to import a key entry from
+     * ADS.
+     *
+     * @param cryptoManager  The CryptoManager instance.
+     *
+     * @param keyIDString  The key identifier.
+     *
+     * @param algorithm  The algorithm for which the key entry was
+     * produced.
+     *
+     * @param key  The cipher key.
+     *
+     * @param isCompromised  Mark the key as compromised, so that it
+     * will not subsequently be used for new data. The key entry
+     * must be maintained in order to verify existing signatures.
+     *
+     * @return  The key entry, if one was successfully produced.
+     *
+     * @throws CryptoManagerException  In case of an error in the
+     * parameters used to initialize or validate the key entry.
+     */
+    public static MacKeyEntry importMacKeyEntry(
+            final CryptoManager cryptoManager,
+            final String keyIDString,
+            final String algorithm,
+            final byte[] key,
+            final boolean isCompromised)
+            throws CryptoManagerException {
+      Validator.ensureNotNull(keyIDString, algorithm, key);
+
+      final KeyEntryID keyID = new KeyEntryID(keyIDString);
+
+      // Check map for existing key entry with the supplied keyID.
+      MacKeyEntry keyEntry = getKeyEntry(cryptoManager, keyID);
+      if (null != keyEntry) {
+        // TODO: compare keyEntry with supplied parameters to ensure
+        // equal.
+        return keyEntry;
+      }
+
+      // Instantiate new entry.
+      keyEntry = new MacKeyEntry(keyID, algorithm, key,
+              isCompromised);
+
+      // Validate new entry.
+      getMacEngine(keyEntry);
+
+      // Cache new entry.
+      cryptoManager.macKeyEntryCache.put(keyEntry.getKeyID(),
+              keyEntry);
+
+      return keyEntry;
+    }
+
+
+    /**
+     * Retrieve a MacKeyEntry from the MacKeyEntry Map based on
+     * the algorithm name and key length.
+     *
+     * @param cryptoManager  The CryptoManager instance with which the
+     * key entry is associated.
+     *
+     * @param algorithm  The MAC algorithm for which the key was
+     * produced.
+     *
+     * @param keyLengthBits  The MAC key length in bits.
+     *
+     * @return  The key entry corresponding to the parameters, or null
+     * if no such entry exists.
+     */
+    public static MacKeyEntry getKeyEntry(
+            final CryptoManager cryptoManager,
+            final String algorithm,
+            final int keyLengthBits) {
+      Validator.ensureNotNull(cryptoManager, algorithm);
+      Validator.ensureTrue(0 < keyLengthBits);
+
+      MacKeyEntry keyEntry = null;
+      // search for an existing key that satisfies the request
+      for (Map.Entry<KeyEntryID, MacKeyEntry> i
+              : cryptoManager.macKeyEntryCache.entrySet()) {
+        MacKeyEntry entry = i.getValue();
+        if (! entry.isCompromised()
+                && entry.getType().equals(algorithm)
+                && entry.getKeyLengthBits() == keyLengthBits) {
+          keyEntry = entry;
+          break;
+        }
+      }
+
+      // TODO: if (null == keyEntry) Does ADS monitoring thread keep
+      // map updated with keys produced at other sites? Otherwise,
+      // search ADS for suitable key.
+
+      // TODO: if (null == keyEntry) consider generating key here.
+
+      return keyEntry;
+    }
+
+
+    /**
+     * Given a key identifier, return the associated cipher key entry
+     * from the supplied map. This method would typically be used by
+     * a decryption routine.
+     *
+     * @param cryptoManager  The CryptoManager instance with which the
+     * key entry is associated.
+     *
+     * @param keyID  The key identifier.
+     *
+     * @return  The key entry associated with the key identifier.
+     */
+    public static MacKeyEntry getKeyEntry(
+            final CryptoManager cryptoManager,
+            final KeyEntryID keyID) {
+      return cryptoManager.macKeyEntryCache.get(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 {@code MacKeyEntry} using the
+     * specified parameters. This constructor would typically be used
+     * for key generation.
+     *
+     * @param algorithm  The name of the MAC algorithm for which the
+     * key entry is to be produced.
+     *
+     * @param keyLengthBits  The length of the requested key in bits.
+     *
+     * @throws CryptoManagerException If there is a problem
+     * instantiating the key generator.
+     */
+    private MacKeyEntry(final String algorithm,
+                        final int keyLengthBits)
+            throws CryptoManagerException {
+      // Generate a new key.
+      super(algorithm, keyLengthBits);
+
+      // copy arguments
+      this.fType = new String(algorithm);
+    }
+
+    /**
+     * Construct an instance of MacKeyEntry using the specified
+     * parameters. This constructor would typically be used for key
+     * entries imported from ADS, for which the full set of paramters
+     * is known.
+     *
+     * @param keyID  The unique identifier of this MAC algorithm/key
+     * pair.
+     *
+     * @param algorithm  The name of the MAC algorithm for which the
+     * key entry is to be produced.
+     *
+     * @param key  The MAC key.
+     *
+     * @param isCompromised {@code false} if the key may be used
+     * for signing, or {@code true} if the key is being retained only
+     * for use in signature verification.
+     */
+    private MacKeyEntry(final KeyEntryID keyID,
+                        final String algorithm,
+                        final byte[] key,
+                        final boolean isCompromised) {
+      super(keyID, algorithm, key, isCompromised);
+
+      // copy arguments
+      this.fType = new String(algorithm);
+    }
+
+
+    /**
+     * The algorithm for which the key entry was created.
+     *
+     * @return The algorithm.
+     */
+    public String getType() {
+      return fType;
+    }
+
+
+    // state
+    private final String fType;
+  }
+
+
+
+  /**
    * This class defines an exception that is thrown in the case of
    * problems with encryption key managagment, and is a wrapper for a
    * variety of other cipher related exceptions.
diff --git a/opends/src/server/org/opends/server/util/ServerConstants.java b/opends/src/server/org/opends/server/util/ServerConstants.java
index cb9efc5..a202534 100644
--- a/opends/src/server/org/opends/server/util/ServerConstants.java
+++ b/opends/src/server/org/opends/server/util/ServerConstants.java
@@ -2235,10 +2235,11 @@
 
 
   /**
-   * The name of the backup property that holds the name of the MAC algorithm
-   * used to generate the signed hash of a backup.
+   * The name of the backup property that holds the identifer of the key entry
+   * that contains the MAC algorithm and shared secret key used to generate
+   * the signed hash of a backup.
    */
-  public static final String BACKUP_PROPERTY_MAC_ALGORITHM = "mac_algorithm";
+  public static final String BACKUP_PROPERTY_MAC_KEY_ID = "mac_key_id";
 
 
 
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/CryptoManagerTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/CryptoManagerTestCase.java
index 794970e..ef5b5d7 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/CryptoManagerTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/CryptoManagerTestCase.java
@@ -40,10 +40,16 @@
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Arrays;
 
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
+import org.testng.annotations.DataProvider;
+
+import javax.crypto.Mac;
 
 /**
  This class tests the CryptoManager.
@@ -69,30 +75,127 @@
 
   }
 
+
+  @Test
+  public void testMacSuccess()
+          throws Exception {
+    final CryptoManager cm = DirectoryServer.getCryptoManager();
+    final String text = "1234";
+
+    final String macKeyID = cm.getMacEngineKeyEntryID();
+
+    final Mac signingMac = cm.getMacEngine(macKeyID);
+    final byte[] signedHash = signingMac.doFinal(text.getBytes());
+
+    final Mac validatingMac = cm.getMacEngine(macKeyID);
+    final byte[] calculatedSignature = validatingMac.doFinal(text.getBytes());
+    
+    assertTrue(Arrays.equals(calculatedSignature, signedHash));
+  }
+
+
   /**
-   Tests a simple encryption-decryption cycle.
+   Cipher parameters
+   */
+  private class CipherParameters {
+    private final String fAlgorithm;
+    private final String fMode;
+    private final String fPadding;
+    private final int fKeyLength;
+    private final int fIVLength;
+
+    public CipherParameters(final String algorithm, final String mode,
+                            final String padding, final int keyLength,
+                            final int ivLength) {
+      fAlgorithm = algorithm;
+      fMode = mode;
+      fPadding = padding;
+      fKeyLength = keyLength;
+      fIVLength = ivLength;
+    }
+
+    public String getTransformation() {
+      if (null == fAlgorithm) return null; // default
+      return (null == fMode)
+              ? new String(fAlgorithm)
+              : (new StringBuilder(fAlgorithm)).append("/").append(fMode)
+                .append("/").append(fPadding).toString();
+    }
+
+    public int getKeyLength() {
+      return fKeyLength;
+    }
+
+    public int getIVLength() {
+      return fIVLength;
+    }
+  }
+
+
+  /**
+   Cipher parameter data set.
+
+   @return The set of Cipher parameters with which to test.
+   */
+  @DataProvider(name = "cipherParametersData")
+  public Object[][] cipherParametersData() {
+
+    List<CipherParameters> paramList = new LinkedList<CipherParameters>();
+    // default (preferred) AES/CBC/PKCS5Padding 128bit key.
+    paramList.add(new CipherParameters(null, null, null, 128, 128));
+    // custom
+    paramList.add(new CipherParameters("Blowfish", "CFB", "NoPadding", 128, 64));
+    paramList.add(new CipherParameters("RC4", null, null, 104, 0));
+    paramList.add(new CipherParameters("DES", "CFB", "NoPadding", 56, 56));
+    paramList.add(new CipherParameters("DESede", "ECB", "PKCS5Padding", 168, 56));
+
+
+    Object[][] cipherParameters = new Object[paramList.size()][1];
+    for (int i=0; i < paramList.size(); i++)
+    {
+      cipherParameters[i] = new Object[] { paramList.get(i) };
+    }
+
+    return cipherParameters;
+  }
+
+
+  /**
+   Tests a simple encryption-decryption cycle using the supplied cipher
+   parameters.
+
+   @param cp  Cipher parameters to use for this test iteration.
 
    @throws Exception If an exceptional condition arises.
    */
-  @Test
-  public void testEncryptDecryptSuccess() throws Exception {
+  @Test(dataProvider = "cipherParametersData")
+  public void testEncryptDecryptSuccess(CipherParameters cp)
+          throws Exception {
     final CryptoManager cm = DirectoryServer.getCryptoManager();
     final String secretMessage = "1234";
 
-    final byte[] cipherText = cm.encrypt(secretMessage.getBytes());
+    final byte[] cipherText = (null == cp.getTransformation())
+            ? cm.encrypt(secretMessage.getBytes()) // default
+            : cm.encrypt(cp.getTransformation(), cp.getKeyLength(),
+                         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.
+   Tests a simple cipher stream encryption-decryption cycle using the supplied
+   cipher parameters.
+
+   @param cp  Cipher parameters to use for this test iteration.
 
    @throws Exception If an exceptional condition arises.
    */
-  @Test
-  public void testCipherEncryptDecryptSuccess() throws Exception {
+  @Test(dataProvider = "cipherParametersData")
+  public void testStreamEncryptDecryptSuccess(CipherParameters cp)
+          throws Exception {
     final CryptoManager cm = DirectoryServer.getCryptoManager();
     final String secretMessage = "56789";
 
@@ -101,200 +204,9 @@
     tempFile.deleteOnExit();
 
     OutputStream os = new FileOutputStream(tempFile);
-    os = cm.getCipherOutputStream(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);
-  }
-
-  // TODO: other-than-preferred cipher algorithms, failure cases...
-  /**
-   Tests a simple encryption-decryption cycle.
-
-   @throws Exception If an exceptional condition arises.
-   */
-  @Test
-  public void testEncryptDecryptSuccessX() throws Exception {
-    final CryptoManager cm = DirectoryServer.getCryptoManager();
-    final String secretMessage = "1234";
-
-    final byte[] cipherText = cm.encrypt("Blowfish/CFB/NoPadding", 128,
-            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 testCipherEncryptDecryptSuccessX() 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("Blowfish/CFB/NoPadding", 128, 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);
-  }
-
-  /**
-   Tests a simple encryption-decryption cycle.
-
-   @throws Exception If an exceptional condition arises.
-   */
-  @Test
-  public void testEncryptDecryptSuccessY() throws Exception {
-    final CryptoManager cm = DirectoryServer.getCryptoManager();
-    final String secretMessage = "1234";
-
-    final byte[] cipherText = cm.encrypt("RC4", 104,
-            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 testCipherEncryptDecryptSuccessY() 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("RC4", 104, 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);
-  }
-
-  /**
-   Tests a simple encryption-decryption cycle.
-
-   @throws Exception If an exceptional condition arises.
-   */
-  @Test
-  public void testEncryptDecryptSuccessZ() throws Exception {
-    final CryptoManager cm = DirectoryServer.getCryptoManager();
-    final String secretMessage = "1234";
-
-    final byte[] cipherText = cm.encrypt("DES/CFB/NoPadding", 56,
-            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 testCipherEncryptDecryptSuccessZ() 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/CFB/NoPadding", 56, 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);
-  }
-
-  /**
-   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("DESede/ECB/PKCS5Padding", 168,
-            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("DESede/ECB/PKCS5Padding", 168, os);
+    os = (null == cp.getTransformation())
+            ? cm.getCipherOutputStream(os) // default
+            : cm.getCipherOutputStream(cp.getTransformation(), cp.getKeyLength(), os);
     os.write(secretMessage.getBytes());
     os.close();
 

--
Gitblit v1.10.0