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

Valery Kharseko
3 days ago 1bda529e3cf685d5108832ecac7f433c85acb6fd
Remove global digestLock serialization in digest password storage schemes (#667)
9 files modified
410 ■■■■ changed files
opendj-server-legacy/src/main/java/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java 34 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/MD5PasswordStorageScheme.java 42 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/SHA1PasswordStorageScheme.java 42 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedMD5PasswordStorageScheme.java 52 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA1PasswordStorageScheme.java 53 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA256PasswordStorageScheme.java 51 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA384PasswordStorageScheme.java 51 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA512PasswordStorageScheme.java 52 ●●●● 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,9 +441,8 @@
    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)
@@ -461,7 +474,6 @@
      // Return an MD5 digest of the resulting array.
      return md5Digest.digest(oPadAndHash);
    }
  }
  @Override
  public boolean isPasswordBased(String mechanism)
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,13 +116,11 @@
    byte[] digestBytes;
    byte[] plaintextBytes = null;
    synchronized (digestLock)
    {
      try
      {
        // TODO: Can we avoid this copy?
        plaintextBytes = plaintext.toByteArray();
        digestBytes = messageDigest.digest(plaintextBytes);
      digestBytes = messageDigest.get().digest(plaintextBytes);
      }
      catch (Exception e)
      {
@@ -127,7 +138,6 @@
          Arrays.fill(plaintextBytes, (byte) 0);
        }
      }
    }
    return ByteString.valueOfUtf8(Base64.encode(digestBytes));
  }
@@ -144,13 +154,11 @@
    byte[] plaintextBytes = null;
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        // TODO: Can we avoid this copy?
        plaintextBytes = plaintext.toByteArray();
        digestBytes = messageDigest.digest(plaintextBytes);
      digestBytes = messageDigest.get().digest(plaintextBytes);
      }
      catch (Exception e)
      {
@@ -168,7 +176,6 @@
          Arrays.fill(plaintextBytes, (byte) 0);
        }
      }
    }
    buffer.append(Base64.encode(digestBytes));
@@ -182,14 +189,12 @@
    byte[] plaintextPasswordBytes = null;
    ByteString userPWDigestBytes;
    synchronized (digestLock)
    {
      try
      {
        // TODO: Can we avoid this copy?
        plaintextPasswordBytes = plaintextPassword.toByteArray();
        userPWDigestBytes =
            ByteString.wrap(messageDigest.digest(plaintextPasswordBytes));
          ByteString.wrap(messageDigest.get().digest(plaintextPasswordBytes));
      }
      catch (Exception e)
      {
@@ -204,7 +209,6 @@
          Arrays.fill(plaintextPasswordBytes, (byte) 0);
        }
      }
    }
    ByteString storedPWDigestBytes;
    try
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,13 +116,11 @@
    byte[] digestBytes;
    byte[] plaintextBytes = null;
    synchronized (digestLock)
    {
      try
      {
        // TODO: Can we avoid this copy?
        plaintextBytes = plaintext.toByteArray();
        digestBytes = messageDigest.digest(plaintextBytes);
      digestBytes = messageDigest.get().digest(plaintextBytes);
      }
      catch (Exception e)
      {
@@ -127,7 +138,6 @@
          Arrays.fill(plaintextBytes, (byte) 0);
        }
      }
    }
    return ByteString.valueOfUtf8(Base64.encode(digestBytes));
  }
@@ -145,12 +155,10 @@
    byte[] plaintextBytes = null;
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        plaintextBytes = plaintext.toByteArray();
        digestBytes = messageDigest.digest(plaintextBytes);
      digestBytes = messageDigest.get().digest(plaintextBytes);
      }
      catch (Exception e)
      {
@@ -168,7 +176,6 @@
          Arrays.fill(plaintextBytes, (byte) 0);
        }
      }
    }
    buffer.append(Base64.encode(digestBytes));
@@ -183,13 +190,11 @@
    byte[] plaintextPasswordBytes = null;
    ByteString userPWDigestBytes;
    synchronized (digestLock)
    {
      try
      {
        plaintextPasswordBytes = plaintextPassword.toByteArray();
        userPWDigestBytes =
            ByteString.wrap(messageDigest.digest(plaintextPasswordBytes));
          ByteString.wrap(messageDigest.get().digest(plaintextPasswordBytes));
      }
      catch (Exception e)
      {
@@ -204,7 +209,6 @@
          Arrays.fill(plaintextPasswordBytes, (byte) 0);
        }
      }
    }
    ByteString storedPWDigestBytes;
    try
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,7 +102,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);
      }
    });
    random     = new Random();
  }
@@ -120,8 +133,6 @@
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
@@ -130,7 +141,7 @@
                         NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      digestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -145,7 +156,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    // Append the salt to the hashed value and base64-the whole thing.
    byte[] hashPlusSalt = new byte[digestBytes.length + NUM_SALT_BYTES];
