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

Valery Kharseko
2 days ago 1bda529e3cf685d5108832ecac7f433c85acb6fd
Remove global digestLock serialization in digest password storage schemes (#667)
9 files modified
1362 ■■■■ changed files
opendj-server-legacy/src/main/java/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java 90 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/MD5PasswordStorageScheme.java 134 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/SHA1PasswordStorageScheme.java 130 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedMD5PasswordStorageScheme.java 194 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA1PasswordStorageScheme.java 195 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA256PasswordStorageScheme.java 195 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA384PasswordStorageScheme.java 195 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA512PasswordStorageScheme.java 196 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/util/Crypt.java 33 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.extensions;
@@ -81,11 +82,13 @@
  /** The identity mapper that will be used to map ID strings to user entries. */
  private IdentityMapper<?> identityMapper;
  /** The message digest engine that will be used to create the MD5 digests. */
  private MessageDigest md5Digest;
  /** The lock that will be used to provide threadsafe access to the message digest. */
  private Object digestLock;
  /**
   * The message digest engines that will be used to create the MD5 digests.
   * MessageDigest is not thread-safe, so a per-thread instance is used
   * instead of a shared instance guarded by a lock: hashing under a global
   * lock serializes all concurrent CRAM-MD5 binds.
   */
  private ThreadLocal<MessageDigest> md5Digest;
  /** The random number generator that we will use to create the server challenge. */
  private SecureRandom randomGenerator;
@@ -109,12 +112,12 @@
    currentConfig = configuration;
    // Initialize the variables needed for the MD5 digest creation.
    digestLock      = new Object();
    randomGenerator = new SecureRandom();
    try
    {
      md5Digest = MessageDigest.getInstance("MD5");
      // Fail fast at initialization time if the algorithm is unavailable.
      MessageDigest.getInstance("MD5");
    }
    catch (Exception e)
    {
@@ -125,6 +128,17 @@
      throw new InitializationException(message, e);
    }
    md5Digest = ThreadLocal.withInitial(() -> {
      try
      {
        return MessageDigest.getInstance("MD5");
      }
      catch (Exception e)
      {
        throw new IllegalStateException(e);
      }
    });
    // Create and fill the iPad and oPad arrays.
    iPad = new byte[HMAC_MD5_BLOCK_LENGTH];
    oPad = new byte[HMAC_MD5_BLOCK_LENGTH];
@@ -427,40 +441,38 @@
    byte[] p = password.toByteArray();
    byte[] c = challenge.toByteArray();
    // Grab a lock to protect the MD5 digest generation.
    synchronized (digestLock)
    MessageDigest md5Digest = this.md5Digest.get();
    // If the password is longer than the HMAC-MD5 block length, then use an
    // MD5 digest of the password rather than the password itself.
    if (p.length > HMAC_MD5_BLOCK_LENGTH)
    {
      // If the password is longer than the HMAC-MD5 block length, then use an
      // MD5 digest of the password rather than the password itself.
      if (p.length > HMAC_MD5_BLOCK_LENGTH)
      {
        p = md5Digest.digest(p);
      }
      // Create byte arrays with data needed for the hash generation.
      byte[] iPadAndData = new byte[HMAC_MD5_BLOCK_LENGTH + c.length];
      System.arraycopy(iPad, 0, iPadAndData, 0, HMAC_MD5_BLOCK_LENGTH);
      System.arraycopy(c, 0, iPadAndData, HMAC_MD5_BLOCK_LENGTH, c.length);
      byte[] oPadAndHash = new byte[HMAC_MD5_BLOCK_LENGTH + MD5_DIGEST_LENGTH];
      System.arraycopy(oPad, 0, oPadAndHash, 0, HMAC_MD5_BLOCK_LENGTH);
      // Iterate through the bytes in the key and XOR them with the iPad and
      // oPad as appropriate.
      for (int i=0; i < p.length; i++)
      {
        iPadAndData[i] ^= p[i];
        oPadAndHash[i] ^= p[i];
      }
      // Copy an MD5 digest of the iPad-XORed key and the data into the array to
      // be hashed.
      System.arraycopy(md5Digest.digest(iPadAndData), 0, oPadAndHash,
                       HMAC_MD5_BLOCK_LENGTH, MD5_DIGEST_LENGTH);
      // Return an MD5 digest of the resulting array.
      return md5Digest.digest(oPadAndHash);
      p = md5Digest.digest(p);
    }
    // Create byte arrays with data needed for the hash generation.
    byte[] iPadAndData = new byte[HMAC_MD5_BLOCK_LENGTH + c.length];
    System.arraycopy(iPad, 0, iPadAndData, 0, HMAC_MD5_BLOCK_LENGTH);
    System.arraycopy(c, 0, iPadAndData, HMAC_MD5_BLOCK_LENGTH, c.length);
    byte[] oPadAndHash = new byte[HMAC_MD5_BLOCK_LENGTH + MD5_DIGEST_LENGTH];
    System.arraycopy(oPad, 0, oPadAndHash, 0, HMAC_MD5_BLOCK_LENGTH);
    // Iterate through the bytes in the key and XOR them with the iPad and
    // oPad as appropriate.
    for (int i=0; i < p.length; i++)
    {
      iPadAndData[i] ^= p[i];
      oPadAndHash[i] ^= p[i];
    }
    // Copy an MD5 digest of the iPad-XORed key and the data into the array to
    // be hashed.
    System.arraycopy(md5Digest.digest(iPadAndData), 0, oPadAndHash,
                     HMAC_MD5_BLOCK_LENGTH, MD5_DIGEST_LENGTH);
    // Return an MD5 digest of the resulting array.
    return md5Digest.digest(oPadAndHash);
  }
  @Override
