make classes package private, apply google-java-formatter

This commit is contained in:
akuksin
2020-02-27 22:17:40 +01:00
parent ab5143cb2f
commit bfd004b73a
19 changed files with 299 additions and 296 deletions

View File

@@ -6,7 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class PasswordEncodingSpringBootApplication { public class PasswordEncodingSpringBootApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(PasswordEncodingSpringBootApplication.class, args); SpringApplication.run(PasswordEncodingSpringBootApplication.class, args);
} }
} }

View File

@@ -9,20 +9,20 @@ import org.springframework.transaction.annotation.Transactional;
@Service @Service
public class JdbcUserDetailPasswordService implements UserDetailsPasswordService { public class JdbcUserDetailPasswordService implements UserDetailsPasswordService {
private final UserRepository userRepository; private final UserRepository userRepository;
private final UserDetailsMapper userDetailsMapper; private final UserDetailsMapper userDetailsMapper;
public JdbcUserDetailPasswordService( public JdbcUserDetailPasswordService(
UserRepository userRepository, UserDetailsMapper userDetailsMapper) { UserRepository userRepository, UserDetailsMapper userDetailsMapper) {
this.userRepository = userRepository; this.userRepository = userRepository;
this.userDetailsMapper = userDetailsMapper; this.userDetailsMapper = userDetailsMapper;
} }
@Override @Override
public UserDetails updatePassword(UserDetails user, String newPassword) { public UserDetails updatePassword(UserDetails user, String newPassword) {
UserCredentials userCredentials = userRepository.findByUsername(user.getUsername()); UserCredentials userCredentials = userRepository.findByUsername(user.getUsername());
userCredentials.setPassword(newPassword); userCredentials.setPassword(newPassword);
return userDetailsMapper.toUserDetails(userCredentials); return userDetailsMapper.toUserDetails(userCredentials);
} }
} }

View File

@@ -10,19 +10,19 @@ import org.springframework.transaction.annotation.Transactional;
@Transactional @Transactional
public class JdbcUserDetailsService implements UserDetailsService { public class JdbcUserDetailsService implements UserDetailsService {
private final UserRepository userRepository; private final UserRepository userRepository;
private final UserDetailsMapper userDetailsMapper; private final UserDetailsMapper userDetailsMapper;
public JdbcUserDetailsService( public JdbcUserDetailsService(
UserRepository userRepository, UserDetailsMapper userDetailsMapper) { UserRepository userRepository, UserDetailsMapper userDetailsMapper) {
this.userRepository = userRepository; this.userRepository = userRepository;
this.userDetailsMapper = userDetailsMapper; this.userDetailsMapper = userDetailsMapper;
} }
@Override @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserCredentials userCredentials = userRepository.findByUsername(username); UserCredentials userCredentials = userRepository.findByUsername(username);
return userDetailsMapper.toUserDetails(userCredentials); return userDetailsMapper.toUserDetails(userCredentials);
} }
} }

View File

@@ -16,17 +16,16 @@ import java.util.Set;
@Table(name = "users") @Table(name = "users")
public class UserCredentials { public class UserCredentials {
@Id @Id private String username;
private String username;
private String password; private String password;
boolean enabled; boolean enabled;
@ElementCollection @ElementCollection
@JoinTable( @JoinTable(
name = "authorities", name = "authorities",
joinColumns = {@JoinColumn(name = "username")}) joinColumns = {@JoinColumn(name = "username")})
@Column(name = "authority") @Column(name = "authority")
private Set<String> roles; private Set<String> roles;
} }

View File

@@ -5,13 +5,13 @@ import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class UserDetailsMapper { class UserDetailsMapper {
UserDetails toUserDetails(UserCredentials userCredentials) { UserDetails toUserDetails(UserCredentials userCredentials) {
return User.withUsername(userCredentials.getUsername()) return User.withUsername(userCredentials.getUsername())
.password(userCredentials.getPassword()) .password(userCredentials.getPassword())
.roles(userCredentials.getRoles().toArray(String[]::new)) .roles(userCredentials.getRoles().toArray(String[]::new))
.build(); .build();
} }
} }

View File

@@ -6,5 +6,5 @@ import org.springframework.transaction.annotation.Transactional;
@Transactional @Transactional
public interface UserRepository extends JpaRepository<UserCredentials, String> { public interface UserRepository extends JpaRepository<UserCredentials, String> {
UserCredentials findByUsername(String username); UserCredentials findByUsername(String username);
} }

View File