@@ -174,8 +184,6 @@
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
@@ -184,7 +192,7 @@
                         NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      digestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -199,7 +207,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    // Append the salt to the hashed value and base64-the whole thing.
    byte[] hashPlusSalt = new byte[digestBytes.length + NUM_SALT_BYTES];
@@ -251,11 +258,9 @@
    byte[] userDigestBytes;
    synchronized (digestLock)
    {
      try
      {
        userDigestBytes = messageDigest.digest(plainPlusSalt);
      userDigestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -267,7 +272,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    return Arrays.equals(digestBytes, userDigestBytes);
  }
@@ -297,8 +301,6 @@
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
@@ -307,7 +309,7 @@
                         NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      digestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -322,7 +324,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    // Encode and return the value.
    StringBuilder authPWValue = new StringBuilder();
@@ -359,19 +360,16 @@
    System.arraycopy(saltBytes, 0, plainPlusSaltBytes, plainBytesLength,
                     saltBytes.length);
    synchronized (digestLock)
    {
      try
      {
        return Arrays.equals(digestBytes,
                                messageDigest.digest(plainPlusSaltBytes));
                              messageDigest.get().digest(plainPlusSaltBytes));
      }
      finally
      {
        Arrays.fill(plainPlusSaltBytes, (byte) 0);
      }
    }
  }
  @Override
  public boolean isReversible()
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,7 +109,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);
      }
    });
    random     = new SecureRandom();
  }
@@ -128,8 +140,6 @@
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
@@ -138,7 +148,7 @@
                         NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      digestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -153,7 +163,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    // Append the salt to the hashed value and base64-the whole thing.
    byte[] hashPlusSalt = new byte[digestBytes.length + NUM_SALT_BYTES];
@@ -182,8 +191,6 @@
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
@@ -192,7 +199,7 @@
                         NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      digestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -207,7 +214,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    // Append the salt to the hashed value and base64-the whole thing.
    byte[] hashPlusSalt = new byte[digestBytes.length + NUM_SALT_BYTES];
@@ -259,11 +265,9 @@
    byte[] userDigestBytes;
    synchronized (digestLock)
    {
      try
      {
        userDigestBytes = messageDigest.digest(plainPlusSalt);
      userDigestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -275,7 +279,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    return Arrays.equals(digestBytes, userDigestBytes);
  }
@@ -305,8 +308,6 @@
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
@@ -315,7 +316,7 @@
                         NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      digestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -330,7 +331,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    // Encode and return the value.
    StringBuilder authPWValue = new StringBuilder();
@@ -367,19 +367,16 @@
    System.arraycopy(saltBytes, 0, plainPlusSaltBytes, plainBytesLength,
                     saltBytes.length);
    synchronized (digestLock)
    {
      try
      {
        return Arrays.equals(digestBytes,
                messageDigest.digest(plainPlusSaltBytes));
              messageDigest.get().digest(plainPlusSaltBytes));
      }
      finally
      {
        Arrays.fill(plainPlusSaltBytes, (byte) 0);
      }
    }
  }
  @Override
  public boolean isReversible()
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,7 +91,7 @@
  {
    try
    {
      messageDigest =
      // Fail fast at initialization time if the algorithm is unavailable.
           MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_256);
    }
    catch (Exception e)
@@ -100,7 +103,16 @@
      throw new InitializationException(message, e);
    }
    digestLock = new Object();
    messageDigest = ThreadLocal.withInitial(() -> {
      try
      {
        return MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_256);
      }
      catch (Exception e)
      {
        throw new IllegalStateException(e);
      }
    });
    random     = new Random();
  }
@@ -122,8 +134,6 @@
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
@@ -132,7 +142,7 @@
                         NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      digestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -147,7 +157,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    // Append the salt to the hashed value and base64-the whole thing.
    byte[] hashPlusSalt = new byte[digestBytes.length + NUM_SALT_BYTES];
@@ -176,8 +185,6 @@
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
@@ -186,7 +193,7 @@
                         NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      digestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -201,7 +208,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    // Append the salt to the hashed value and base64-the whole thing.
    byte[] hashPlusSalt = new byte[digestBytes.length + NUM_SALT_BYTES];
@@ -255,11 +261,9 @@
    byte[] userDigestBytes;
    synchronized (digestLock)
    {
      try
      {
        userDigestBytes = messageDigest.digest(plainPlusSalt);
      userDigestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -271,7 +275,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    return Arrays.equals(digestBytes, userDigestBytes);
  }
@@ -301,8 +304,6 @@
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
@@ -311,7 +312,7 @@
                         NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      digestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -326,7 +327,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    // Encode and return the value.
    StringBuilder authPWValue = new StringBuilder();
@@ -363,19 +363,16 @@
    System.arraycopy(saltBytes, 0, plainPlusSaltBytes, plainBytesLength,
                     saltBytes.length);
    synchronized (digestLock)
    {
      try
      {
        return Arrays.equals(digestBytes,
                                  messageDigest.digest(plainPlusSaltBytes));
                                messageDigest.get().digest(plainPlusSaltBytes));
      }
      finally
      {
        Arrays.fill(plainPlusSaltBytes, (byte) 0);
      }
    }
  }
  @Override
  public boolean isReversible()
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,7 +91,7 @@
  {
    try
    {
      messageDigest =
      // Fail fast at initialization time if the algorithm is unavailable.
           MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_384);
    }
    catch (Exception e)