opendj-server-legacy/src/main/java/org/opends/server/extensions/MD5PasswordStorageScheme.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2008 Sun Microsystems, Inc.
 * Portions Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.extensions;
@@ -53,11 +54,13 @@
  private static final String CLASS_NAME =
       "org.opends.server.extensions.MD5PasswordStorageScheme";
  /** The message digest that will actually be used to generate the MD5 hashes. */
  private MessageDigest messageDigest;
  /** The lock used to provide threadsafe access to the message digest. */
  private Object digestLock;
  /**
   * The message digests used to generate the MD5 hashes.
   * MessageDigest is not thread-safe, so a per-thread instance is used
   * instead of a shared instance guarded by a lock: hashing under a global
   * lock serializes all concurrent bind password verifications.
   */
  private ThreadLocal<MessageDigest> messageDigest;
  /**
   * Creates a new instance of this password storage scheme.  Note that no
@@ -76,7 +79,8 @@
  {
    try
    {
      messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_MD5);
      // Fail fast at initialization time if the algorithm is unavailable.
      MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_MD5);
    }
    catch (Exception e)
    {
@@ -87,7 +91,16 @@
      throw new InitializationException(message, e);
    }
    digestLock = new Object();
    messageDigest = ThreadLocal.withInitial(() -> {
      try
      {
        return MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_MD5);
      }
      catch (Exception e)
      {
        throw new IllegalStateException(e);
      }
    });
  }
  @Override
@@ -103,29 +116,26 @@
    byte[] digestBytes;
    byte[] plaintextBytes = null;
    synchronized (digestLock)
    try
    {
      try
      {
        // TODO: Can we avoid this copy?
        plaintextBytes = plaintext.toByteArray();
        digestBytes = messageDigest.digest(plaintextBytes);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // TODO: Can we avoid this copy?
      plaintextBytes = plaintext.toByteArray();
      digestBytes = messageDigest.get().digest(plaintextBytes);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      if (plaintextBytes != null)
      {
        if (plaintextBytes != null)
        {
          Arrays.fill(plaintextBytes, (byte) 0);
        }
        Arrays.fill(plaintextBytes, (byte) 0);
      }
    }
@@ -144,29 +154,26 @@
    byte[] plaintextBytes = null;
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // TODO: Can we avoid this copy?
        plaintextBytes = plaintext.toByteArray();
        digestBytes = messageDigest.digest(plaintextBytes);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // TODO: Can we avoid this copy?
      plaintextBytes = plaintext.toByteArray();
      digestBytes = messageDigest.get().digest(plaintextBytes);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      if (plaintextBytes != null)
      {
        if (plaintextBytes != null)
        {
          Arrays.fill(plaintextBytes, (byte) 0);
        }
        Arrays.fill(plaintextBytes, (byte) 0);
      }
    }
@@ -182,27 +189,24 @@
    byte[] plaintextPasswordBytes = null;
    ByteString userPWDigestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // TODO: Can we avoid this copy?
        plaintextPasswordBytes = plaintextPassword.toByteArray();
        userPWDigestBytes =
            ByteString.wrap(messageDigest.digest(plaintextPasswordBytes));
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // TODO: Can we avoid this copy?
      plaintextPasswordBytes = plaintextPassword.toByteArray();
      userPWDigestBytes =
          ByteString.wrap(messageDigest.get().digest(plaintextPasswordBytes));
    }
    catch (Exception e)
    {
      logger.traceException(e);
        return false;
      }
      finally
      return false;
    }
    finally
    {
      if (plaintextPasswordBytes != null)
      {
        if (plaintextPasswordBytes != null)
        {
          Arrays.fill(plaintextPasswordBytes, (byte) 0);
        }
        Arrays.fill(plaintextPasswordBytes, (byte) 0);
      }
    }
opendj-server-legacy/src/main/java/org/opends/server/extensions/SHA1PasswordStorageScheme.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2008 Sun Microsystems, Inc.
 * Portions Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.extensions;
@@ -53,11 +54,13 @@
  private static final String CLASS_NAME =
       "org.opends.server.extensions.SHA1PasswordStorageScheme";
  /** The message digest that will actually be used to generate the SHA-1 hashes. */
  private MessageDigest messageDigest;
  /** The lock used to provide threadsafe access to the message digest. */
  private Object digestLock;
  /**
   * The message digests used to generate the SHA-1 hashes.
   * MessageDigest is not thread-safe, so a per-thread instance is used
   * instead of a shared instance guarded by a lock: hashing under a global
   * lock serializes all concurrent bind password verifications.
   */
  private ThreadLocal<MessageDigest> messageDigest;
  /**
   * Creates a new instance of this password storage scheme.  Note that no
@@ -76,7 +79,8 @@
  {
    try
    {
      messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_1);
      // Fail fast at initialization time if the algorithm is unavailable.
      MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_1);
    }
    catch (Exception e)
    {
@@ -87,7 +91,16 @@
      throw new InitializationException(message, e);
    }
    digestLock = new Object();
    messageDigest = ThreadLocal.withInitial(() -> {
      try
      {
        return MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_1);
      }
      catch (Exception e)
      {
        throw new IllegalStateException(e);
      }
    });
  }
  @Override
@@ -103,29 +116,26 @@
    byte[] digestBytes;
    byte[] plaintextBytes = null;
    synchronized (digestLock)
    try
    {
      try
      {
        // TODO: Can we avoid this copy?
        plaintextBytes = plaintext.toByteArray();
        digestBytes = messageDigest.digest(plaintextBytes);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // TODO: Can we avoid this copy?
      plaintextBytes = plaintext.toByteArray();
      digestBytes = messageDigest.get().digest(plaintextBytes);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      if (plaintextBytes != null)
      {
        if (plaintextBytes != null)
        {
          Arrays.fill(plaintextBytes, (byte) 0);
        }
        Arrays.fill(plaintextBytes, (byte) 0);
      }
    }
@@ -145,28 +155,25 @@
    byte[] plaintextBytes = null;
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        plaintextBytes = plaintext.toByteArray();
        digestBytes = messageDigest.digest(plaintextBytes);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      plaintextBytes = plaintext.toByteArray();
      digestBytes = messageDigest.get().digest(plaintextBytes);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      if (plaintextBytes != null)
      {
        if (plaintextBytes != null)
        {
          Arrays.fill(plaintextBytes, (byte) 0);
        }
        Arrays.fill(plaintextBytes, (byte) 0);
      }
    }
@@ -183,26 +190,23 @@
    byte[] plaintextPasswordBytes = null;
    ByteString userPWDigestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        plaintextPasswordBytes = plaintextPassword.toByteArray();
        userPWDigestBytes =
            ByteString.wrap(messageDigest.digest(plaintextPasswordBytes));
      }
      catch (Exception e)
      {
        logger.traceException(e);
      plaintextPasswordBytes = plaintextPassword.toByteArray();
      userPWDigestBytes =
          ByteString.wrap(messageDigest.get().digest(plaintextPasswordBytes));
    }
    catch (Exception e)
    {
      logger.traceException(e);
        return false;
      }
      finally
      return false;
    }
    finally
    {
      if (plaintextPasswordBytes != null)
      {
        if (plaintextPasswordBytes != null)
        {
          Arrays.fill(plaintextPasswordBytes, (byte) 0);
        }
        Arrays.fill(plaintextPasswordBytes, (byte) 0);
      }
    }
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedMD5PasswordStorageScheme.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2008 Sun Microsystems, Inc.
 * Portions Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.extensions;
@@ -62,11 +63,13 @@
  /** The number of bytes MD5 algorithm produces. */
  private static final int MD5_LENGTH = 16;
  /** The message digest that will actually be used to generate the MD5 hashes. */
  private MessageDigest messageDigest;
  /** The lock used to provide threadsafe access to the message digest. */
  private Object digestLock;
  /**
   * The message digests used to generate the MD5 hashes.
   * MessageDigest is not thread-safe, so a per-thread instance is used
   * instead of a shared instance guarded by a lock: hashing under a global
   * lock serializes all concurrent bind password verifications.
   */
  private ThreadLocal<MessageDigest> messageDigest;
  /** The secure random number generator to use to generate the salt values. */
  private Random random;
