Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2024 BSI Business Systems Integration AG
* Copyright (c) 2010, 2026 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand All @@ -21,6 +21,7 @@
import java.security.DigestInputStream;
import java.security.DigestOutputStream;
import java.util.Arrays;
import java.util.Set;

import org.eclipse.scout.rt.platform.BEANS;
import org.eclipse.scout.rt.platform.util.Assertions.AssertionException;
Expand Down Expand Up @@ -253,6 +254,32 @@ public void testHashPassword() {
Assert.assertTrue(ok);
}

@Test
public void testVerifyPassword() {
char[] password1 = "password-one".toCharArray();
char[] password2 = "password-two".toCharArray();
char[] password3 = "password-three".toCharArray();
char[] password4 = "password-four".toCharArray();

byte[] salt = SecurityUtility.createRandomBytes();
byte[] salt2 = SecurityUtility.createRandomBytes();

byte[] hash1 = SecurityUtility.hashPassword(password1, salt);
byte[] hash2 = SecurityUtility.hashPassword(password2, salt);
byte[] hash3 = SecurityUtility.hashPassword(password3, salt2);
Set<byte[]> expectedHashes = Set.of(hash1, hash2, hash3);

Assert.assertTrue(SecurityUtility.verifyPasswordHash(password1, salt, expectedHashes));
Assert.assertTrue(SecurityUtility.verifyPasswordHash(password2, salt, expectedHashes));
Assert.assertFalse(SecurityUtility.verifyPasswordHash(password3, salt, expectedHashes));
Assert.assertFalse(SecurityUtility.verifyPasswordHash(password4, salt, expectedHashes));

Assert.assertFalse(SecurityUtility.verifyPasswordHash(password1, salt2, expectedHashes));
Assert.assertFalse(SecurityUtility.verifyPasswordHash(password2, salt2, expectedHashes));
Assert.assertTrue(SecurityUtility.verifyPasswordHash(password3, salt2, expectedHashes));
Assert.assertFalse(SecurityUtility.verifyPasswordHash(password4, salt2, expectedHashes));
}

@Test
public void testCreateMac() {
byte[] data = "testdata".getBytes();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2024 BSI Business Systems Integration AG
* Copyright (c) 2010, 2026 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand All @@ -14,6 +14,7 @@
import java.security.DigestInputStream;
import java.security.DigestOutputStream;
import java.security.SecureRandom;
import java.util.Collection;

import org.eclipse.scout.rt.platform.ApplicationScoped;
import org.eclipse.scout.rt.platform.exception.ProcessingException;
Expand Down Expand Up @@ -198,6 +199,17 @@ public interface ISecurityProvider {
*/
boolean verifyPasswordHash(char[] password, byte[] salt, byte[] expectedHash);

/**
* This method is recommended in combination with {@link #createPasswordHash(char[], byte[])} where the iteration
* count is omitted. This has the advantage that the check of the password hash is independent of the creation of the
* hash. In case the iteration count is increased yearly, this method checks if the hash is valid
*
* @return true if calculated password hash created with {@link #createPasswordHash(char[], byte[])} matches one of
* the expected hashes.
* @since 25.2
*/
boolean verifyPasswordHash(char[] password, byte[] salt, Collection<byte[]> expectedHashes);

/**
* Encrypts the given data using the given {@link EncryptionKey}.<br>
* Use {@link #decrypt(InputStream, OutputStream, EncryptionKey)} to decrypt the data again using the same key.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2024 BSI Business Systems Integration AG
* Copyright (c) 2010, 2026 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand All @@ -22,6 +22,7 @@
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;

Expand Down Expand Up @@ -341,6 +342,19 @@ public static boolean verifyPasswordHash(char[] password, byte[] salt, byte[] ex
return SECURITY_PROVIDER.get().verifyPasswordHash(password, salt, expectedHash);
}

/**
* This method is recommended in combination with {@link #hashPassword(char[], byte[])} where the iteration count is
* omitted. This has the advantage that the check of the password hash is independent of the creation of the hash. In
* case the iteration count is increased yearly, this method checks if the hash is valid
*
* @return true if calculated password hash created with {@link #hashPassword(char[], byte[])} matches one of the
* expected hashes.
* @since 25.2
*/
public static boolean verifyPasswordHash(char[] password, byte[] salt, Collection<byte[]> expectedHashes) {
return SECURITY_PROVIDER.get().verifyPasswordHash(password, salt, expectedHashes);
}

/**
* Creates a hash for the given data using the given salt.
* <p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2025 BSI Business Systems Integration AG
* Copyright (c) 2010, 2026 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand Down Expand Up @@ -44,8 +44,10 @@
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.crypto.Cipher;
Expand All @@ -62,6 +64,7 @@
import org.eclipse.scout.rt.platform.exception.ProcessingException;
import org.eclipse.scout.rt.platform.util.Assertions;
import org.eclipse.scout.rt.platform.util.Base64Utility;
import org.eclipse.scout.rt.platform.util.CollectionUtility;

/**
* Utility class for encryption/decryption, hashing, random number generation and digital signatures.<br>
Expand Down Expand Up @@ -250,23 +253,32 @@ public byte[] createPasswordHash(char[] password, byte[] salt, int iterations) {

@Override
public boolean verifyPasswordHash(char[] password, byte[] salt, byte[] expectedHash) {
if (Arrays.equals(expectedHash, createPasswordHash(password, salt, MIN_PASSWORD_HASH_ITERATIONS))) {
return verifyPasswordHash(password, salt, CollectionUtility.hashSet(expectedHash));
}

@Override
public boolean verifyPasswordHash(char[] password, byte[] salt, Collection<byte[]> expectedHashes) {
if (CollectionUtility.isEmpty(expectedHashes)) {
return false;
}
Predicate<byte[]> acceptHash = hash -> expectedHashes.stream().anyMatch(expectedHash -> Arrays.equals(expectedHash, hash));
if (acceptHash.test(createPasswordHash(password, salt, MIN_PASSWORD_HASH_ITERATIONS))) {
return true;
}
if (Arrays.equals(expectedHash, createPasswordHash(password, salt, MIN_PASSWORD_HASH_ITERATIONS_2021))) {
if (acceptHash.test(createPasswordHash(password, salt, MIN_PASSWORD_HASH_ITERATIONS_2021))) {
return true;
}
if (Arrays.equals(expectedHash, createPasswordHash(password, salt, MIN_PASSWORD_HASH_ITERATIONS_2019))) {
if (acceptHash.test(createPasswordHash(password, salt, MIN_PASSWORD_HASH_ITERATIONS_2019))) {
return true;
}
if (Arrays.equals(expectedHash, createPasswordHash(password, salt, MIN_PASSWORD_HASH_ITERATIONS_2016))) {
if (acceptHash.test(createPasswordHash(password, salt, MIN_PASSWORD_HASH_ITERATIONS_2016))) {
return true;
}
//2014 variants
if (Arrays.equals(expectedHash, createHash(new ByteArrayInputStream(new String(password).getBytes(StandardCharsets.UTF_8)), salt, 3557))) {
if (acceptHash.test(createHash(new ByteArrayInputStream(new String(password).getBytes(StandardCharsets.UTF_8)), salt, 3557))) {
return true;
}
if (Arrays.equals(expectedHash, createHash(new ByteArrayInputStream(new String(password).getBytes(StandardCharsets.UTF_16)), salt, 3557))) {
if (acceptHash.test(createHash(new ByteArrayInputStream(new String(password).getBytes(StandardCharsets.UTF_16)), salt, 3557))) {
return true;
}
return false;
Expand Down
Loading