@@ -100,7 +103,16 @@
      throw new InitializationException(message, e);
    }
    digestLock = new Object();
    messageDigest = ThreadLocal.withInitial(() -> {
      try
      {
        return MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_384);
      }
      catch (Exception e)
      {
        throw new IllegalStateException(e);
      }
    });
    random     = new Random();
  }
@@ -122,8 +134,6 @@
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
@@ -132,7 +142,7 @@
                         NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      digestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -147,7 +157,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    // Append the salt to the hashed value and base64-the whole thing.
    byte[] hashPlusSalt = new byte[digestBytes.length + NUM_SALT_BYTES];
@@ -176,8 +185,6 @@
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
@@ -186,7 +193,7 @@
                         NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      digestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -201,7 +208,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    // Append the salt to the hashed value and base64-the whole thing.
    byte[] hashPlusSalt = new byte[digestBytes.length + NUM_SALT_BYTES];
@@ -255,11 +261,9 @@
    byte[] userDigestBytes;
    synchronized (digestLock)
    {
      try
      {
        userDigestBytes = messageDigest.digest(plainPlusSalt);
      userDigestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -271,7 +275,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    return Arrays.equals(digestBytes, userDigestBytes);
  }
@@ -301,8 +304,6 @@
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
@@ -311,7 +312,7 @@
                         NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      digestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -326,7 +327,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    // Encode and return the value.
    StringBuilder authPWValue = new StringBuilder();
@@ -363,19 +363,16 @@
    System.arraycopy(saltBytes, 0, plainPlusSaltBytes, plainBytesLength,
                     saltBytes.length);
    synchronized (digestLock)
    {
      try
      {
        return Arrays.equals(digestBytes,
                                  messageDigest.digest(plainPlusSaltBytes));
                                messageDigest.get().digest(plainPlusSaltBytes));
      }
      finally
      {
        Arrays.fill(plainPlusSaltBytes, (byte) 0);
      }
    }
  }
  @Override
  public boolean isReversible()
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,7 +98,7 @@
  {
    try
    {
      messageDigest =
      // Fail fast at initialization time if the algorithm is unavailable.
           MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_512);
    }
    catch (Exception e)
@@ -108,7 +110,16 @@
      throw new InitializationException(message, e);
    }
    digestLock = new Object();
    messageDigest = ThreadLocal.withInitial(() -> {
      try
      {
        return MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_512);
      }
      catch (Exception e)
      {
        throw new IllegalStateException(e);
      }
    });
    random     = new SecureRandom();
  }
@@ -130,8 +141,6 @@
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
@@ -140,7 +149,7 @@
                         NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      digestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -155,7 +164,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    // Append the salt to the hashed value and base64-the whole thing.
    byte[] hashPlusSalt = new byte[digestBytes.length + NUM_SALT_BYTES];
@@ -184,8 +192,6 @@
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
@@ -194,7 +200,7 @@
                         NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      digestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -209,7 +215,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    // Append the salt to the hashed value and base64-the whole thing.
    byte[] hashPlusSalt = new byte[digestBytes.length + NUM_SALT_BYTES];
@@ -263,11 +268,9 @@
    byte[] userDigestBytes;
    synchronized (digestLock)
    {
      try
      {
        userDigestBytes = messageDigest.digest(plainPlusSalt);
      userDigestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -279,7 +282,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    return Arrays.equals(digestBytes, userDigestBytes);
  }
@@ -309,8 +311,6 @@
    byte[] digestBytes;
    synchronized (digestLock)
    {
      try
      {
        // Generate the salt and put in the plain+salt array.
@@ -319,7 +319,7 @@
                         NUM_SALT_BYTES);
        // Create the hash from the concatenated value.
        digestBytes = messageDigest.digest(plainPlusSalt);
      digestBytes = messageDigest.get().digest(plainPlusSalt);
      }
      catch (Exception e)
      {
@@ -334,7 +334,6 @@
      {
        Arrays.fill(plainPlusSalt, (byte) 0);
      }
    }
    // Encode and return the value.
    StringBuilder authPWValue = new StringBuilder();
@@ -371,19 +370,16 @@
    System.arraycopy(saltBytes, 0, plainPlusSaltBytes, plainBytesLength,
                     saltBytes.length);
    synchronized (digestLock)
    {
      try
      {
        return Arrays.equals(digestBytes,
                                  messageDigest.digest(plainPlusSaltBytes));
                                messageDigest.get().digest(plainPlusSaltBytes));
      }
      finally
      {
        Arrays.fill(plainPlusSaltBytes, (byte) 0);
      }
    }
  }
  @Override
  public boolean isReversible()
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);