@@ -88,7 +91,8 @@
  {
    try
    {
      messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_MD5);
      // Fail fast at initialization time if the algorithm is unavailable.
      MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_MD5);
    }
    catch (Exception e)
    {
@@ -98,8 +102,17 @@
      throw new InitializationException(message, e);
    }
    digestLock = new Object();
    random     = new Random();
    messageDigest = ThreadLocal.withInitial(() -> {
      try
      {
        return MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_MD5);
      }
      catch (Exception e)
      {
        throw new IllegalStateException(e);
      }
    });
    random = new Random();
  }
  @Override
@@ -120,31 +133,28 @@
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
        random.nextBytes(saltBytes);
        System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                         NUM_SALT_BYTES);
      // Generate the salt and put in the plain+salt array.
      random.nextBytes(saltBytes);
      System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                       NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // Create the hash from the concatenated value.
      digestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    // Append the salt to the hashed value and base64-the whole thing.
@@ -174,31 +184,28 @@
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
        random.nextBytes(saltBytes);
        System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                         NUM_SALT_BYTES);
      // Generate the salt and put in the plain+salt array.
      random.nextBytes(saltBytes);
      System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                       NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // Create the hash from the concatenated value.
      digestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    // Append the salt to the hashed value and base64-the whole thing.
@@ -251,22 +258,19 @@
    byte[] userDigestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        userDigestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      userDigestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        return false;
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      return false;
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    return Arrays.equals(digestBytes, userDigestBytes);
@@ -297,31 +301,28 @@
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
        random.nextBytes(saltBytes);
        System.arraycopy(saltBytes,0, plainPlusSalt, plaintextLength,
                         NUM_SALT_BYTES);
      // Generate the salt and put in the plain+salt array.
      random.nextBytes(saltBytes);
      System.arraycopy(saltBytes,0, plainPlusSalt, plaintextLength,
                       NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // Create the hash from the concatenated value.
      digestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    // Encode and return the value.
@@ -359,17 +360,14 @@
    System.arraycopy(saltBytes, 0, plainPlusSaltBytes, plainBytesLength,
                     saltBytes.length);
    synchronized (digestLock)
    try
    {
      try
      {
        return Arrays.equals(digestBytes,
                                messageDigest.digest(plainPlusSaltBytes));
      }
      finally
      {
        Arrays.fill(plainPlusSaltBytes, (byte) 0);
      }
      return Arrays.equals(digestBytes,
                              messageDigest.get().digest(plainPlusSaltBytes));
    }
    finally
    {
      Arrays.fill(plainPlusSaltBytes, (byte) 0);
    }
  }
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA1PasswordStorageScheme.java
@@ -13,7 +13,7 @@
 *
 * Copyright 2006-2010 Sun Microsystems, Inc.
 * Portions Copyright 2010-2016 ForgeRock AS.
 * Portions Copyrighted 2026 3A Systems, LLC.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.extensions;
