reformat code
This commit is contained in:
@@ -6,7 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@SpringBootApplication
|
||||
public class PasswordEncodingSpringBootApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(PasswordEncodingSpringBootApplication.class, args);
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(PasswordEncodingSpringBootApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,20 +9,20 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Service
|
||||
public class JdbcUserDetailPasswordService implements UserDetailsPasswordService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
private final UserDetailsMapper userDetailsMapper;
|
||||
private final UserDetailsMapper userDetailsMapper;
|
||||
|
||||
public JdbcUserDetailPasswordService(
|
||||
UserRepository userRepository, UserDetailsMapper userDetailsMapper) {
|
||||
this.userRepository = userRepository;
|
||||
this.userDetailsMapper = userDetailsMapper;
|
||||
}
|
||||
public JdbcUserDetailPasswordService(
|
||||
UserRepository userRepository, UserDetailsMapper userDetailsMapper) {
|
||||
this.userRepository = userRepository;
|
||||
this.userDetailsMapper = userDetailsMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails updatePassword(UserDetails user, String newPassword) {
|
||||
UserCredentials userCredentials = userRepository.findByUsername(user.getUsername());
|
||||
userCredentials.setPassword(newPassword);
|
||||
return userDetailsMapper.toUserDetails(userCredentials);
|
||||
}
|
||||
@Override
|
||||
public UserDetails updatePassword(UserDetails user, String newPassword) {
|
||||
UserCredentials userCredentials = userRepository.findByUsername(user.getUsername());
|
||||
userCredentials.setPassword(newPassword);
|
||||
return userDetailsMapper.toUserDetails(userCredentials);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,19 +10,19 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Transactional
|
||||
public class JdbcUserDetailsService implements UserDetailsService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
private final UserDetailsMapper userDetailsMapper;
|
||||
private final UserDetailsMapper userDetailsMapper;
|
||||
|
||||
public JdbcUserDetailsService(
|
||||
UserRepository userRepository, UserDetailsMapper userDetailsMapper) {
|
||||
this.userRepository = userRepository;
|
||||
this.userDetailsMapper = userDetailsMapper;
|
||||
}
|
||||
public JdbcUserDetailsService(
|
||||
UserRepository userRepository, UserDetailsMapper userDetailsMapper) {
|
||||
this.userRepository = userRepository;
|
||||
this.userDetailsMapper = userDetailsMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
UserCredentials userCredentials = userRepository.findByUsername(username);
|
||||
return userDetailsMapper.toUserDetails(userCredentials);
|
||||
}
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
UserCredentials userCredentials = userRepository.findByUsername(username);
|
||||
return userDetailsMapper.toUserDetails(userCredentials);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,17 @@ import java.util.Set;
|
||||
@Table(name = "users")
|
||||
public class UserCredentials {
|
||||
|
||||
@Id private String username;
|
||||
@Id
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
private String password;
|
||||
|
||||
boolean enabled;
|
||||
boolean enabled;
|
||||
|
||||
@ElementCollection
|
||||
@JoinTable(
|
||||
name = "authorities",
|
||||
joinColumns = {@JoinColumn(name = "username")})
|
||||
@Column(name = "authority")
|
||||
private Set<String> roles;
|
||||
@ElementCollection
|
||||
@JoinTable(
|
||||
name = "authorities",
|
||||
joinColumns = {@JoinColumn(name = "username")})
|
||||
@Column(name = "authority")
|
||||
private Set<String> roles;
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Transactional
|
||||
public interface UserRepository extends JpaRepository<UserCredentials, String> {
|
||||
|
||||
UserCredentials findByUsername(String username);
|
||||
UserCredentials findByUsername(String username);
|
||||
}
|
||||
|
||||
@@ -29,84 +29,75 @@ import java.util.Map;
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final UserDetailsMapper userDetailsMapper;
|
||||
private final BcCryptWorkFactorService bcCryptWorkFactorService;
|
||||
private final UserRepository userRepository;
|
||||
private final UserDetailsMapper userDetailsMapper;
|
||||
private final BcCryptWorkFactorService bcCryptWorkFactorService;
|
||||
|
||||
public SecurityConfiguration(
|
||||
UserRepository userRepository,
|
||||
UserDetailsMapper userDetailsMapper,
|
||||
BcCryptWorkFactorService bcCryptWorkFactorService) {
|
||||
this.userRepository = userRepository;
|
||||
this.userDetailsMapper = userDetailsMapper;
|
||||
this.bcCryptWorkFactorService = bcCryptWorkFactorService;
|
||||
}
|
||||
public SecurityConfiguration(
|
||||
UserRepository userRepository,
|
||||
UserDetailsMapper userDetailsMapper,
|
||||
BcCryptWorkFactorService bcCryptWorkFactorService) {
|
||||
this.userRepository = userRepository;
|
||||
this.userDetailsMapper = userDetailsMapper;
|
||||
this.bcCryptWorkFactorService = bcCryptWorkFactorService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity httpSecurity) throws Exception {
|
||||
httpSecurity
|
||||
.csrf()
|
||||
.disable()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/registration")
|
||||
.permitAll()
|
||||
.anyRequest()
|
||||
.authenticated()
|
||||
.and()
|
||||
.httpBasic();
|
||||
@Override
|
||||
protected void configure(HttpSecurity httpSecurity) throws Exception {
|
||||
httpSecurity
|
||||
.csrf()
|
||||
.disable()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/registration")
|
||||
.permitAll()
|
||||
.anyRequest()
|
||||
.authenticated()
|
||||
.and()
|
||||
.httpBasic();
|
||||
|
||||
httpSecurity.headers().frameOptions().disable();
|
||||
}
|
||||
httpSecurity.headers().frameOptions().disable();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.authenticationProvider(daoAuthenticationProvider()).eraseCredentials(false);
|
||||
}
|
||||
@Autowired
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.authenticationProvider(daoAuthenticationProvider()).eraseCredentials(false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
// we must user deprecated encoder to support their encoding
|
||||
String encodingId = "bcrypt";
|
||||
Map<String, PasswordEncoder> encoders = new HashMap<>();
|
||||
encoders.put(
|
||||
encodingId, new BCryptPasswordEncoder(bcCryptWorkFactorService.calculateStrength()));
|
||||
encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
|
||||
encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
|
||||
encoders.put(
|
||||
"MD5",
|
||||
new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
|
||||
encoders.put(
|
||||
"noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
|
||||
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
|
||||
encoders.put("scrypt", new SCryptPasswordEncoder());
|
||||
encoders.put(
|
||||
"SHA-1",
|
||||
new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
|
||||
encoders.put(
|
||||
"SHA-256",
|
||||
new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
|
||||
encoders.put(
|
||||
"sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
|
||||
encoders.put("argon2", new Argon2PasswordEncoder());
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
// we must user deprecated encoder to support their encoding
|
||||
String encodingId = "bcrypt";
|
||||
Map<String, PasswordEncoder> encoders = new HashMap<>();
|
||||
encoders.put(encodingId, new BCryptPasswordEncoder(bcCryptWorkFactorService.calculateStrength()));
|
||||
encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
|
||||
encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
|
||||
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
|
||||
encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
|
||||
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
|
||||
encoders.put("scrypt", new SCryptPasswordEncoder());
|
||||
encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
|
||||
encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
|
||||
encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
|
||||
encoders.put("argon2", new Argon2PasswordEncoder());
|
||||
|
||||
return new DelegatingPasswordEncoder(encodingId, encoders);
|
||||
}
|
||||
return new DelegatingPasswordEncoder(encodingId, encoders);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserDetailsPasswordService userDetailsPasswordService() {
|
||||
return new JdbcUserDetailPasswordService(userRepository, userDetailsMapper);
|
||||
}
|
||||
@Bean
|
||||
public UserDetailsPasswordService userDetailsPasswordService() {
|
||||
return new JdbcUserDetailPasswordService(userRepository, userDetailsMapper);
|
||||
}
|
||||
|
||||
public UserDetailsService userDetailsService() {
|
||||
return new JdbcUserDetailsService(userRepository, userDetailsMapper);
|
||||
}
|
||||
public UserDetailsService userDetailsService() {
|
||||
return new JdbcUserDetailsService(userRepository, userDetailsMapper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DaoAuthenticationProvider daoAuthenticationProvider() {
|
||||
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
|
||||
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
|
||||
daoAuthenticationProvider.setUserDetailsPasswordService(userDetailsPasswordService());
|
||||
daoAuthenticationProvider.setUserDetailsService(userDetailsService());
|
||||
return daoAuthenticationProvider;
|
||||
}
|
||||
@Bean
|
||||
public DaoAuthenticationProvider daoAuthenticationProvider() {
|
||||
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
|
||||
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
|
||||
daoAuthenticationProvider.setUserDetailsPasswordService(userDetailsPasswordService());
|
||||
daoAuthenticationProvider.setUserDetailsService(userDetailsService());
|
||||
return daoAuthenticationProvider;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
|
||||
|
||||
public class Argon2Example {
|
||||
|
||||
public String encode(String plainPassword) {
|
||||
int saltLength = 16; // salt length in bytes
|
||||
int hashLength = 32; // hash length in bytes
|
||||
int parallelism = 1; // currently is not supported
|
||||
int memory = 4096; // memory costs
|
||||
int iterations = 3;
|
||||
public String encode(String plainPassword) {
|
||||
int saltLength = 16; // salt length in bytes
|
||||
int hashLength = 32; // hash length in bytes
|
||||
int parallelism = 1; // currently is not supported
|
||||
int memory = 4096; // memory costs
|
||||
int iterations = 3;
|
||||
|
||||
Argon2PasswordEncoder argon2PasswordEncoder =
|
||||
new Argon2PasswordEncoder(saltLength, hashLength, parallelism, memory, iterations);
|
||||
return argon2PasswordEncoder.encode(plainPassword);
|
||||
}
|
||||
Argon2PasswordEncoder argon2PasswordEncoder =
|
||||
new Argon2PasswordEncoder(saltLength, hashLength, parallelism, memory, iterations);
|
||||
return argon2PasswordEncoder.encode(plainPassword);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ import java.security.SecureRandom;
|
||||
|
||||
public class BCryptExample {
|
||||
|
||||
public String encode(String plainPassword) {
|
||||
int strength = 10;
|
||||
BCryptPasswordEncoder bCryptPasswordEncoder =
|
||||
new BCryptPasswordEncoder(strength, new SecureRandom());
|
||||
return bCryptPasswordEncoder.encode(plainPassword);
|
||||
}
|
||||
public String encode(String plainPassword) {
|
||||
int strength = 10;
|
||||
BCryptPasswordEncoder bCryptPasswordEncoder =
|
||||
new BCryptPasswordEncoder(strength, new SecureRandom());
|
||||
return bCryptPasswordEncoder.encode(plainPassword);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@ import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
|
||||
|
||||
public class Pbkdf2Example {
|
||||
|
||||
public String encode(String plainPassword) {
|
||||
public String encode(String plainPassword) {
|
||||
|
||||
String pepper = "pepper"; // secret key used by password encoding
|
||||
int iterations = 200000; // number of hash iteration
|
||||
int hashWidth = 256; // hash with in bits
|
||||
String pepper = "pepper"; // secret key used by password encoding
|
||||
int iterations = 200000; // number of hash iteration
|
||||
int hashWidth = 256; // hash with in bits
|
||||
|
||||
Pbkdf2PasswordEncoder pbkdf2PasswordEncoder =
|
||||
new Pbkdf2PasswordEncoder(pepper, iterations, hashWidth);
|
||||
return pbkdf2PasswordEncoder.encode(plainPassword);
|
||||
}
|
||||
Pbkdf2PasswordEncoder pbkdf2PasswordEncoder =
|
||||
new Pbkdf2PasswordEncoder(pepper, iterations, hashWidth);
|
||||
return pbkdf2PasswordEncoder.encode(plainPassword);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
|
||||
|
||||
public class SCryptExample {
|
||||
|
||||
public String encode(String plainPassword) {
|
||||
int cpuCost = (int) Math.pow(2, 14); // factor to increase CPU costs
|
||||
int memoryCost = 8; // factor to increases memory usage
|
||||
int parallelization = 1; // currently nor supported by Spring Security
|
||||
int keyLength = 32; // key length in bytes
|
||||
int saltLength = 64; // salt length in bytes
|
||||
public String encode(String plainPassword) {
|
||||
int cpuCost = (int) Math.pow(2, 14); // factor to increase CPU costs
|
||||
int memoryCost = 8; // factor to increases memory usage
|
||||
int parallelization = 1; // currently nor supported by Spring Security
|
||||
int keyLength = 32; // key length in bytes
|
||||
int saltLength = 64; // salt length in bytes
|
||||
|
||||
SCryptPasswordEncoder sCryptPasswordEncoder =
|
||||
new SCryptPasswordEncoder(cpuCost, memoryCost, parallelization, keyLength, saltLength);
|
||||
return sCryptPasswordEncoder.encode(plainPassword);
|
||||
}
|
||||
SCryptPasswordEncoder sCryptPasswordEncoder =
|
||||
new SCryptPasswordEncoder(cpuCost, memoryCost, parallelization, keyLength, saltLength);
|
||||
return sCryptPasswordEncoder.encode(plainPassword);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,19 +13,19 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class PasswordMigration {
|
||||
|
||||
@Bean
|
||||
public ApplicationListener<AuthenticationSuccessEvent> authenticationSuccessListener(
|
||||
PasswordEncoder encoder, UserDetailsPasswordService userDetailsPasswordService) {
|
||||
return (AuthenticationSuccessEvent event) -> {
|
||||
Authentication authentication = event.getAuthentication();
|
||||
User user = (User) authentication.getPrincipal();
|
||||
String encodedPassword = user.getPassword();
|
||||
if (encodedPassword.startsWith("{SHA-1}")) {
|
||||
CharSequence clearTextPassword = (CharSequence) authentication.getCredentials();
|
||||
String newPassword = encoder.encode(clearTextPassword);
|
||||
userDetailsPasswordService.updatePassword(user, newPassword);
|
||||
}
|
||||
((UsernamePasswordAuthenticationToken) authentication).eraseCredentials();
|
||||
};
|
||||
}
|
||||
@Bean
|
||||
public ApplicationListener<AuthenticationSuccessEvent> authenticationSuccessListener(
|
||||
PasswordEncoder encoder, UserDetailsPasswordService userDetailsPasswordService) {
|
||||
return (AuthenticationSuccessEvent event) -> {
|
||||
Authentication authentication = event.getAuthentication();
|
||||
User user = (User) authentication.getPrincipal();
|
||||
String encodedPassword = user.getPassword();
|
||||
if (encodedPassword.startsWith("{SHA-1}")) {
|
||||
CharSequence clearTextPassword = (CharSequence) authentication.getCredentials();
|
||||
String newPassword = encoder.encode(clearTextPassword);
|
||||
userDetailsPasswordService.updatePassword(user, newPassword);
|
||||
}
|
||||
((UsernamePasswordAuthenticationToken) authentication).eraseCredentials();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@ import lombok.NoArgsConstructor;
|
||||
@AllArgsConstructor
|
||||
public class Car {
|
||||
|
||||
private String name;
|
||||
private String color;
|
||||
private String name;
|
||||
private String color;
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ import java.util.Set;
|
||||
@RestController
|
||||
public class CarResources {
|
||||
|
||||
// we use this endpoint as authentication test
|
||||
@GetMapping("/cars")
|
||||
public Set<Car> cars() {
|
||||
return Set.of(
|
||||
Car.builder().name("vw").color("black").build(),
|
||||
Car.builder().name("bmw").color("white").build());
|
||||
}
|
||||
// we use this endpoint as authentication test
|
||||
@GetMapping("/cars")
|
||||
public Set<Car> cars() {
|
||||
return Set.of(
|
||||
Car.builder().name("vw").color("black").build(),
|
||||
Car.builder().name("bmw").color("white").build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@ import lombok.NoArgsConstructor;
|
||||
@NoArgsConstructor
|
||||
public class UserCredentialsDto {
|
||||
|
||||
private String username;
|
||||
private String password;
|
||||
private String username;
|
||||
private String password;
|
||||
}
|
||||
|
||||
@@ -16,24 +16,24 @@ import java.util.Set;
|
||||
@Transactional
|
||||
public class UserResources {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
public UserResources(UserRepository userRepository, PasswordEncoder passwordEncoder) {
|
||||
this.userRepository = userRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
public UserResources(UserRepository userRepository, PasswordEncoder passwordEncoder) {
|
||||
this.userRepository = userRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
@PostMapping("/registration")
|
||||
@ResponseStatus(code = HttpStatus.CREATED)
|
||||
public void register(@RequestBody UserCredentialsDto userCredentialsDto) {
|
||||
UserCredentials user =
|
||||
UserCredentials.builder()
|
||||
.enabled(true)
|
||||
.username(userCredentialsDto.getUsername())
|
||||
.password(passwordEncoder.encode(userCredentialsDto.getPassword()))
|
||||
.roles(Set.of("USER"))
|
||||
.build();
|
||||
userRepository.save(user);
|
||||
}
|
||||
@PostMapping("/registration")
|
||||
@ResponseStatus(code = HttpStatus.CREATED)
|
||||
public void register(@RequestBody UserCredentialsDto userCredentialsDto) {
|
||||
UserCredentials user =
|
||||
UserCredentials.builder()
|
||||
.enabled(true)
|
||||
.username(userCredentialsDto.getUsername())
|
||||
.password(passwordEncoder.encode(userCredentialsDto.getPassword()))
|
||||
.roles(Set.of("USER"))
|
||||
.build();
|
||||
userRepository.save(user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,93 +9,93 @@ import java.util.concurrent.TimeUnit;
|
||||
@Component
|
||||
public class BcCryptWorkFactorService {
|
||||
|
||||
private static final String TEST_PASSWORD = "my password";
|
||||
private static final int GOAL_MILLISECONDS_PER_PASSWORD = 1000;
|
||||
private static final int MIN_STRENGTH = 4;
|
||||
private static final int MAX_STRENGTH = 31;
|
||||
private static final String TEST_PASSWORD = "my password";
|
||||
private static final int GOAL_MILLISECONDS_PER_PASSWORD = 1000;
|
||||
private static final int MIN_STRENGTH = 4;
|
||||
private static final int MAX_STRENGTH = 31;
|
||||
|
||||
/**
|
||||
* Calculates the strength (a.k.a. log rounds) for the BCrypt Algorithm, so that password encoding
|
||||
* takes about 1s. This method uses the divide-and-conquer algorithm.
|
||||
*/
|
||||
public BcryptWorkFactor calculateStrengthDivideAndConquer() {
|
||||
return calculateStrengthDivideAndConquer(
|
||||
new BcryptWorkFactor(MIN_STRENGTH, Integer.MIN_VALUE),
|
||||
new BcryptWorkFactor(MAX_STRENGTH, Integer.MAX_VALUE));
|
||||
}
|
||||
|
||||
private BcryptWorkFactor calculateStrengthDivideAndConquer(
|
||||
BcryptWorkFactor smallFactor, BcryptWorkFactor bigFactor) {
|
||||
if (bigFactor.getStrength() - smallFactor.getStrength() == 1) {
|
||||
return getClosestStrength(smallFactor, bigFactor);
|
||||
/**
|
||||
* Calculates the strength (a.k.a. log rounds) for the BCrypt Algorithm, so that password encoding
|
||||
* takes about 1s. This method uses the divide-and-conquer algorithm.
|
||||
*/
|
||||
public BcryptWorkFactor calculateStrengthDivideAndConquer() {
|
||||
return calculateStrengthDivideAndConquer(
|
||||
new BcryptWorkFactor(MIN_STRENGTH, Integer.MIN_VALUE),
|
||||
new BcryptWorkFactor(MAX_STRENGTH, Integer.MAX_VALUE));
|
||||
}
|
||||
int midStrength =
|
||||
(bigFactor.getStrength() - smallFactor.getStrength()) / 2 + smallFactor.getStrength();
|
||||
long duration = calculateDuration(midStrength);
|
||||
BcryptWorkFactor midFactor = new BcryptWorkFactor(midStrength, duration);
|
||||
if (duration < GOAL_MILLISECONDS_PER_PASSWORD) {
|
||||
return calculateStrengthDivideAndConquer(midFactor, bigFactor);
|
||||
|
||||
private BcryptWorkFactor calculateStrengthDivideAndConquer(
|
||||
BcryptWorkFactor smallFactor, BcryptWorkFactor bigFactor) {
|
||||
if (bigFactor.getStrength() - smallFactor.getStrength() == 1) {
|
||||
return getClosestStrength(smallFactor, bigFactor);
|
||||
}
|
||||
int midStrength =
|
||||
(bigFactor.getStrength() - smallFactor.getStrength()) / 2 + smallFactor.getStrength();
|
||||
long duration = calculateDuration(midStrength);
|
||||
BcryptWorkFactor midFactor = new BcryptWorkFactor(midStrength, duration);
|
||||
if (duration < GOAL_MILLISECONDS_PER_PASSWORD) {
|
||||
return calculateStrengthDivideAndConquer(midFactor, bigFactor);
|
||||
}
|
||||
return calculateStrengthDivideAndConquer(smallFactor, midFactor);
|
||||
}
|
||||
return calculateStrengthDivideAndConquer(smallFactor, midFactor);
|
||||
}
|
||||
|
||||
private BcryptWorkFactor getClosestStrength(
|
||||
BcryptWorkFactor smallFactor, BcryptWorkFactor bigFactor) {
|
||||
if (isPreviousDurationCloserToGoal(smallFactor.getDuration(), bigFactor.getDuration())) {
|
||||
return smallFactor;
|
||||
private BcryptWorkFactor getClosestStrength(
|
||||
BcryptWorkFactor smallFactor, BcryptWorkFactor bigFactor) {
|
||||
if (isPreviousDurationCloserToGoal(smallFactor.getDuration(), bigFactor.getDuration())) {
|
||||
return smallFactor;
|
||||
}
|
||||
return bigFactor;
|
||||
}
|
||||
return bigFactor;
|
||||
}
|
||||
|
||||
private long calculateDuration(int strength) {
|
||||
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(strength);
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
bCryptPasswordEncoder.encode(TEST_PASSWORD);
|
||||
stopwatch.stop();
|
||||
return stopwatch.elapsed(TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the strength (a.k.a. log rounds) for the BCrypt Algorithm, so that password encoding
|
||||
* takes about 1s. This method iterates over strength from 4 to 31 and calculates the duration of
|
||||
* password encoding for every value of strength. It returns the first strength, that takes more
|
||||
* than 1s
|
||||
*/
|
||||
public int calculateStrength() {
|
||||
for (int strength = MIN_STRENGTH; strength <= MAX_STRENGTH; strength++) {
|
||||
|
||||
long duration = calculateDuration(strength);
|
||||
if (duration >= GOAL_MILLISECONDS_PER_PASSWORD) {
|
||||
return strength;
|
||||
}
|
||||
private long calculateDuration(int strength) {
|
||||
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(strength);
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
bCryptPasswordEncoder.encode(TEST_PASSWORD);
|
||||
stopwatch.stop();
|
||||
return stopwatch.elapsed(TimeUnit.MILLISECONDS);
|
||||
}
|
||||
throw new RuntimeException(
|
||||
String.format(
|
||||
"Could not find suitable round number for bcrypt encoding. The encoding with %d rounds"
|
||||
+ " takes less than %d ms.",
|
||||
MAX_STRENGTH, GOAL_MILLISECONDS_PER_PASSWORD));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param previousDuration duration from previous iteration
|
||||
* @param currentDuration duration of current iteration
|
||||
* @param strength current strength
|
||||
* @return return the current strength, if current duration is closer to
|
||||
* GOAL_MILLISECONDS_PER_PASSWORD, otherwise current strength-1.
|
||||
*/
|
||||
int getStrength(long previousDuration, long currentDuration, int strength) {
|
||||
if (isPreviousDurationCloserToGoal(previousDuration, currentDuration)) {
|
||||
return strength - 1;
|
||||
} else {
|
||||
return strength;
|
||||
/**
|
||||
* Calculates the strength (a.k.a. log rounds) for the BCrypt Algorithm, so that password encoding
|
||||
* takes about 1s. This method iterates over strength from 4 to 31 and calculates the duration of
|
||||
* password encoding for every value of strength. It returns the first strength, that takes more
|
||||
* than 1s
|
||||
*/
|
||||
public int calculateStrength() {
|
||||
for (int strength = MIN_STRENGTH; strength <= MAX_STRENGTH; strength++) {
|
||||
|
||||
long duration = calculateDuration(strength);
|
||||
if (duration >= GOAL_MILLISECONDS_PER_PASSWORD) {
|
||||
return strength;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException(
|
||||
String.format(
|
||||
"Could not find suitable round number for bcrypt encoding. The encoding with %d rounds"
|
||||
+ " takes less than %d ms.",
|
||||
MAX_STRENGTH, GOAL_MILLISECONDS_PER_PASSWORD));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return true, if previousDuration is closer to the goal than currentDuration, false otherwise.
|
||||
*/
|
||||
boolean isPreviousDurationCloserToGoal(long previousDuration, long currentDuration) {
|
||||
return Math.abs(GOAL_MILLISECONDS_PER_PASSWORD - previousDuration)
|
||||
< Math.abs(GOAL_MILLISECONDS_PER_PASSWORD - currentDuration);
|
||||
}
|
||||
/**
|
||||
* @param previousDuration duration from previous iteration
|
||||
* @param currentDuration duration of current iteration
|
||||
* @param strength current strength
|
||||
* @return return the current strength, if current duration is closer to
|
||||
* GOAL_MILLISECONDS_PER_PASSWORD, otherwise current strength-1.
|
||||
*/
|
||||
int getStrength(long previousDuration, long currentDuration, int strength) {
|
||||
if (isPreviousDurationCloserToGoal(previousDuration, currentDuration)) {
|
||||
return strength - 1;
|
||||
} else {
|
||||
return strength;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return true, if previousDuration is closer to the goal than currentDuration, false otherwise.
|
||||
*/
|
||||
boolean isPreviousDurationCloserToGoal(long previousDuration, long currentDuration) {
|
||||
return Math.abs(GOAL_MILLISECONDS_PER_PASSWORD - previousDuration)
|
||||
< Math.abs(GOAL_MILLISECONDS_PER_PASSWORD - currentDuration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@ import lombok.Data;
|
||||
@AllArgsConstructor
|
||||
public class BcryptWorkFactor {
|
||||
|
||||
private int strength;
|
||||
private long duration;
|
||||
private int strength;
|
||||
private long duration;
|
||||
}
|
||||
|
||||
@@ -9,33 +9,33 @@ import java.util.concurrent.TimeUnit;
|
||||
@Component
|
||||
public class Pbkdf2WorkFactorService {
|
||||
|
||||
private static final String TEST_PASSWORD = "my password";
|
||||
private static final String NO_ADDITIONAL_SECRET = "";
|
||||
private static final int GOAL_MILLISECONDS_PER_PASSWORD = 1000;
|
||||
private static final int HASH_WIDTH = 256;
|
||||
private static final int ITERATION_STEP = 5000;
|
||||
private static final String TEST_PASSWORD = "my password";
|
||||
private static final String NO_ADDITIONAL_SECRET = "";
|
||||
private static final int GOAL_MILLISECONDS_PER_PASSWORD = 1000;
|
||||
private static final int HASH_WIDTH = 256;
|
||||
private static final int ITERATION_STEP = 5000;
|
||||
|
||||
/**
|
||||
* Finds the number of Iteration for the {@link Pbkdf2PasswordEncoder} to get the duration of
|
||||
* password encoding close to 1s. The Calculation does not use any secret (pepper) and applies
|
||||
* hash algorithm SHA256.
|
||||
*/
|
||||
public int calculateIteration() {
|
||||
/**
|
||||
* Finds the number of Iteration for the {@link Pbkdf2PasswordEncoder} to get the duration of
|
||||
* password encoding close to 1s. The Calculation does not use any secret (pepper) and applies
|
||||
* hash algorithm SHA256.
|
||||
*/
|
||||
public int calculateIteration() {
|
||||
|
||||
int iterationNumber = 150000;
|
||||
while (true) {
|
||||
Pbkdf2PasswordEncoder pbkdf2PasswordEncoder =
|
||||
new Pbkdf2PasswordEncoder(NO_ADDITIONAL_SECRET, iterationNumber, HASH_WIDTH);
|
||||
int iterationNumber = 150000;
|
||||
while (true) {
|
||||
Pbkdf2PasswordEncoder pbkdf2PasswordEncoder =
|
||||
new Pbkdf2PasswordEncoder(NO_ADDITIONAL_SECRET, iterationNumber, HASH_WIDTH);
|
||||
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
pbkdf2PasswordEncoder.encode(TEST_PASSWORD);
|
||||
stopwatch.stop();
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
pbkdf2PasswordEncoder.encode(TEST_PASSWORD);
|
||||
stopwatch.stop();
|
||||
|
||||
long duration = stopwatch.elapsed(TimeUnit.MILLISECONDS);
|
||||
if (duration > GOAL_MILLISECONDS_PER_PASSWORD) {
|
||||
return iterationNumber;
|
||||
}
|
||||
iterationNumber += ITERATION_STEP;
|
||||
long duration = stopwatch.elapsed(TimeUnit.MILLISECONDS);
|
||||
if (duration > GOAL_MILLISECONDS_PER_PASSWORD) {
|
||||
return iterationNumber;
|
||||
}
|
||||
iterationNumber += ITERATION_STEP;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user