Password Transformer Plugin#

Plugin Descriptor interface:

PasswordTransformerPluginDescriptor

A Password Transformer provides a password hashing algorithm. Installing a Password Transformer plugin is the way to make the Curity Identity Server hash credentials with an algorithm it does not support out of the box.

An algorithm provided by a plugin is used by configuring a Password Transformer with it in Facilities, and then selecting that Password Transformer as the algorithm of one or more Credential Managers, which refer to it by its ID.

A Password Transformer plugin may provide more than one algorithm. The plugin descriptor returns a Map from the simple name of each algorithm to the PasswordTransformer implementation class for it:

public class Argon2PasswordTransformerPluginDescriptor
        implements PasswordTransformerPluginDescriptor<Argon2Configuration>
{
    @Override
    public String getPluginImplementationType()
    {
        return "argon2-password-transformer";
    }

    @Override
    public Class<Argon2Configuration> getConfigurationType()
    {
        return Argon2Configuration.class;
    }

    @Override
    public Map<String, Class<? extends PasswordTransformer>> getPasswordTransformers()
    {
        // hashing algorithms usually come in families
        return Map.of(
                "argon2id", Argon2idPasswordTransformer.class,
                "argon2i", Argon2iPasswordTransformer.class,
                "argon2d", Argon2dPasswordTransformer.class);
    }
}

All password transformers declared by one descriptor share that descriptor’s configuration type. Algorithm names must be valid configuration identifiers and must not clash with the algorithms provided by another installed Password Transformer plugin, otherwise the plugin is rejected at startup. When a plugin offers algorithm families with different configuration settings, provide one descriptor per family.

Implementing a Password Transformer#

A PasswordTransformer both produces the value that is stored (transform) and verifies a clear text password against a previously stored value (match). Implementations are instantiated by the server’s dependency injection container, so they can receive the plugin’s configuration type via constructor injection.

Both methods receive the subject the credentials belong to, so an algorithm can use the subject itself when hashing, or look up information about the user before deciding how to hash. Most algorithms do not need it and can ignore the parameter. The subject is the unvalidated identifier provided by the client (typically the username), so no further information about the account can be assumed from it, and an implementation that does use it must apply the same normalization that the system applies to usernames (for example, lowercase the subject when usernames are case-insensitive) — otherwise a password hashed under one spelling of the username can never be verified under another.

When the algorithm uses the subject in the hash, a username change makes the stored value unverifiable: the server cannot re-hash it without the clear text password, so the user can no longer log in. Deployments using such an algorithm must not enable allow-username-updates in the User Management Service settings (it is disabled by default).

Do not keep the clear text password#

The clear text password’s type is CharSequence rather than a String so that callers can pass in a mutable StringBuilder or a custom Secret type, for example, to be able to clear it as soon as the call returns.

The match method return value#

The result of match drives more than just the verification outcome:

  • Match(shouldUpgrade) — the password matches. Set shouldUpgrade to true when the parameters encoded in the stored value differ from the currently configured ones, so that the server re-hashes and stores the credential with the current settings.
  • NoMatch(invalidData) — the password does not match. Set invalidData to true when the stored value could not have been produced by this transformer at all (for example, it is not in the expected format). This is what allows Credential Rehashing to fall back to the other configured algorithms. Implementations should fail fast in that case, and otherwise aim for fixed-time outcomes.

The transform method#

Everything needed to verify a password later — the salt and all cost parameters — has to be encoded in the value returned by transform. Using an established encoding, such as the PHC string format, keeps the stored hashes interchangeable with other implementations of the same algorithm.

Notice that it’s a mandatory property of any hashing algorithm that the hash cannot be reverted to the original, plain text password, in a reasonable amount of time.

The hash value will be stored in a credential store. One must ensure that the hash meets the constraints imposed by the credential store. For example, when using the default Curity SQL Database schema, the hash must not exceed 128 characters.

Treat the stored value as untrusted input#

match receives whatever is in the credential store. That value may have been written by another product during a migration, or by anyone who has gained write access to the database, so an implementation must not let it decide how much work to do:

  • Bound every parameter decoded from the stored value before using it — cost factors, iteration counts, memory sizes, salt and hash lengths. A single stored credential asking for gigabytes of memory or billions of iterations would otherwise be enough to exhaust the server’s heap or occupy a request thread indefinitely.
  • Never throw. A value that cannot be verified is a NoMatch(true), not an exception. Throwing turns a failed login into a server error and stops Credential Rehashing from trying the other configured algorithms.
  • Bound the concurrency if the algorithm’s cost is in memory rather than only in time. Nothing else limits how many verifications run at once, so an algorithm that allocates while it runs needs its own limit — the hardened-hashing plugin does this with a max-concurrent-operations setting backed by a semaphore held in its ManagedObject, which is the right place for it because a new PasswordTransformer is created for every credential verification.

Algorithms provided by a plugin cannot be used when the server runs in FIPS mode. The server has no way to tell which cryptographic primitives a plugin uses, so it rejects any Credential Manager configured with a Password Transformer, whether as its main algorithm or as a rehashing source.

Reporting invalidData correctly is what makes a Password Transformer usable as a source for Credential Rehashing. An implementation that returns NoMatch(false) for a stored value it did not produce stops the search at itself, so the remaining sources are never tried and passwords hashed by them are rejected. This is why the built-in plaintext algorithm and credential transformation procedures, which cannot make that distinction, are not allowed as the main algorithm of a Credential Manager that rehashes.

For more information, refer to Credential Managers.

The Hardened Hashing Plugin#

An example Password Transformer Plugin is the hardened-hashing Plugin, which provides hashing algorithms in the Argon2 and SCrypt families.

It is open-source and can be used as an example for creating similar plugins, or installed on your Curity Server if you need to ensure that your credentials are stored with a high level of security.

Was this helpful?