From 1bda529e3cf685d5108832ecac7f433c85acb6fd Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Thu, 06 Aug 2026 11:31:31 +0000
Subject: [PATCH] Remove global digestLock serialization in digest password storage schemes (#667)

---
 opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedMD5PasswordStorageScheme.java |  194 ++++++++++++++++++++++++------------------------
 1 files changed, 96 insertions(+), 98 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedMD5PasswordStorageScheme.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedMD5PasswordStorageScheme.java
index b02e865..def7f1e 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedMD5PasswordStorageScheme.java
+++ b/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);
     }
   }
 

--
Gitblit v1.10.0