@@ -70,11 +70,13 @@
  /** The number of bytes SHA algorithm produces. */
  private static final int SHA1_LENGTH = 20;
  /** The message digest that will actually be used to generate the SHA-1 hashes. */
  private MessageDigest messageDigest;
  /** The lock used to provide threadsafe access to the message digest. */
  private Object digestLock;
  /**
   * The message digests used to generate the SHA-1 hashes.
   * MessageDigest is not thread-safe, so a per-thread instance is used
   * instead of a shared instance guarded by a lock: hashing under a global
   * lock serializes all concurrent bind password verifications.
   */
  private ThreadLocal<MessageDigest> messageDigest;
  /** The secure random number generator to use to generate the salt values. */
  private SecureRandom random;
@@ -96,7 +98,8 @@
  {
    try
    {
      messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_1);
      // Fail fast at initialization time if the algorithm is unavailable.
      MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_1);
    }
    catch (Exception e)
    {
@@ -106,8 +109,17 @@
      throw new InitializationException(message, e);
    }
    digestLock = new Object();
    random     = new SecureRandom();
    messageDigest = ThreadLocal.withInitial(() -> {
      try
      {
        return MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_1);
      }
      catch (Exception e)
      {
        throw new IllegalStateException(e);
      }
    });
    random = new SecureRandom();
  }
  @Override
