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/util/Crypt.java | 33
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA512PasswordStorageScheme.java | 196 ++++----
opendj-server-legacy/src/main/java/org/opends/server/extensions/MD5PasswordStorageScheme.java | 134 +++--
opendj-server-legacy/src/main/java/org/opends/server/extensions/SHA1PasswordStorageScheme.java | 130 ++--
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA384PasswordStorageScheme.java | 195 ++++----
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA256PasswordStorageScheme.java | 195 ++++----
opendj-server-legacy/src/main/java/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java | 90 ++-
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedMD5PasswordStorageScheme.java | 194 ++++----
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA1PasswordStorageScheme.java | 195 ++++----
9 files changed, 685 insertions(+), 677 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java
index 8149ec5..400c3a0 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java
+++ b/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
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/MD5PasswordStorageScheme.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/MD5PasswordStorageScheme.java
index 8d639c9..8518405 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/MD5PasswordStorageScheme.java
+++ b/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);
}
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/SHA1PasswordStorageScheme.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/SHA1PasswordStorageScheme.java
index b3843bf..f16a88f 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/SHA1PasswordStorageScheme.java
+++ b/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);
}
}
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);
}
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA1PasswordStorageScheme.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA1PasswordStorageScheme.java
index e417e1c..d82a994 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA1PasswordStorageScheme.java
+++ b/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);
}
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA256PasswordStorageScheme.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA256PasswordStorageScheme.java
index d6780ba..adca969 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA256PasswordStorageScheme.java
+++ b/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);
}
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA384PasswordStorageScheme.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA384PasswordStorageScheme.java
index 187f211..50a5096 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA384PasswordStorageScheme.java
+++ b/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);
}
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA512PasswordStorageScheme.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA512PasswordStorageScheme.java
index 23d969b..58512d4 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA512PasswordStorageScheme.java
+++ b/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);
}
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/util/Crypt.java b/opendj-server-legacy/src/main/java/org/opends/server/util/Crypt.java
index 1391b2e..078f849 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/util/Crypt.java
+++ b/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);
--
Gitblit v1.10.0