Skip to content
David Bertoldi edited this page Oct 7, 2022 · 6 revisions

scrypt is a password-based key derivation function created by Colin Percival, originally for the Tarsnap online backup service. The algorithm was specifically designed to make it costly to perform large-scale custom hardware attacks by requiring large amounts of memory.

📑 Define scrypt parameters

scrypt accepts 4 parameters: the work factor which defines the CPU/memory cost, the resources which fine-tunes sequential memory read size and performance, the parallelisation level and the desired key length.

Name Default value Properties Description
Work factor (N) 65536 hash.scrypt.workfactor Defines the CPU/memory cost. Must be a power of 2.
Resources (r) 8 hash.scrypt.resources Defines the size of memory blocks.
Parallelisation (p) 1 hash.scrypt.parallelization Defines the cost of parallelisation for an attacker.
Output length 64 hash.scrypt.derivedKeyLength Defines the desired length of the final derived key

You can define a singleton custom scrypt function by calling ScryptFunction.getInstance(int, int, int, int) or ScryptFunction.getInstance(int, int, int)

ScryptFunction scrypt = ScryptFunction.getInstance(16384, 8, 2, 128);

In this case you have created a singleton instance which uses a work factor of 214, with 8 bytes memory blocks, a parallelisation cost of 2 and that produces a 128 bytes derived key.

Alternatively if you have defined the parameters in the psw4j.properties file

ScryptFunction scrypt = AlgorithmFinder.getScryptInstance();

Additionally you can create a ScryptFunction singleton instance from the hash, since all the parameters required are stored into it.

String hashed = "$s0$e0801$c2FsdA==$dFcxr0SE8yOWiWntoomu7gBbWQOsVh5kpayhIXl793NO+f1YQi4uIhg7ysup7Ie6DIO3oueI8Dzg2gZGNDPNpg==";

ScryptFunction scrypt = ScryptFunction.getInstanceFromHash(hashed);

#️⃣ How to hash passwords

Hashing passwords with scrypt can be done quite easily.

Hash hash = Password.hash(plainTextPassword).withScrypt();

hash.getResult(); // $s0$e0801$c2FsdA==$dFcxr0SE8yOWiWntoomu7gBbWQOsVh5kpayhIXl793NO+f1YQi4uIhg7ysup7Ie6DIO3oueI8Dzg2gZGNDPNpg==

This approach takes the parameters from psw4j.properties file (like AlgorithmFinder.getScryptInstance()). However it's possible to use user-defined parameters as we saw previously

ScryptFunction myScrypt = ScryptFunction.getInstance(16384, 8, 2, 128);

Password.hash(plainTextPassword).with(myScrypt);

Add salt

You have two way to define the cryptographic salt.

Explicit

Method addSalt(String) make you define the intended salt.

Hash hash = Password.hash(plainTextPassword).addSalt("a1b2c3d4").withScrypt();

For security reasons, the salt must never be the same.

Randomly

Method addRandomSalt(int) adds a random generated salt with a defined length (in bytes)

Hash hash = Password.hash(plainTextPassword).addRandomSalt(42).withScrypt();

Alternatively, you can use addRandomSalt() (no arguments) to define a random salt of 64 bytes

Hash hash = Password.hash(plainTextPassword).addRandomSalt().withScrypt();

Add pepper

Method addPepper(CharSequence) make you define the intended pepper.

Hash hash = Password.hash(plainTextPassword).addPepper("AlicePepper").withScrypt();

Alternatively you can define the pepper in the psw4j.properties file at the property global.pepper

Hash hash = Password.hash(plainTextPassword).addPepper().withScrypt();

The pepper is always prepended.

✔️ How to check the hash

Ideally the hash is retrieved from the database. Once retrieved you can check the user-provided passwords against the hash from your database. The salt is always encoded within the hash.

String hashFromDB = getHashFromDatabase(user);

boolean verified = Password.check(userProvidedPassword, hashFromDB).withScrypt();

The parameters used are taken from your psw4j.properties file. Alternatively you can define your own parameters

String hashFromDB = getHashFromDatabase(user);

ScryptFunction myScrypt = ScryptFunction.getInstance(16384, 8, 2, 128);

boolean verified = Password.check(userProvidedPassword, hashFromDB).with(myScrypt);

🔄 How to update the hash

If you want to migrate your cryptographic hashes from the original configuration to a more secure one, you can refresh them during the first user login.

String hashFromDB = getHashFromDatabase(user);

ScryptFunction myScrypt = ScryptFunction.getInstance(16384, 8, 2, 128);

HashUpdate update = Password.check(userProvidedPassword, hashFromDB)
                            .andUpdate()
                            .addNewRandomSalt().with(myScrypt);

if(update.isVerified()) 
{
    Hash newHash = update.getHash();
    storeNewHash(user, newHash.getHash());
}

You can switch to any other hashing function offered by Password4j (for example Argon2)

ScryptFunction oldFunction = AlgorithmFinder.getScryptFunction();
Argon2Function newFunction = AlgorithmFinder.getArgon2Function();

HashUpdate update = Password.check(userProvidedPassword, hashFromDB)
                            .andUpdate()
                            .addNewRandomSalt().with(oldFunction, newFunction);