@@ -128,31 +140,28 @@
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
        random.nextBytes(saltBytes);
        System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                         NUM_SALT_BYTES);
      // Generate the salt and put in the plain+salt array.
      random.nextBytes(saltBytes);
      System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                       NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // Create the hash from the concatenated value.
      digestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    // Append the salt to the hashed value and base64-the whole thing.
@@ -182,31 +191,28 @@
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
        random.nextBytes(saltBytes);
        System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                         NUM_SALT_BYTES);
      // Generate the salt and put in the plain+salt array.
      random.nextBytes(saltBytes);
      System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                       NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // Create the hash from the concatenated value.
      digestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    // Append the salt to the hashed value and base64-the whole thing.
@@ -259,22 +265,19 @@
    byte[] userDigestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        userDigestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      userDigestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        return false;
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      return false;
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    return Arrays.equals(digestBytes, userDigestBytes);
@@ -305,31 +308,28 @@
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
        random.nextBytes(saltBytes);
        System.arraycopy(saltBytes,0, plainPlusSalt, plaintextLength,
                         NUM_SALT_BYTES);
      // Generate the salt and put in the plain+salt array.
      random.nextBytes(saltBytes);
      System.arraycopy(saltBytes,0, plainPlusSalt, plaintextLength,
                       NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // Create the hash from the concatenated value.
      digestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    // Encode and return the value.
@@ -367,17 +367,14 @@
    System.arraycopy(saltBytes, 0, plainPlusSaltBytes, plainBytesLength,
                     saltBytes.length);
    synchronized (digestLock)
    try
    {
      try
      {
        return Arrays.equals(digestBytes,
                messageDigest.digest(plainPlusSaltBytes));
      }
      finally
      {
        Arrays.fill(plainPlusSaltBytes, (byte) 0);
      }
      return Arrays.equals(digestBytes,
              messageDigest.get().digest(plainPlusSaltBytes));
    }
    finally
    {
      Arrays.fill(plainPlusSaltBytes, (byte) 0);
    }
  }
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA256PasswordStorageScheme.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2008 Sun Microsystems, Inc.
 * Portions Copyright 2010-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.extensions;
@@ -62,11 +63,13 @@
  /** Size of the dgiest in bytes. */
  private static final int SHA256_LENGTH = 256 / 8;
  /** The message digest that will actually be used to generate the 256-bit SHA-2 hashes. */
  private MessageDigest messageDigest;
  /** The lock used to provide threadsafe access to the message digest. */
  private Object digestLock;
  /**
   * The message digests used to generate the 256-bit SHA-2 hashes.
   * MessageDigest is not thread-safe, so a per-thread instance is used
   * instead of a shared instance guarded by a lock: hashing under a global
   * lock serializes all concurrent bind password verifications.
   */
  private ThreadLocal<MessageDigest> messageDigest;
  /** The secure random number generator to use to generate the salt values. */
  private Random random;