@@ -26,67 +26,74 @@ import java.util.Map;
@EnableWebSecurity @EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter { public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final BcCryptWorkFactorService bcCryptWorkFactorService; private final BcCryptWorkFactorService bcCryptWorkFactorService;
private final JdbcUserDetailsService jdbcUserDetailsService; private final JdbcUserDetailsService jdbcUserDetailsService;
private final JdbcUserDetailPasswordService jdbcUserDetailPasswordService; private final JdbcUserDetailPasswordService jdbcUserDetailPasswordService;
public SecurityConfiguration( public SecurityConfiguration(
BcCryptWorkFactorService bcCryptWorkFactorService, BcCryptWorkFactorService bcCryptWorkFactorService,
JdbcUserDetailsService jdbcUserDetailsService, JdbcUserDetailsService jdbcUserDetailsService,
JdbcUserDetailPasswordService jdbcUserDetailPasswordService) { JdbcUserDetailPasswordService jdbcUserDetailPasswordService) {
this.bcCryptWorkFactorService = bcCryptWorkFactorService; this.bcCryptWorkFactorService = bcCryptWorkFactorService;
this.jdbcUserDetailsService = jdbcUserDetailsService; this.jdbcUserDetailsService = jdbcUserDetailsService;
this.jdbcUserDetailPasswordService = jdbcUserDetailPasswordService; this.jdbcUserDetailPasswordService = jdbcUserDetailPasswordService;
} }
@Override @Override
protected void configure(HttpSecurity httpSecurity) throws Exception { protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity httpSecurity
.csrf() .csrf()
.disable() .disable()
.authorizeRequests() .authorizeRequests()
.antMatchers("/registration") .antMatchers("/registration")
.permitAll() .permitAll()
.anyRequest() .anyRequest()
.authenticated() .authenticated()
.and() .and()
.httpBasic(); .httpBasic();
httpSecurity.headers().frameOptions().disable(); httpSecurity.headers().frameOptions().disable();
} }
@Autowired @Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider()) auth.authenticationProvider(daoAuthenticationProvider()).eraseCredentials(false);
.eraseCredentials(false); }
}
@Bean @Bean
public PasswordEncoder passwordEncoder() { public PasswordEncoder passwordEncoder() {
// we must user deprecated encoder to support their encoding // we must user deprecated encoder to support their encoding
String encodingId = "bcrypt"; String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>(); Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder(bcCryptWorkFactorService.calculateStrength())); encoders.put(
encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder()); encodingId, new BCryptPasswordEncoder(bcCryptWorkFactorService.calculateStrength()));
encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder()); encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5")); encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance()); encoders.put(
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder()); "MD5",
encoders.put("scrypt", new SCryptPasswordEncoder()); new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1")); encoders.put(
encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256")); "noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder()); encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("argon2", new Argon2PasswordEncoder()); 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);
} }
public AuthenticationProvider daoAuthenticationProvider() {
public AuthenticationProvider daoAuthenticationProvider() { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder()); daoAuthenticationProvider.setUserDetailsPasswordService(this.jdbcUserDetailPasswordService);
daoAuthenticationProvider.setUserDetailsPasswordService(this.jdbcUserDetailPasswordService); daoAuthenticationProvider.setUserDetailsService(this.jdbcUserDetailsService);
daoAuthenticationProvider.setUserDetailsService(this.jdbcUserDetailsService); return daoAuthenticationProvider;
return daoAuthenticationProvider; }
}
} }

View File

@@ -2,17 +2,17 @@ package io.reflectoring.passwordencoding.encoder;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder; import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
public class Argon2Example { class Argon2Example {
public String encode(String plainPassword) { public String encode(String plainPassword) {
int saltLength = 16; // salt length in bytes int saltLength = 16; // salt length in bytes
int hashLength = 32; // hash length in bytes int hashLength = 32; // hash length in bytes
int parallelism = 1; // currently is not supported int parallelism = 1; // currently is not supported
int memory = 4096; // memory costs int memory = 4096; // memory costs
int iterations = 3; int iterations = 3;
Argon2PasswordEncoder argon2PasswordEncoder = Argon2PasswordEncoder argon2PasswordEncoder =
new Argon2PasswordEncoder(saltLength, hashLength, parallelism, memory, iterations); new Argon2PasswordEncoder(saltLength, hashLength, parallelism, memory, iterations);
return argon2PasswordEncoder.encode(plainPassword); return argon2PasswordEncoder.encode(plainPassword);
} }
} }

View File

