diff --git a/password/impl/src/main/java/org/wildfly/security/password/impl/BSDUnixDESCryptPasswordImpl.java b/password/impl/src/main/java/org/wildfly/security/password/impl/BSDUnixDESCryptPasswordImpl.java index cb918d2ebe6..c7fb6a44b5b 100644 --- a/password/impl/src/main/java/org/wildfly/security/password/impl/BSDUnixDESCryptPasswordImpl.java +++ b/password/impl/src/main/java/org/wildfly/security/password/impl/BSDUnixDESCryptPasswordImpl.java @@ -30,7 +30,6 @@ import java.security.spec.InvalidParameterSpecException; import java.security.spec.KeySpec; import java.util.Arrays; -import java.util.concurrent.ThreadLocalRandom; import org.wildfly.security.password.interfaces.BSDUnixDESCryptPassword; import org.wildfly.security.password.spec.ClearPasswordSpec; @@ -72,11 +71,11 @@ class BSDUnixDESCryptPasswordImpl extends AbstractPasswordImpl implements BSDUni } BSDUnixDESCryptPasswordImpl(final ClearPasswordSpec passwordSpec) throws InvalidKeySpecException { - this(passwordSpec.getEncodedPassword(), ThreadLocalRandom.current().nextInt() & 0xffffff, DEFAULT_ITERATION_COUNT); + this(passwordSpec.getEncodedPassword(), PasswordUtil.generateRandomSaltInt() & 0xffffff, DEFAULT_ITERATION_COUNT); } BSDUnixDESCryptPasswordImpl(final char[] password, final Charset hashCharset) throws InvalidKeySpecException, InvalidParameterSpecException { - this(password, ThreadLocalRandom.current().nextInt() & 0xffffff, DEFAULT_ITERATION_COUNT, hashCharset); + this(password, PasswordUtil.generateRandomSaltInt() & 0xffffff, DEFAULT_ITERATION_COUNT, hashCharset); } BSDUnixDESCryptPasswordImpl(final char[] password, final IteratedSaltedPasswordAlgorithmSpec spec, final Charset hashCharset) throws InvalidKeySpecException, InvalidParameterSpecException { @@ -84,7 +83,7 @@ class BSDUnixDESCryptPasswordImpl extends AbstractPasswordImpl implements BSDUni } BSDUnixDESCryptPasswordImpl(final char[] password, final IteratedPasswordAlgorithmSpec spec, final Charset hashCharset) throws InvalidKeySpecException, InvalidParameterSpecException { - this(password, ThreadLocalRandom.current().nextInt() & 0xffffff, spec.getIterationCount(), hashCharset); + this(password, PasswordUtil.generateRandomSaltInt() & 0xffffff, spec.getIterationCount(), hashCharset); } BSDUnixDESCryptPasswordImpl(final char[] password, final SaltedPasswordAlgorithmSpec spec, final Charset hashCharset) throws InvalidKeySpecException, InvalidParameterSpecException { diff --git a/password/impl/src/main/java/org/wildfly/security/password/impl/PasswordUtil.java b/password/impl/src/main/java/org/wildfly/security/password/impl/PasswordUtil.java index 21e1f8a58f1..9327f4b5ac0 100644 --- a/password/impl/src/main/java/org/wildfly/security/password/impl/PasswordUtil.java +++ b/password/impl/src/main/java/org/wildfly/security/password/impl/PasswordUtil.java @@ -17,7 +17,7 @@ */ package org.wildfly.security.password.impl; -import java.util.concurrent.ThreadLocalRandom; +import org.wildfly.common.Assert; /** * Helper utility methods for operations on passwords. @@ -27,6 +27,8 @@ */ final class PasswordUtil { + private static final ThreadLocalSecureRandom THREAD_LOCAL_SECURE_RANDOM = new ThreadLocalSecureRandom(); + /** * Generate a random salt as byte array. * @@ -35,7 +37,22 @@ final class PasswordUtil { */ public static byte[] generateRandomSalt(int saltSize) { byte[] randomSalt = new byte[saltSize]; - ThreadLocalRandom.current().nextBytes(randomSalt); + THREAD_LOCAL_SECURE_RANDOM.get().nextBytes(randomSalt); return randomSalt; } + + /** + * Generate a random salt as int. + * + * @return a byte array representing the random salt + */ + static int generateRandomSaltInt() { + byte[] saltBytes = generateRandomSalt(4); + return convertBytesToInt(saltBytes); + } + + static int convertBytesToInt(byte[] saltBytes) { + Assert.assertTrue(saltBytes.length == 4); + return (saltBytes[0] & 0xff) << 24 | (saltBytes[1] & 0xff) << 16 | (saltBytes[2] & 0xff) << 8 | saltBytes[3] & 0xff; + } } diff --git a/password/impl/src/main/java/org/wildfly/security/password/impl/ThreadLocalSecureRandom.java b/password/impl/src/main/java/org/wildfly/security/password/impl/ThreadLocalSecureRandom.java new file mode 100644 index 00000000000..5a99da754e8 --- /dev/null +++ b/password/impl/src/main/java/org/wildfly/security/password/impl/ThreadLocalSecureRandom.java @@ -0,0 +1,32 @@ +/* + * JBoss, Home of Professional Open Source + * + * Copyright 2024 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.password.impl; + +import java.security.SecureRandom; +import java.util.function.Supplier; + +class ThreadLocalSecureRandom implements Supplier { + final ThreadLocal localInstance = new ThreadLocal<>(); + + public SecureRandom get() { + if (localInstance.get() == null) { + localInstance.set(new SecureRandom()); + } + return localInstance.get(); + } +} diff --git a/password/impl/src/main/java/org/wildfly/security/password/impl/UnixDESCryptPasswordImpl.java b/password/impl/src/main/java/org/wildfly/security/password/impl/UnixDESCryptPasswordImpl.java index 00ad94110da..267dfd70200 100644 --- a/password/impl/src/main/java/org/wildfly/security/password/impl/UnixDESCryptPasswordImpl.java +++ b/password/impl/src/main/java/org/wildfly/security/password/impl/UnixDESCryptPasswordImpl.java @@ -31,7 +31,6 @@ import java.security.spec.InvalidParameterSpecException; import java.security.spec.KeySpec; import java.util.Arrays; -import java.util.concurrent.ThreadLocalRandom; import org.wildfly.security.password.interfaces.UnixDESCryptPassword; import org.wildfly.security.password.spec.ClearPasswordSpec; @@ -68,11 +67,11 @@ class UnixDESCryptPasswordImpl extends AbstractPasswordImpl implements UnixDESCr } UnixDESCryptPasswordImpl(final ClearPasswordSpec spec) throws InvalidKeySpecException, InvalidKeyException { - this((short) (ThreadLocalRandom.current().nextInt() & 0xfff), spec.getEncodedPassword()); + this((short) (PasswordUtil.generateRandomSaltInt() & 0xfff), spec.getEncodedPassword()); } UnixDESCryptPasswordImpl(final char[] passwordChars, final Charset hashCharset) throws InvalidKeyException { - this((short) (ThreadLocalRandom.current().nextInt() & 0xfff), passwordChars, hashCharset); + this((short) (PasswordUtil.generateRandomSaltInt() & 0xfff), passwordChars, hashCharset); } UnixDESCryptPasswordImpl(final char[] passwordChars, SaltedPasswordAlgorithmSpec algorithmSpec, final Charset hashCharset) throws InvalidParameterSpecException, InvalidKeyException { diff --git a/password/impl/src/test/java/org/wildfly/security/password/impl/PasswordUtilTest.java b/password/impl/src/test/java/org/wildfly/security/password/impl/PasswordUtilTest.java new file mode 100644 index 00000000000..ace17e04677 --- /dev/null +++ b/password/impl/src/test/java/org/wildfly/security/password/impl/PasswordUtilTest.java @@ -0,0 +1,32 @@ +/* + * JBoss, Home of Professional Open Source + * + * Copyright 2024 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.password.impl; + +import org.junit.Assert; +import org.junit.Test; + +public class PasswordUtilTest { + + @Test + public void testConvertBytesToInt() { + Assert.assertEquals(0, PasswordUtil.convertBytesToInt(new byte[] {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00})); + Assert.assertEquals(Integer.MAX_VALUE, PasswordUtil.convertBytesToInt(new byte[] {(byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff})); + Assert.assertEquals(Integer.MIN_VALUE, PasswordUtil.convertBytesToInt(new byte[] {(byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00})); + Assert.assertEquals(-1, PasswordUtil.convertBytesToInt(new byte[] {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff})); + } +}