@@ -88,8 +91,8 @@
  {
    try
    {
      messageDigest =
           MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_256);
      // Fail fast at initialization time if the algorithm is unavailable.
      MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_256);
    }
    catch (Exception e)
    {
@@ -100,8 +103,17 @@
      throw new InitializationException(message, e);
    }
    digestLock = new Object();
    random     = new Random();
    messageDigest = ThreadLocal.withInitial(() -> {
      try
      {
        return MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_256);
      }
      catch (Exception e)
      {
        throw new IllegalStateException(e);
      }
    });
    random = new Random();
  }
  @Override
@@ -122,31 +134,28 @@
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
        random.nextBytes(saltBytes);
        System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                         NUM_SALT_BYTES);
      // Generate the salt and put in the plain+salt array.
      random.nextBytes(saltBytes);
      System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                       NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // Create the hash from the concatenated value.
      digestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    // Append the salt to the hashed value and base64-the whole thing.
@@ -176,31 +185,28 @@
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
        random.nextBytes(saltBytes);
        System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                         NUM_SALT_BYTES);
      // Generate the salt and put in the plain+salt array.
      random.nextBytes(saltBytes);
      System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                       NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // Create the hash from the concatenated value.
      digestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    // Append the salt to the hashed value and base64-the whole thing.
@@ -255,22 +261,19 @@
    byte[] userDigestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        userDigestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      userDigestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        return false;
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      return false;
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    return Arrays.equals(digestBytes, userDigestBytes);
@@ -301,31 +304,28 @@
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
        random.nextBytes(saltBytes);
        System.arraycopy(saltBytes,0, plainPlusSalt, plaintextLength,
                         NUM_SALT_BYTES);
      // Generate the salt and put in the plain+salt array.
      random.nextBytes(saltBytes);
      System.arraycopy(saltBytes,0, plainPlusSalt, plaintextLength,
                       NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // Create the hash from the concatenated value.
      digestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    // Encode and return the value.
@@ -363,17 +363,14 @@
    System.arraycopy(saltBytes, 0, plainPlusSaltBytes, plainBytesLength,
                     saltBytes.length);
    synchronized (digestLock)
    try
    {
      try
      {
        return Arrays.equals(digestBytes,
                                  messageDigest.digest(plainPlusSaltBytes));
      }
      finally
      {
        Arrays.fill(plainPlusSaltBytes, (byte) 0);
      }
      return Arrays.equals(digestBytes,
                                messageDigest.get().digest(plainPlusSaltBytes));
    }
    finally
    {
      Arrays.fill(plainPlusSaltBytes, (byte) 0);
    }
  }
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA384PasswordStorageScheme.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2008 Sun Microsystems, Inc.
 * Portions Copyright 2010-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.extensions;
@@ -62,11 +63,13 @@
  /** The size of the digest in bytes. */
  private static final int SHA384_LENGTH = 384 / 8;
  /** The message digest that will actually be used to generate the 384-bit SHA-2 hashes. */
  private MessageDigest messageDigest;
  /** The lock used to provide threadsafe access to the message digest. */
  private Object digestLock;
  /**
   * The message digests used to generate the 384-bit SHA-2 hashes.
   * MessageDigest is not thread-safe, so a per-thread instance is used
   * instead of a shared instance guarded by a lock: hashing under a global
   * lock serializes all concurrent bind password verifications.
   */
  private ThreadLocal<MessageDigest> messageDigest;
  /** The secure random number generator to use to generate the salt values. */
  private Random random;
@@ -88,8 +91,8 @@
  {
    try
    {
      messageDigest =
           MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_384);
      // Fail fast at initialization time if the algorithm is unavailable.
      MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_384);
    }
    catch (Exception e)
    {
@@ -100,8 +103,17 @@
      throw new InitializationException(message, e);
    }
    digestLock = new Object();
    random     = new Random();
    messageDigest = ThreadLocal.withInitial(() -> {
      try
      {
        return MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_384);
      }
      catch (Exception e)
      {
        throw new IllegalStateException(e);
      }
    });
    random = new Random();
  }
  @Override