@@ -4,12 +4,12 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.security.SecureRandom; import java.security.SecureRandom;
public class BCryptExample { class BCryptExample {
public String encode(String plainPassword) { public String encode(String plainPassword) {
int strength = 10; int strength = 10;
BCryptPasswordEncoder bCryptPasswordEncoder = BCryptPasswordEncoder bCryptPasswordEncoder =
new BCryptPasswordEncoder(strength, new SecureRandom()); new BCryptPasswordEncoder(strength, new SecureRandom());
return bCryptPasswordEncoder.encode(plainPassword); return bCryptPasswordEncoder.encode(plainPassword);
} }
} }

View File

@@ -2,16 +2,16 @@ package io.reflectoring.passwordencoding.encoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
public class Pbkdf2Example { class Pbkdf2Example {
public String encode(String plainPassword) { public String encode(String plainPassword) {
String pepper = "pepper"; // secret key used by password encoding String pepper = "pepper"; // secret key used by password encoding
int iterations = 200000; // number of hash iteration int iterations = 200000; // number of hash iteration
int hashWidth = 256; // hash with in bits int hashWidth = 256; // hash with in bits
Pbkdf2PasswordEncoder pbkdf2PasswordEncoder = Pbkdf2PasswordEncoder pbkdf2PasswordEncoder =
new Pbkdf2PasswordEncoder(pepper, iterations, hashWidth); new Pbkdf2PasswordEncoder(pepper, iterations, hashWidth);
return pbkdf2PasswordEncoder.encode(plainPassword); return pbkdf2PasswordEncoder.encode(plainPassword);
} }
} }

View File

@@ -2,17 +2,17 @@ package io.reflectoring.passwordencoding.encoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder; import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
public class SCryptExample { class SCryptExample {
public String encode(String plainPassword) { public String encode(String plainPassword) {
int cpuCost = (int) Math.pow(2, 14); // factor to increase CPU costs int cpuCost = (int) Math.pow(2, 14); // factor to increase CPU costs
int memoryCost = 8; // factor to increases memory usage int memoryCost = 8; // factor to increases memory usage
int parallelization = 1; // currently nor supported by Spring Security int parallelization = 1; // currently nor supported by Spring Security
int keyLength = 32; // key length in bytes int keyLength = 32; // key length in bytes
int saltLength = 64; // salt length in bytes int saltLength = 64; // salt length in bytes
SCryptPasswordEncoder sCryptPasswordEncoder = SCryptPasswordEncoder sCryptPasswordEncoder =
new SCryptPasswordEncoder(cpuCost, memoryCost, parallelization, keyLength, saltLength); new SCryptPasswordEncoder(cpuCost, memoryCost, parallelization, keyLength, saltLength);
return sCryptPasswordEncoder.encode(plainPassword); return sCryptPasswordEncoder.encode(plainPassword);
} }
} }

View File

@@ -11,21 +11,21 @@ import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class PasswordMigration { class PasswordMigration {
@Bean @Bean
public ApplicationListener<AuthenticationSuccessEvent> authenticationSuccessListener( public ApplicationListener<AuthenticationSuccessEvent> authenticationSuccessListener(
PasswordEncoder encoder, UserDetailsPasswordService userDetailsPasswordService) { PasswordEncoder encoder, UserDetailsPasswordService userDetailsPasswordService) {
return (AuthenticationSuccessEvent event) -> { return (AuthenticationSuccessEvent event) -> {
Authentication authentication = event.getAuthentication(); Authentication authentication = event.getAuthentication();
User user = (User) authentication.getPrincipal(); User user = (User) authentication.getPrincipal();
String encodedPassword = user.getPassword(); String encodedPassword = user.getPassword();
if (encodedPassword.startsWith("{SHA-1}")) { if (encodedPassword.startsWith("{SHA-1}")) {
CharSequence clearTextPassword = (CharSequence) authentication.getCredentials(); CharSequence clearTextPassword = (CharSequence) authentication.getCredentials();
String newPassword = encoder.encode(clearTextPassword); String newPassword = encoder.encode(clearTextPassword);
userDetailsPasswordService.updatePassword(user, newPassword); userDetailsPasswordService.updatePassword(user, newPassword);
} }
((UsernamePasswordAuthenticationToken) authentication).eraseCredentials(); ((UsernamePasswordAuthenticationToken) authentication).eraseCredentials();
}; };
} }
} }

View File

@@ -9,8 +9,8 @@ import lombok.NoArgsConstructor;
@Data @Data
@Builder @Builder
@AllArgsConstructor @AllArgsConstructor
public class Car { class Car {
private String name; private String name;
private String color; private String color;
} }

View File

