Package org.codelibs.fess.helper
Class PasswordHashHelper
java.lang.Object
org.codelibs.fess.helper.PasswordHashHelper
Facade for password hashing and verification.
Implements a prefix-based ({id}hash) delegating scheme that is
functionally equivalent to Spring Security's
PasswordEncoderFactories.createDelegatingPasswordEncoder() (v5.8
defaults), without pulling in any external dependency. The default encoder
is BCrypt (cost 10, $2a$) and legacy pre-prefix hashes are
still verifiable via app.digest.algorithm (sha256/sha512/md5 hex
lower-case, no salt).
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprotected classBCrypt encoder backed by the vendored jBCrypt implementation.protected static interfaceInternal encoder abstraction. -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringFull id prefix token for BCrypt ({bcrypt}).protected static final StringPlaintext seed used to generate the dummy BCrypt hash consumed byapplyTimingPadding().protected StringCached dummy BCrypt hash used byapplyTimingPadding()as a timing-attack countermeasure.protected Map<String, PasswordHashHelper.PasswordEncoder> Registered encoders keyed by id.protected static final StringEncoder id for BCrypt.protected static final StringPrefix marker (start) of the encoder id.protected static final StringPrefix marker (end) of the encoder id. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoidConsumes approximately one BCrypt verification worth of CPU to equalise authentication latency across success and failure paths.Encodes the raw password using the configured default algorithm.protected StringExtracts the{id}from a stored password value, ornullif it does not carry a prefix.protected StringReturns the dummy BCrypt hash used byapplyTimingPadding(), generating it lazily on first access using the configured BCrypt cost.protected Map<String, PasswordHashHelper.PasswordEncoder> Returns the encoder map, initializing it lazily on first access.protected byte[]hexDecodeLowerCase(String hex) Decodes a lower-case hex string into bytes.voidinit()Validates the password configuration eagerly at container start-up so that a misconfiguredapp.password.algorithmfails fast instead of surfacing on the first user login.booleanisTimingSafeHash(String storedPassword) Indicates whether the supplied stored hash is in a format whose verification viamatches(String, String)already pays a BCrypt cost (or equivalent), meaning the caller does not need to addapplyTimingPadding()on the failure branch.booleanVerifies that the raw password matches the stored representation.protected booleanmatchesLegacy(String rawPassword, String storedPassword) Verifies a legacy (unprefixed) hex digest usingapp.digest.algorithmwith a constant-time comparison.protected intparseBcryptCost(String bcryptHash) Parses the cost (log rounds) component of a BCrypt hash ($2a$NN$...).protected intResolves the effective BCrypt cost, clamped into the jBCrypt-valid range [4, 30].protected StringResolves the encoder id used for new passwords.protected StringtoJcaDigestName(String algorithm) Translates the short algorithm id (sha256/sha512/md5) recorded inapp.digest.algorithminto the JCA standard name.booleanupgradeEncoding(String storedPassword) Determines whether the stored password should be re-encoded using the currently configured algorithm and parameters.
-
Field Details
-
PREFIX
Prefix marker (start) of the encoder id.- See Also:
-
SUFFIX
Prefix marker (end) of the encoder id.- See Also:
-
ID_BCRYPT
Encoder id for BCrypt.- See Also:
-
BCRYPT_PREFIX
Full id prefix token for BCrypt ({bcrypt}).- See Also:
-
DUMMY_BCRYPT_SEED
Plaintext seed used to generate the dummy BCrypt hash consumed byapplyTimingPadding(). The value is not secret and is not user-supplied; it exists only to produce a well-formed hash for timing-attack equalisation.- See Also:
-
encoders
Registered encoders keyed by id. Populated lazily and read-only after init. -
dummyBcryptHash
Cached dummy BCrypt hash used byapplyTimingPadding()as a timing-attack countermeasure. Built at container start-up (seeinit()) using the currently configuredapp.password.bcrypt.cost, so that the dummy path uses the same BCrypt cost as real user records. Stored in avolatilefield with double-checked locking so a runtime cost bump (via config reload) can still trigger a one-time regeneration without synchronisation on the hot path.
-
-
Constructor Details
-
PasswordHashHelper
public PasswordHashHelper()Default constructor.
-
-
Method Details
-
init
@PostConstruct public void init()Validates the password configuration eagerly at container start-up so that a misconfiguredapp.password.algorithmfails fast instead of surfacing on the first user login. Also forces the encoder map to build so its construction cost is not paid on the login critical path. -
applyTimingPadding
public void applyTimingPadding()Consumes approximately one BCrypt verification worth of CPU to equalise authentication latency across success and failure paths. Callers should invoke this on the failure branch when the normalmatches(java.lang.String, java.lang.String)invocation did not already pay a BCrypt cost (for example: user not found, legacy hex-digest stored value, unknown-id prefix).This method never throws: if the dummy hash cannot be produced for any reason, it returns silently rather than leaking a distinguishable exception to the caller.
-
isTimingSafeHash
Indicates whether the supplied stored hash is in a format whose verification viamatches(String, String)already pays a BCrypt cost (or equivalent), meaning the caller does not need to addapplyTimingPadding()on the failure branch.- Parameters:
storedPassword- the stored (hashed) password value- Returns:
trueif the id prefix denotes a timing-safe encoder (currently only{bcrypt});falsefor legacy unprefixed values and unknown prefixes
-
getDummyBcryptHash
Returns the dummy BCrypt hash used byapplyTimingPadding(), generating it lazily on first access using the configured BCrypt cost. Uses double-checked locking so subsequent calls are lock-free.- Returns:
- a valid
{bcrypt}$2a$...string that will never match any real user-supplied plaintext, ornullif generation failed (logged only)
-
resolveBcryptCost
protected int resolveBcryptCost()Resolves the effective BCrypt cost, clamped into the jBCrypt-valid range [4, 30]. Extracted fromPasswordHashHelper.BcryptPasswordEncoderso the dummy-hash path uses the exact same value as real user records.- Returns:
- the effective BCrypt cost in the range [4, 30]
-
encode
Encodes the raw password using the configured default algorithm.- Parameters:
rawPassword- the plain-text password (must not benull)- Returns:
- the encoded password prefixed with
{id} - Throws:
NullPointerException- ifrawPasswordis nullIllegalStateException- if the configured algorithm is unknown
-
matches
Verifies that the raw password matches the stored representation. Falls back to legacy hex digest verification when the stored value carries no{id}prefix.- Parameters:
rawPassword- the plain-text password being verifiedstoredPassword- the stored (hashed) password- Returns:
trueif the password matches,falseotherwise (including null/empty inputs and unknown prefixes)
-
upgradeEncoding
Determines whether the stored password should be re-encoded using the currently configured algorithm and parameters.- Parameters:
storedPassword- the stored (hashed) password- Returns:
trueif re-encoding is recommended
-
resolveIdForEncode
Resolves the encoder id used for new passwords.- Returns:
- the configured encoder id (lower-cased)
-
extractId
Extracts the{id}from a stored password value, ornullif it does not carry a prefix.- Parameters:
storedPassword- the stored password value (possibly prefixed)- Returns:
- the id inside
{...}, ornullif not prefixed
-
parseBcryptCost
Parses the cost (log rounds) component of a BCrypt hash ($2a$NN$...).- Parameters:
bcryptHash- the BCrypt hash string (without any{id}prefix)- Returns:
- the cost value, or
-1if parsing fails
-
matchesLegacy
Verifies a legacy (unprefixed) hex digest usingapp.digest.algorithmwith a constant-time comparison.- Parameters:
rawPassword- the plain-text passwordstoredPassword- the stored legacy hex digest- Returns:
trueif matched
-
toJcaDigestName
Translates the short algorithm id (sha256/sha512/md5) recorded inapp.digest.algorithminto the JCA standard name.- Parameters:
algorithm- the short algorithm id- Returns:
- the JCA digest name, or
nullif unsupported
-
hexDecodeLowerCase
Decodes a lower-case hex string into bytes. Upper-case input is also accepted for robustness — constant-time comparison still succeeds.- Parameters:
hex- the hex-encoded input- Returns:
- the decoded bytes, or
nullif the input is not a valid even-length hex string
-
getEncoders
Returns the encoder map, initializing it lazily on first access.- Returns:
- an immutable map of encoder id to encoder
-