@@ -122,31 +134,28 @@
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
        random.nextBytes(saltBytes);
        System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                         NUM_SALT_BYTES);
      // Generate the salt and put in the plain+salt array.
      random.nextBytes(saltBytes);
      System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                       NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // Create the hash from the concatenated value.
      digestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    // Append the salt to the hashed value and base64-the whole thing.
@@ -176,31 +185,28 @@
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
        random.nextBytes(saltBytes);
        System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                         NUM_SALT_BYTES);
      // Generate the salt and put in the plain+salt array.
      random.nextBytes(saltBytes);
      System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                       NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // Create the hash from the concatenated value.
      digestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    // Append the salt to the hashed value and base64-the whole thing.
@@ -255,22 +261,19 @@
    byte[] userDigestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        userDigestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      userDigestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        return false;
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      return false;
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    return Arrays.equals(digestBytes, userDigestBytes);
@@ -301,31 +304,28 @@
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
        random.nextBytes(saltBytes);
        System.arraycopy(saltBytes,0, plainPlusSalt, plaintextLength,
                         NUM_SALT_BYTES);
      // Generate the salt and put in the plain+salt array.
      random.nextBytes(saltBytes);
      System.arraycopy(saltBytes,0, plainPlusSalt, plaintextLength,
                       NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // Create the hash from the concatenated value.
      digestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    // Encode and return the value.
@@ -363,17 +363,14 @@
    System.arraycopy(saltBytes, 0, plainPlusSaltBytes, plainBytesLength,
                     saltBytes.length);
    synchronized (digestLock)
    try
    {
      try
      {
        return Arrays.equals(digestBytes,
                                  messageDigest.digest(plainPlusSaltBytes));
      }
      finally
      {
        Arrays.fill(plainPlusSaltBytes, (byte) 0);
      }
      return Arrays.equals(digestBytes,
                                messageDigest.get().digest(plainPlusSaltBytes));
    }
    finally
    {
      Arrays.fill(plainPlusSaltBytes, (byte) 0);
    }
  }
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA512PasswordStorageScheme.java
@@ -13,7 +13,7 @@
 *
 * Copyright 2006-2008 Sun Microsystems, Inc.
 * Portions Copyright 2010-2016 ForgeRock AS.
 * Portions Copyrighted 2026 3A Systems, LLC.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.extensions;
@@ -70,11 +70,13 @@
  /** The size of the digest in bytes. */
  private static final int SHA512_LENGTH = 512 / 8;
  /** The message digest that will actually be used to generate the 512-bit SHA-2 hashes. */
  private MessageDigest messageDigest;
  /** The lock used to provide threadsafe access to the message digest. */
  private Object digestLock;
  /**
   * The message digests used to generate the 512-bit SHA-2 hashes.
   * MessageDigest is not thread-safe, so a per-thread instance is used
   * instead of a shared instance guarded by a lock: hashing under a global
   * lock serializes all concurrent bind password verifications.
   */
  private ThreadLocal<MessageDigest> messageDigest;
  /** The secure random number generator to use to generate the salt values. */
  private SecureRandom random;
@@ -96,8 +98,8 @@
  {
    try
    {
      messageDigest =
           MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_512);
      // Fail fast at initialization time if the algorithm is unavailable.
      MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_512);
    }
    catch (Exception e)
    {
@@ -108,8 +110,17 @@
      throw new InitializationException(message, e);
    }
    digestLock = new Object();
    random     = new SecureRandom();
    messageDigest = ThreadLocal.withInitial(() -> {
      try
      {
        return MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_512);
      }
      catch (Exception e)
      {
        throw new IllegalStateException(e);
      }
    });
    random = new SecureRandom();
  }
  @Override
@@ -130,31 +141,28 @@
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
        random.nextBytes(saltBytes);
        System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                         NUM_SALT_BYTES);
      // Generate the salt and put in the plain+salt array.
      random.nextBytes(saltBytes);
      System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                       NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // Create the hash from the concatenated value.
      digestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    // Append the salt to the hashed value and base64-the whole thing.