@@ -6,12 +6,10 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.Set; import java.util.Set;
@RestController @RestController
public class CarResources { class CarResources {
@GetMapping("/cars") @GetMapping("/cars")
public Set<Car> cars() { public Set<Car> cars() {
return Set.of( return Set.of(new Car("vw", "black"), new Car("bmw", "white"));
new Car("vw", "black"), }
new Car("bmw", "white"));
}
} }

View File

@@ -9,8 +9,7 @@ import lombok.NoArgsConstructor;
@Builder @Builder
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class UserCredentialsDto { class UserCredentialsDto {
private String username;
private String username; private String password;
private String password;
} }

View File

@@ -14,26 +14,26 @@ import java.util.Set;
@RestController @RestController
@Transactional @Transactional
public class UserResources { class UserResources {
private final UserRepository userRepository; private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder; private final PasswordEncoder passwordEncoder;
public UserResources(UserRepository userRepository, PasswordEncoder passwordEncoder) { public UserResources(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository; this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder; this.passwordEncoder = passwordEncoder;
} }
@PostMapping("/registration") @PostMapping("/registration")
@ResponseStatus(code = HttpStatus.CREATED) @ResponseStatus(code = HttpStatus.CREATED)
public void register(@RequestBody UserCredentialsDto userCredentialsDto) { public void register(@RequestBody UserCredentialsDto userCredentialsDto) {
UserCredentials user = UserCredentials user =
UserCredentials.builder() UserCredentials.builder()
.enabled(true) .enabled(true)
.username(userCredentialsDto.getUsername()) .username(userCredentialsDto.getUsername())
.password(passwordEncoder.encode(userCredentialsDto.getPassword())) .password(passwordEncoder.encode(userCredentialsDto.getPassword()))
.roles(Set.of("USER")) .roles(Set.of("USER"))
.build(); .build();
userRepository.save(user); userRepository.save(user);
} }
} }

View File

@@ -9,93 +9,93 @@ import java.util.concurrent.TimeUnit;
@Component @Component
public class BcCryptWorkFactorService { public class BcCryptWorkFactorService {
private static final String TEST_PASSWORD = "my password"; private static final String TEST_PASSWORD = "my password";
private static final int GOAL_MILLISECONDS_PER_PASSWORD = 1000; private static final int GOAL_MILLISECONDS_PER_PASSWORD = 1000;
private static final int MIN_STRENGTH = 4; private static final int MIN_STRENGTH = 4;
private static final int MAX_STRENGTH = 31; private static final int MAX_STRENGTH = 31;
/** /**
* Calculates the strength (a.k.a. log rounds) for the BCrypt Algorithm, so that password encoding * 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. * takes about 1s. This method uses the divide-and-conquer algorithm.
*/ */
public BcryptWorkFactor calculateStrengthDivideAndConquer() { public BcryptWorkFactor calculateStrengthDivideAndConquer() {
return calculateStrengthDivideAndConquer( return calculateStrengthDivideAndConquer(
new BcryptWorkFactor(MIN_STRENGTH, Integer.MIN_VALUE), new BcryptWorkFactor(MIN_STRENGTH, Integer.MIN_VALUE),
new BcryptWorkFactor(MAX_STRENGTH, Integer.MAX_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);
} }
int midStrength =
private BcryptWorkFactor calculateStrengthDivideAndConquer( (bigFactor.getStrength() - smallFactor.getStrength()) / 2 + smallFactor.getStrength();
BcryptWorkFactor smallFactor, BcryptWorkFactor bigFactor) { long duration = calculateDuration(midStrength);
if (bigFactor.getStrength() - smallFactor.getStrength() == 1) { BcryptWorkFactor midFactor = new BcryptWorkFactor(midStrength, duration);
return getClosestStrength(smallFactor, bigFactor); if (duration < GOAL_MILLISECONDS_PER_PASSWORD) {
} return calculateStrengthDivideAndConquer(midFactor, 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( private BcryptWorkFactor getClosestStrength(
BcryptWorkFactor smallFactor, BcryptWorkFactor bigFactor) { BcryptWorkFactor smallFactor, BcryptWorkFactor bigFactor) {
if (isPreviousDurationCloserToGoal(smallFactor.getDuration(), bigFactor.getDuration())) { if (isPreviousDurationCloserToGoal(smallFactor.getDuration(), bigFactor.getDuration())) {
return smallFactor; return smallFactor;
}
return bigFactor;
} }
return bigFactor;
}
private long calculateDuration(int strength) { private long calculateDuration(int strength) {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(strength); BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(strength);
Stopwatch stopwatch = Stopwatch.createStarted(); Stopwatch stopwatch = Stopwatch.createStarted();
bCryptPasswordEncoder.encode(TEST_PASSWORD); bCryptPasswordEncoder.encode(TEST_PASSWORD);
stopwatch.stop(); stopwatch.stop();
return stopwatch.elapsed(TimeUnit.MILLISECONDS); 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;
}
} }
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));
}
/** /**
* Calculates the strength (a.k.a. log rounds) for the BCrypt Algorithm, so that password encoding * @param previousDuration duration from previous iteration
* takes about 1s. This method iterates over strength from 4 to 31 and calculates the duration of * @param currentDuration duration of current iteration
* password encoding for every value of strength. It returns the first strength, that takes more * @param strength current strength
* than 1s * @return return the current strength, if current duration is closer to
*/ * GOAL_MILLISECONDS_PER_PASSWORD, otherwise current strength-1.
public int calculateStrength() { */
for (int strength = MIN_STRENGTH; strength <= MAX_STRENGTH; strength++) { int getStrength(long previousDuration, long currentDuration, int strength) {
if (isPreviousDurationCloserToGoal(previousDuration, currentDuration)) {
long duration = calculateDuration(strength); return strength - 1;
if (duration >= GOAL_MILLISECONDS_PER_PASSWORD) { } else {
return strength; 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));
} }
}
/** /**
* @param previousDuration duration from previous iteration * return true, if previousDuration is closer to the goal than currentDuration, false otherwise.
* @param currentDuration duration of current iteration */
* @param strength current strength boolean isPreviousDurationCloserToGoal(long previousDuration, long currentDuration) {
* @return return the current strength, if current duration is closer to return Math.abs(GOAL_MILLISECONDS_PER_PASSWORD - previousDuration)
* GOAL_MILLISECONDS_PER_PASSWORD, otherwise current strength-1. < Math.abs(GOAL_MILLISECONDS_PER_PASSWORD - currentDuration);
*/ }
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);
}
} }

