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