@@ -184,31 +192,28 @@
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
        random.nextBytes(saltBytes);
        System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                         NUM_SALT_BYTES);
      // Generate the salt and put in the plain+salt array.
      random.nextBytes(saltBytes);
      System.arraycopy(saltBytes,0, plainPlusSalt, plainBytesLength,
                       NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // Create the hash from the concatenated value.
      digestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    // Append the salt to the hashed value and base64-the whole thing.
@@ -263,22 +268,19 @@
    byte[] userDigestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        userDigestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      userDigestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        return false;
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      return false;
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    return Arrays.equals(digestBytes, userDigestBytes);
@@ -309,31 +311,28 @@
    byte[] digestBytes;
    synchronized (digestLock)
    try
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
        random.nextBytes(saltBytes);
        System.arraycopy(saltBytes,0, plainPlusSalt, plaintextLength,
                         NUM_SALT_BYTES);
      // Generate the salt and put in the plain+salt array.
      random.nextBytes(saltBytes);
      System.arraycopy(saltBytes,0, plainPlusSalt, plaintextLength,
                       NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      // Create the hash from the concatenated value.
      digestBytes = messageDigest.get().digest(plainPlusSalt);
    }
    catch (Exception e)
    {
      logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                     message, e);
      }
      finally
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
          CLASS_NAME, getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      Arrays.fill(plainPlusSalt, (byte) 0);
    }
    // Encode and return the value.
@@ -371,17 +370,14 @@
    System.arraycopy(saltBytes, 0, plainPlusSaltBytes, plainBytesLength,
                     saltBytes.length);
    synchronized (digestLock)
    try
    {
      try
      {
        return Arrays.equals(digestBytes,
                                  messageDigest.digest(plainPlusSaltBytes));
      }
      finally
      {
        Arrays.fill(plainPlusSaltBytes, (byte) 0);
      }
      return Arrays.equals(digestBytes,
                                messageDigest.get().digest(plainPlusSaltBytes));
    }
    finally
    {
      Arrays.fill(plainPlusSaltBytes, (byte) 0);
    }
  }
opendj-server-legacy/src/main/java/org/opends/server/util/Crypt.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008 Sun Microsystems, Inc.
 * Portions Copyright 2015 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
@@ -136,18 +137,26 @@
    int _iobuf[]  = new int[16];
  }
  private final SubCrypt _crypt;
  /**
   * The working state of the algorithm. setkey(), encrypt() and _crypt() all
   * scribble on these buffers (and _crypt() returns a reference to _iobuf),
   * so a per-thread instance is used instead of a shared instance guarded by
   * a lock: encrypting under a global lock serializes all concurrent {CRYPT}
   * password operations.
   */
  private final ThreadLocal<SubCrypt> _crypt = ThreadLocal.withInitial(() -> {
    SubCrypt c = new SubCrypt();
    copy(e, c._E);
    return c;
  });
  /**
   * Constructor.
   */
  public Crypt() {
    _crypt = new SubCrypt();
    copy(e, _crypt._E);
  }
  private void copy(byte[] src, int[] dest) {
  private static void copy(byte[] src, int[] dest) {
    for (int i = 0; i < dest.length; i++) {
      dest[i] = src[i];
    }
@@ -158,7 +167,7 @@
   */
  private void setkey(int[] key)
  {
    SubCrypt _c = _crypt;
    SubCrypt _c = _crypt.get();
    /*
     * if (_c == null) { _cryptinit(); _c = __crypt; }
@@ -270,7 +279,7 @@
   */
  private final void encrypt(int block[], int edflag)
  {
    SubCrypt _c = _crypt;
    SubCrypt _c = _crypt.get();
    /*
     * First, permute the bits in the input
@@ -369,8 +378,6 @@
    }
  }
  private Object digestLock = new Object();
  /**
   * Encode the supplied password in unix crypt form with the provided
   * salt.
@@ -382,11 +389,7 @@
   */
  public byte[] crypt(byte[] pw, byte[] salt)
  {
    int[] r;
    synchronized (digestLock)
    {
      r = _crypt(pw, salt);
    }
    int[] r = _crypt(pw, salt);
    //TODO: crypt always returns same size array?  So don't mess
    // around calculating the number of zeros at the end.
@@ -416,7 +419,7 @@
  private int[] _crypt(byte[] pw, byte[] salt)
  {
    SubCrypt _c = _crypt;
    SubCrypt _c = _crypt.get();
    Arrays.fill(_c._ablock, 0);