View File

@@ -5,8 +5,8 @@ import lombok.Data;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
public class BcryptWorkFactor { class BcryptWorkFactor {
private int strength; private int strength;
private long duration; private long duration;
} }

View File

@@ -7,35 +7,35 @@ import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@Component @Component
public class Pbkdf2WorkFactorService { class Pbkdf2WorkFactorService {
private static final String TEST_PASSWORD = "my password"; private static final String TEST_PASSWORD = "my password";
private static final String NO_ADDITIONAL_SECRET = ""; private static final String NO_ADDITIONAL_SECRET = "";
private static final int GOAL_MILLISECONDS_PER_PASSWORD = 1000; private static final int GOAL_MILLISECONDS_PER_PASSWORD = 1000;
private static final int HASH_WIDTH = 256; private static final int HASH_WIDTH = 256;
private static final int ITERATION_STEP = 5000; private static final int ITERATION_STEP = 5000;
/** /**
* Finds the number of Iteration for the {@link Pbkdf2PasswordEncoder} to get the duration of * 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 * password encoding close to 1s. The Calculation does not use any secret (pepper) and applies
* hash algorithm SHA256. * hash algorithm SHA256.
*/ */
public int calculateIteration() { public int calculateIteration() {
int iterationNumber = 150000; int iterationNumber = 150000;
while (true) { while (true) {
Pbkdf2PasswordEncoder pbkdf2PasswordEncoder = Pbkdf2PasswordEncoder pbkdf2PasswordEncoder =
new Pbkdf2PasswordEncoder(NO_ADDITIONAL_SECRET, iterationNumber, HASH_WIDTH); new Pbkdf2PasswordEncoder(NO_ADDITIONAL_SECRET, iterationNumber, HASH_WIDTH);
Stopwatch stopwatch = Stopwatch.createStarted(); Stopwatch stopwatch = Stopwatch.createStarted();
pbkdf2PasswordEncoder.encode(TEST_PASSWORD); pbkdf2PasswordEncoder.encode(TEST_PASSWORD);
stopwatch.stop(); stopwatch.stop();
long duration = stopwatch.elapsed(TimeUnit.MILLISECONDS); long duration = stopwatch.elapsed(TimeUnit.MILLISECONDS);
if (duration > GOAL_MILLISECONDS_PER_PASSWORD) { if (duration > GOAL_MILLISECONDS_PER_PASSWORD) {
return iterationNumber; return iterationNumber;
} }
iterationNumber += ITERATION_STEP; iterationNumber += ITERATION_STEP;
}
} }
}
} }