Skip to content

Add cache for password hash in druid-basic-security#15648

Merged
abhishekagarwal87 merged 3 commits intoapache:masterfrom
kfaraz:pass_hash_cache
Jan 10, 2024
Merged

Add cache for password hash in druid-basic-security#15648
abhishekagarwal87 merged 3 commits intoapache:masterfrom
kfaraz:pass_hash_cache

Conversation

@kfaraz
Copy link
Copy Markdown
Contributor

@kfaraz kfaraz commented Jan 9, 2024

Changes

  • Add class PasswordHashGenerator. Move hashing logic from BasicAuthUtils to this new class.
  • Add cache in the hash generator to contain the computed hash of passwords and boost validator performance
  • Cache has max size 1000 and expiry 1 hour
  • Key of the cache is an SHA-256 hash of the (password + random salt generated on service startup)

Pending changes

  • Testing to verify boost in performance

Release note

The computed hash values of passwords are now cached to boost authentication validator performance.


This PR has:

  • been self-reviewed.
  • added documentation for new or modified features or behaviors.
  • a release note entry in the PR description.
  • added Javadocs for most classes and all non-trivial methods. Linked related entities via Javadoc links.
  • added or updated version, license, or notice information in licenses.yaml
  • added comments explaining the "why" and the intent of the code wherever would not be obvious for an unfamiliar reader.
  • added unit tests or modified existing tests to cover new code paths, ensuring the threshold for code coverage is met.
  • added integration tests.
  • been tested in a test Druid cluster.

@Test
public void testHashPassword()
{
char[] password = "HELLO".toCharArray();

Check failure

Code scanning / CodeQL

Hard-coded credential in API call

Hard-coded value flows to [sensitive API call](1).
{
final PasswordHashGenerator hashGenerator = new PasswordHashGenerator();

final char[] password = "this_is_a_long_password".toCharArray();

Check failure

Code scanning / CodeQL

Hard-coded credential in API call

Hard-coded value flows to [sensitive API call](1).
{
final byte[] passwordSha;
final byte[] salt;
final int numIterations;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need numIterations in CacheKey?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is because the final hash value can change if numIterations changes. Also, it doesn't seem like we use the same number of iterations for every credential as it is configurable. But let me double check to be sure.

/**
* Hashes the given password using the {@link #HASH_ALGORITHM}.
*/
public byte[] getOrComputePasswordHash(char[] password, byte[] salt, int numIterations)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you run some local test to how much runs-per-second you get with "getOrComputePasswordHash" and with "computePasswordHash"? Just to make sure that we do see perf gains. By benchmark, I really mean just a simple test method calling these functions in a loop

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the newly added test PasswordHashGeneratorTest does a bit of comparison of the two methods.

Comment on lines +53 to +65
// Verify that the first computation takes a few ms
stopwatch.restart();
hashGenerator.getOrComputePasswordHash(password, salt, iterations);
long firstComputeTimeMillis = stopwatch.millisElapsed();
Assert.assertTrue(firstComputeTimeMillis > 50);

// Verify that each subsequent computation takes less than 1ms
for (int i = 0; i < 10; ++i) {
stopwatch.restart();
hashGenerator.getOrComputePasswordHash(password, salt, iterations);
long recomputeTimeMillis = stopwatch.millisElapsed();
Assert.assertTrue(recomputeTimeMillis <= 1);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a robust test though at least the perf is verified. Instead you could expose a method to access the cache and then access the CacheStats through getStats and verify hit count.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I had similar thoughts, let me try adding that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@imply-cheddar
Copy link
Copy Markdown
Contributor

Yay!

@abhishekagarwal87 abhishekagarwal87 merged commit d623756 into apache:master Jan 10, 2024
@LakshSingla LakshSingla added this to the 29.0.0 milestone Jan 29, 2024
@kfaraz kfaraz deleted the pass_hash_cache branch May 2, 2025 07:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants