Skip to content
Merged
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
1 change: 1 addition & 0 deletions crypto/spring-security-crypto.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dependencies {
management platform(project(":spring-security-dependencies"))
optional 'org.springframework:spring-core'
optional 'org.bouncycastle:bcpkix-jdk18on'
optional libs.com.password4j.password4j

testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.security.crypto.password4j;

import com.password4j.AlgorithmFinder;
import com.password4j.Argon2Function;

/**
* Implementation of {@link org.springframework.security.crypto.password.PasswordEncoder}
* that uses the Password4j library with Argon2 hashing algorithm.
*
* <p>
* Argon2 is the winner of the Password Hashing Competition (2015) and is recommended for
* new applications. It provides excellent resistance against GPU-based attacks and
* includes built-in salt generation. This implementation leverages Password4j's Argon2
* support which properly includes the salt in the output hash.
* </p>
*
* <p>
* This implementation is thread-safe and can be shared across multiple threads.
* </p>
*
* <p>
* <strong>Usage Examples:</strong>
* </p>
* <pre>{@code
* // Using default Argon2 settings (recommended)
* PasswordEncoder encoder = new Argon2Password4jPasswordEncoder();
*
* // Using custom Argon2 configuration
* PasswordEncoder customEncoder = new Argon2Password4jPasswordEncoder(
* Argon2Function.getInstance(65536, 3, 4, 32, Argon2.ID));
* }</pre>
*
* @author Mehrdad Bozorgmehr
* @since 7.0
* @see Argon2Function
* @see AlgorithmFinder#getArgon2Instance()
*/
public class Argon2Password4jPasswordEncoder extends Password4jPasswordEncoder {

/**
* Constructs an Argon2 password encoder using the default Argon2 configuration from
* Password4j's AlgorithmFinder.
*/
public Argon2Password4jPasswordEncoder() {
super(AlgorithmFinder.getArgon2Instance());
}

/**
* Constructs an Argon2 password encoder with a custom Argon2 function.
* @param argon2Function the Argon2 function to use for encoding passwords, must not
* be null
* @throws IllegalArgumentException if argon2Function is null
*/
public Argon2Password4jPasswordEncoder(Argon2Function argon2Function) {
super(argon2Function);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.security.crypto.password4j;

import java.security.SecureRandom;
import java.util.Base64;

import com.password4j.AlgorithmFinder;
import com.password4j.BalloonHashingFunction;
import com.password4j.Hash;
import com.password4j.Password;

import org.springframework.security.crypto.password.AbstractValidatingPasswordEncoder;
import org.springframework.util.Assert;

/**
* Implementation of {@link org.springframework.security.crypto.password.PasswordEncoder}
* that uses the Password4j library with Balloon hashing algorithm.
*
* <p>
* Balloon hashing is a memory-hard password hashing algorithm designed to be resistant to
* both time-memory trade-off attacks and side-channel attacks. This implementation
* handles the salt management explicitly since Password4j's Balloon hashing
* implementation does not include the salt in the output hash.
* </p>
*
* <p>
* The encoded password format is: {salt}:{hash} where both salt and hash are Base64
* encoded.
* </p>
*
* <p>
* This implementation is thread-safe and can be shared across multiple threads.
* </p>
*
* <p>
* <strong>Usage Examples:</strong>
* </p>
* <pre>{@code
* // Using default Balloon hashing settings (recommended)
* PasswordEncoder encoder = new BalloonHashingPassword4jPasswordEncoder();
*
* // Using custom Balloon hashing function
* PasswordEncoder customEncoder = new BalloonHashingPassword4jPasswordEncoder(
* BalloonHashingFunction.getInstance(1024, 3, 4, "SHA-256"));
* }</pre>
*
* @author Mehrdad Bozorgmehr
* @since 7.0
* @see BalloonHashingFunction
* @see AlgorithmFinder#getBalloonHashingInstance()
*/
public class BalloonHashingPassword4jPasswordEncoder extends AbstractValidatingPasswordEncoder {

private static final String DELIMITER = ":";

private static final int DEFAULT_SALT_LENGTH = 32;

private final BalloonHashingFunction balloonHashingFunction;

private final SecureRandom secureRandom;

private final int saltLength;

/**
* Constructs a Balloon hashing password encoder using the default Balloon hashing
* configuration from Password4j's AlgorithmFinder.
*/
public BalloonHashingPassword4jPasswordEncoder() {
this(AlgorithmFinder.getBalloonHashingInstance());
}

/**
* Constructs a Balloon hashing password encoder with a custom Balloon hashing
* function.
* @param balloonHashingFunction the Balloon hashing function to use for encoding
* passwords, must not be null
* @throws IllegalArgumentException if balloonHashingFunction is null
*/
public BalloonHashingPassword4jPasswordEncoder(BalloonHashingFunction balloonHashingFunction) {
this(balloonHashingFunction, DEFAULT_SALT_LENGTH);
}

/**
* Constructs a Balloon hashing password encoder with a custom Balloon hashing
* function and salt length.
* @param balloonHashingFunction the Balloon hashing function to use for encoding
* passwords, must not be null
* @param saltLength the length of the salt in bytes, must be positive
* @throws IllegalArgumentException if balloonHashingFunction is null or saltLength is
* not positive
*/
public BalloonHashingPassword4jPasswordEncoder(BalloonHashingFunction balloonHashingFunction, int saltLength) {
Assert.notNull(balloonHashingFunction, "balloonHashingFunction cannot be null");
Assert.isTrue(saltLength > 0, "saltLength must be positive");
this.balloonHashingFunction = balloonHashingFunction;
this.saltLength = saltLength;
this.secureRandom = new SecureRandom();
}

@Override
protected String encodeNonNullPassword(String rawPassword) {
byte[] salt = new byte[this.saltLength];
this.secureRandom.nextBytes(salt);

Hash hash = Password.hash(rawPassword).addSalt(salt).with(this.balloonHashingFunction);
String encodedSalt = Base64.getEncoder().encodeToString(salt);
String encodedHash = hash.getResult();

return encodedSalt + DELIMITER + encodedHash;
}

@Override
protected boolean matchesNonNull(String rawPassword, String encodedPassword) {
if (!encodedPassword.contains(DELIMITER)) {
return false;
}

String[] parts = encodedPassword.split(DELIMITER, 2);
if (parts.length != 2) {
return false;
}

try {
byte[] salt = Base64.getDecoder().decode(parts[0]);
String expectedHash = parts[1];

Hash hash = Password.hash(rawPassword).addSalt(salt).with(this.balloonHashingFunction);
return expectedHash.equals(hash.getResult());
}
catch (IllegalArgumentException ex) {
// Invalid Base64 encoding
return false;
}
}

@Override
protected boolean upgradeEncodingNonNull(String encodedPassword) {
// For now, we'll return false to maintain existing behavior
// This could be enhanced in the future to check if the encoding parameters
// match the current configuration
return false;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.security.crypto.password4j;

import com.password4j.AlgorithmFinder;
import com.password4j.BcryptFunction;

/**
* Implementation of {@link org.springframework.security.crypto.password.PasswordEncoder}
* that uses the Password4j library with BCrypt hashing algorithm.
*
* <p>
* BCrypt is a well-established password hashing algorithm that includes built-in salt
* generation and is resistant to rainbow table attacks. This implementation leverages
* Password4j's BCrypt support which properly includes the salt in the output hash.
* </p>
*
* <p>
* This implementation is thread-safe and can be shared across multiple threads.
* </p>
*
* <p>
* <strong>Usage Examples:</strong>
* </p>
* <pre>{@code
* // Using default BCrypt settings (recommended)
* PasswordEncoder encoder = new BcryptPassword4jPasswordEncoder();
*
* // Using custom round count
* PasswordEncoder customEncoder = new BcryptPassword4jPasswordEncoder(BcryptFunction.getInstance(12));
* }</pre>
*
* @author Mehrdad Bozorgmehr
* @since 7.0
* @see BcryptFunction
* @see AlgorithmFinder#getBcryptInstance()
*/
public class BcryptPassword4jPasswordEncoder extends Password4jPasswordEncoder {

/**
* Constructs a BCrypt password encoder using the default BCrypt configuration from
* Password4j's AlgorithmFinder.
*/
public BcryptPassword4jPasswordEncoder() {
super(AlgorithmFinder.getBcryptInstance());
}

/**
* Constructs a BCrypt password encoder with a custom BCrypt function.
* @param bcryptFunction the BCrypt function to use for encoding passwords, must not
* be null
* @throws IllegalArgumentException if bcryptFunction is null
*/
public BcryptPassword4jPasswordEncoder(BcryptFunction bcryptFunction) {
super(bcryptFunction);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.security.crypto.password4j;

import com.password4j.Hash;
import com.password4j.HashingFunction;
import com.password4j.Password;

import org.springframework.security.crypto.password.AbstractValidatingPasswordEncoder;
import org.springframework.util.Assert;

/**
* Abstract base class for Password4j-based password encoders. This class provides the
* common functionality for password encoding and verification using the Password4j
* library.
*
* <p>
* This class is package-private and should not be used directly. Instead, use the
* specific public subclasses that support verified hashing algorithms such as BCrypt,
* Argon2, and SCrypt implementations.
* </p>
*
* <p>
* This implementation is thread-safe and can be shared across multiple threads.
* </p>
*
* @author Mehrdad Bozorgmehr
* @since 7.0
*/
abstract class Password4jPasswordEncoder extends AbstractValidatingPasswordEncoder {

private final HashingFunction hashingFunction;

/**
* Constructs a Password4j password encoder with the specified hashing function. This
* constructor is package-private and intended for use by subclasses only.
* @param hashingFunction the hashing function to use for encoding passwords, must not
* be null
* @throws IllegalArgumentException if hashingFunction is null
*/
Password4jPasswordEncoder(HashingFunction hashingFunction) {
Assert.notNull(hashingFunction, "hashingFunction cannot be null");
this.hashingFunction = hashingFunction;
}

@Override
protected String encodeNonNullPassword(String rawPassword) {
Hash hash = Password.hash(rawPassword).with(this.hashingFunction);
return hash.getResult();
}

@Override
protected boolean matchesNonNull(String rawPassword, String encodedPassword) {
return Password.check(rawPassword, encodedPassword).with(this.hashingFunction);
}

@Override
protected boolean upgradeEncodingNonNull(String encodedPassword) {
// Password4j handles upgrade detection internally for most algorithms
// For now, we'll return false to maintain existing behavior
return false;
}

}
Loading