add calculation of bcryt strength with Divide-and-conquer algorithm
This commit is contained in:
@@ -6,8 +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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(UserRepository userRepository, UserDetailsMapper userDetailsMapper) {
|
public JdbcUserDetailPasswordService(
|
||||||
this.userRepository = userRepository;
|
UserRepository userRepository, UserDetailsMapper userDetailsMapper) {
|
||||||
this.userDetailsMapper = userDetailsMapper;
|
this.userRepository = userRepository;
|
||||||
}
|
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);
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(UserRepository userRepository, UserDetailsMapper userDetailsMapper) {
|
public JdbcUserDetailsService(
|
||||||
this.userRepository = userRepository;
|
UserRepository userRepository, UserDetailsMapper userDetailsMapper) {
|
||||||
this.userDetailsMapper = userDetailsMapper;
|
this.userRepository = userRepository;
|
||||||
}
|
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);
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,16 +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(name = "authorities", joinColumns = {@JoinColumn(name = "username")})
|
@JoinTable(
|
||||||
@Column(name = "authority")
|
name = "authorities",
|
||||||
private Set<String> roles;
|
joinColumns = {@JoinColumn(name = "username")})
|
||||||
|
@Column(name = "authority")
|
||||||
|
private Set<String> roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,11 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component
|
@Component
|
||||||
public class UserDetailsMapper {
|
public class UserDetailsMapper {
|
||||||
|
|
||||||
UserDetails toUserDetails(UserCredentials userCredentials) {
|
UserDetails toUserDetails(UserCredentials userCredentials) {
|
||||||
|
|
||||||
return User
|
return User.withUsername(userCredentials.getUsername())
|
||||||
.withUsername(userCredentials.getUsername())
|
.password(userCredentials.getPassword())
|
||||||
.password(userCredentials.getPassword())
|
.roles(userCredentials.getRoles().toArray(String[]::new))
|
||||||
.roles(userCredentials.getRoles().toArray(String[]::new))
|
.build();
|
||||||
.build();
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,73 +29,84 @@ import java.util.Map;
|
|||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final UserDetailsMapper userDetailsMapper;
|
||||||
|
private final BcCryptWorkFactorService bcCryptWorkFactorService;
|
||||||
|
|
||||||
private final UserRepository userRepository;
|
public SecurityConfiguration(
|
||||||
private final UserDetailsMapper userDetailsMapper;
|
UserRepository userRepository,
|
||||||
private final BcCryptWorkFactorService bcCryptWorkFactorService;
|
UserDetailsMapper userDetailsMapper,
|
||||||
|
BcCryptWorkFactorService bcCryptWorkFactorService) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.userDetailsMapper = userDetailsMapper;
|
||||||
|
this.bcCryptWorkFactorService = bcCryptWorkFactorService;
|
||||||
|
}
|
||||||
|
|
||||||
public SecurityConfiguration(UserRepository userRepository, UserDetailsMapper userDetailsMapper, BcCryptWorkFactorService bcCryptWorkFactorService) {
|
@Override
|
||||||
this.userRepository = userRepository;
|
protected void configure(HttpSecurity httpSecurity) throws Exception {
|
||||||
this.userDetailsMapper = userDetailsMapper;
|
httpSecurity
|
||||||
this.bcCryptWorkFactorService = bcCryptWorkFactorService;
|
.csrf()
|
||||||
}
|
.disable()
|
||||||
|
.authorizeRequests()
|
||||||
|
.antMatchers("/registration")
|
||||||
|
.permitAll()
|
||||||
|
.anyRequest()
|
||||||
|
.authenticated()
|
||||||
|
.and()
|
||||||
|
.httpBasic();
|
||||||
|
|
||||||
@Override
|
httpSecurity.headers().frameOptions().disable();
|
||||||
protected void configure(HttpSecurity httpSecurity) throws Exception {
|
}
|
||||||
httpSecurity
|
|
||||||
.csrf().disable()
|
|
||||||
.authorizeRequests()
|
|
||||||
.antMatchers("/registration").permitAll()
|
|
||||||
.anyRequest().authenticated()
|
|
||||||
.and()
|
|
||||||
.httpBasic();
|
|
||||||
|
|
||||||
httpSecurity.headers().frameOptions().disable();
|
@Autowired
|
||||||
}
|
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||||
|
auth.authenticationProvider(daoAuthenticationProvider()).eraseCredentials(false);
|
||||||
|
}
|
||||||
|
|
||||||
@Autowired
|
@Bean
|
||||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
public PasswordEncoder passwordEncoder() {
|
||||||
auth
|
// we must user deprecated encoder to support their encoding
|
||||||
.authenticationProvider(daoAuthenticationProvider())
|
String encodingId = "bcrypt";
|
||||||
.eraseCredentials(false);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public UserDetailsPasswordService userDetailsPasswordService() {
|
||||||
// we must user deprecated encoder to support their encoding
|
return new JdbcUserDetailPasswordService(userRepository, userDetailsMapper);
|
||||||
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);
|
public UserDetailsService userDetailsService() {
|
||||||
}
|
return new JdbcUserDetailsService(userRepository, userDetailsMapper);
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public UserDetailsPasswordService userDetailsPasswordService() {
|
public DaoAuthenticationProvider daoAuthenticationProvider() {
|
||||||
return new JdbcUserDetailPasswordService(userRepository, userDetailsMapper);
|
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
|
||||||
}
|
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
|
||||||
|
daoAuthenticationProvider.setUserDetailsPasswordService(userDetailsPasswordService());
|
||||||
public UserDetailsService userDetailsService() {
|
daoAuthenticationProvider.setUserDetailsService(userDetailsService());
|
||||||
return new JdbcUserDetailsService(userRepository, userDetailsMapper);
|
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 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) {
|
Argon2PasswordEncoder argon2PasswordEncoder =
|
||||||
int saltLength = 16; // salt length in bytes
|
new Argon2PasswordEncoder(saltLength, hashLength, parallelism, memory, iterations);
|
||||||
int hashLength = 32; // hash length in bytes
|
return argon2PasswordEncoder.encode(plainPassword);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import java.security.SecureRandom;
|
|||||||
|
|
||||||
public class BCryptExample {
|
public class BCryptExample {
|
||||||
|
|
||||||
public String encode(String plainPassword) {
|
public String encode(String plainPassword) {
|
||||||
int strength = 10;
|
int strength = 10;
|
||||||
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(strength, new SecureRandom());
|
BCryptPasswordEncoder bCryptPasswordEncoder =
|
||||||
return bCryptPasswordEncoder.encode(plainPassword);
|
new BCryptPasswordEncoder(strength, new SecureRandom());
|
||||||
}
|
return bCryptPasswordEncoder.encode(plainPassword);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
|
|||||||
|
|
||||||
public class Pbkdf2Example {
|
public 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 = new Pbkdf2PasswordEncoder(pepper, iterations, hashWidth);
|
Pbkdf2PasswordEncoder pbkdf2PasswordEncoder =
|
||||||
return pbkdf2PasswordEncoder.encode(plainPassword);
|
new Pbkdf2PasswordEncoder(pepper, iterations, hashWidth);
|
||||||
}
|
return pbkdf2PasswordEncoder.encode(plainPassword);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,15 @@ import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
|
|||||||
|
|
||||||
public class SCryptExample {
|
public 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 = new SCryptPasswordEncoder(cpuCost, memoryCost, parallelization, keyLength, saltLength);
|
SCryptPasswordEncoder sCryptPasswordEncoder =
|
||||||
return sCryptPasswordEncoder.encode(plainPassword);
|
new SCryptPasswordEncoder(cpuCost, memoryCost, parallelization, keyLength, saltLength);
|
||||||
}
|
return sCryptPasswordEncoder.encode(plainPassword);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,21 +13,19 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component
|
@Component
|
||||||
public class PasswordMigration {
|
public class PasswordMigration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public ApplicationListener<AuthenticationSuccessEvent> authenticationSuccessListener(
|
public ApplicationListener<AuthenticationSuccessEvent> authenticationSuccessListener(
|
||||||
PasswordEncoder encoder,
|
PasswordEncoder encoder, UserDetailsPasswordService userDetailsPasswordService) {
|
||||||
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();
|
};
|
||||||
};
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ import lombok.NoArgsConstructor;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class Car {
|
public class Car {
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
private String color;
|
private String color;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,19 +8,11 @@ import java.util.Set;
|
|||||||
@RestController
|
@RestController
|
||||||
public class CarResources {
|
public class CarResources {
|
||||||
|
|
||||||
// we use this endpoint as authentication test
|
// we use this endpoint as authentication test
|
||||||
@GetMapping("/cars")
|
@GetMapping("/cars")
|
||||||
public Set<Car> cars() {
|
public Set<Car> cars() {
|
||||||
return Set.of(
|
return Set.of(
|
||||||
Car.builder()
|
Car.builder().name("vw").color("black").build(),
|
||||||
.name("vw")
|
Car.builder().name("bmw").color("white").build());
|
||||||
.color("black")
|
}
|
||||||
.build(),
|
|
||||||
Car.builder()
|
|
||||||
.name("bmw")
|
|
||||||
.color("white")
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ import lombok.NoArgsConstructor;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class UserCredentialsDto {
|
public class UserCredentialsDto {
|
||||||
|
|
||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,24 +16,24 @@ import java.util.Set;
|
|||||||
@Transactional
|
@Transactional
|
||||||
public class UserResources {
|
public 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,84 +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
|
||||||
|
* 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(
|
||||||
* Calculates the strength (a.k.a. log rounds) for the BCrypt Algorithm, so that password encoding takes about 1s.
|
BcryptWorkFactor smallFactor, BcryptWorkFactor bigFactor) {
|
||||||
* This method iterates over strength from 4 to 31 and calculates the duration of password encoding for every value of strength.
|
if (bigFactor.getStrength() - smallFactor.getStrength() == 1) {
|
||||||
* It returns the first strength, that takes more than 500s
|
return getClosestStrength(smallFactor, bigFactor);
|
||||||
*/
|
|
||||||
public int calculateStrength() {
|
|
||||||
for (int strength = MIN_STRENGTH; strength <= MAX_STRENGTH; strength++) {
|
|
||||||
|
|
||||||
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(strength);
|
|
||||||
|
|
||||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
|
||||||
bCryptPasswordEncoder.encode(TEST_PASSWORD);
|
|
||||||
stopwatch.stop();
|
|
||||||
|
|
||||||
long duration = stopwatch.elapsed(TimeUnit.MILLISECONDS);
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
int midStrength =
|
||||||
/**
|
(bigFactor.getStrength() - smallFactor.getStrength()) / 2 + smallFactor.getStrength();
|
||||||
* Calculates the strength (a.k.a. log rounds) for the BCrypt Algorithm, so that password encoding takes about 1s.
|
long duration = calculateDuration(midStrength);
|
||||||
* This method iterate over strength from 4 to 31 and calculates the duration of password encoding for every value of strength.
|
BcryptWorkFactor midFactor = new BcryptWorkFactor(midStrength, duration);
|
||||||
* When the the duration takes more than 1s, it is compared to previous one and the method returns the strength, tha is closer
|
if (duration < GOAL_MILLISECONDS_PER_PASSWORD) {
|
||||||
* to 1s.
|
return calculateStrengthDivideAndConquer(midFactor, bigFactor);
|
||||||
*/
|
|
||||||
public int calculateStrengthClosestToTimeGoal() {
|
|
||||||
|
|
||||||
long previousDuration = Long.MIN_VALUE;
|
|
||||||
for (int strength = MIN_STRENGTH; strength <= MAX_STRENGTH; strength++) {
|
|
||||||
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(strength);
|
|
||||||
|
|
||||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
|
||||||
bCryptPasswordEncoder.encode(TEST_PASSWORD);
|
|
||||||
stopwatch.stop();
|
|
||||||
long currentDuration = stopwatch.elapsed(TimeUnit.MILLISECONDS);
|
|
||||||
|
|
||||||
if (isGreaterThanGoal(currentDuration)) {
|
|
||||||
return getStrength(previousDuration, currentDuration, strength);
|
|
||||||
}
|
|
||||||
previousDuration = currentDuration;
|
|
||||||
}
|
|
||||||
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 calculateStrengthDivideAndConquer(smallFactor, midFactor);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
private BcryptWorkFactor getClosestStrength(
|
||||||
* @param previousDuration duration from previous iteration
|
BcryptWorkFactor smallFactor, BcryptWorkFactor bigFactor) {
|
||||||
* @param currentDuration duration of current iteration
|
if (isPreviousDurationCloserToGoal(smallFactor.getDuration(), bigFactor.getDuration())) {
|
||||||
* @param strength current strength
|
return smallFactor;
|
||||||
* @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 bigFactor;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isGreaterThanGoal(long duration) {
|
private long calculateDuration(int strength) {
|
||||||
return duration > GOAL_MILLISECONDS_PER_PASSWORD;
|
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(strength);
|
||||||
}
|
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||||
|
bCryptPasswordEncoder.encode(TEST_PASSWORD);
|
||||||
|
stopwatch.stop();
|
||||||
|
return stopwatch.elapsed(TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return true, if previousDuration is closer to the goal than currentDuration, false otherwise.
|
* 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
|
||||||
boolean isPreviousDurationCloserToGoal(long previousDuration, long currentDuration) {
|
* password encoding for every value of strength. It returns the first strength, that takes more
|
||||||
return Math.abs(GOAL_MILLISECONDS_PER_PASSWORD - previousDuration) < Math.abs(GOAL_MILLISECONDS_PER_PASSWORD - currentDuration);
|
* 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package io.reflectoring.passwordencoding.workfactor;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class BcryptWorkFactor {
|
||||||
|
|
||||||
|
private int strength;
|
||||||
|
private long duration;
|
||||||
|
}
|
||||||
@@ -9,31 +9,33 @@ import java.util.concurrent.TimeUnit;
|
|||||||
@Component
|
@Component
|
||||||
public class Pbkdf2WorkFactorService {
|
public 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 password encoding
|
* Finds the number of Iteration for the {@link Pbkdf2PasswordEncoder} to get the duration of
|
||||||
* close to 1s. The Calculation does not use any secret (pepper) and applies hash algorithm SHA256.
|
* password encoding close to 1s. The Calculation does not use any secret (pepper) and applies
|
||||||
*/
|
* hash algorithm SHA256.
|
||||||
public int calculateIteration() {
|
*/
|
||||||
|
public int calculateIteration() {
|
||||||
|
|
||||||
int iterationNumber = 150000;
|
int iterationNumber = 150000;
|
||||||
while (true) {
|
while (true) {
|
||||||
Pbkdf2PasswordEncoder pbkdf2PasswordEncoder = new Pbkdf2PasswordEncoder(NO_ADDITIONAL_SECRET, iterationNumber, HASH_WIDTH);
|
Pbkdf2PasswordEncoder pbkdf2PasswordEncoder =
|
||||||
|
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;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
insert into users (username, password, enabled) VALUES ('admin', '{bcrypt}$2a$10$4V9kA793Pi2xf94dYFgKWuw8ukyETxWb7tZ4/mfco9sWkwvBQndxW', true);
|
insert into users (username, password, enabled)
|
||||||
insert into users (username, password, enabled) VALUES ('user', '{SHA-256}{4Cc0+yDMHnTUy+zOHeMH7yaPhxvlJT//tQTwEhyegiQ=}446d06130bfc254527a7bbd95b50595a977c0058110f8dccb54bd273d99325b8', true);
|
VALUES ('admin', '{bcrypt}$2a$10$4V9kA793Pi2xf94dYFgKWuw8ukyETxWb7tZ4/mfco9sWkwvBQndxW', true);
|
||||||
insert into users (username, password, enabled) VALUES ('user with working factor 5', '{bcrypt}$2a$05$Zz4rToG8YXKMbuAPgm3qj.HpTFsGEdZHhCf9ikIHAoI5elX7ajNm.', true);
|
insert into users (username, password, enabled)
|
||||||
insert into users (username, password, enabled) VALUES ('user with sha1 encoding', '{SHA-1}{6tND0AZfFH3aE1VDg7QkWT6DzFg/NUHtukntgwu8JV4=}804c6e8efebf4e91f88e3baf9fd383e28a21378c', true);
|
VALUES ('user',
|
||||||
insert into users (username, password, enabled) VALUES ('scrypt user', '{scrypt}$e0801$fUx3MxN07zdH3UyARJqOwv3WiWCvE7f6qRm9A5KQfNo5ovSwxMHknQ4vERO4csj/I3imG2HJQg1HHp7Rqzbp7g==$Fm5F9PSoE/jBYLOmnCJcvX1Euf952r5b3BjAl+SwQMs=', true);
|
'{SHA-256}{4Cc0+yDMHnTUy+zOHeMH7yaPhxvlJT//tQTwEhyegiQ=}446d06130bfc254527a7bbd95b50595a977c0058110f8dccb54bd273d99325b8',
|
||||||
|
true);
|
||||||
|
insert into users (username, password, enabled)
|
||||||
|
VALUES ('user with working factor 5', '{bcrypt}$2a$05$Zz4rToG8YXKMbuAPgm3qj.HpTFsGEdZHhCf9ikIHAoI5elX7ajNm.', true);
|
||||||
|
insert into users (username, password, enabled)
|
||||||
|
VALUES ('user with sha1 encoding',
|
||||||
|
'{SHA-1}{6tND0AZfFH3aE1VDg7QkWT6DzFg/NUHtukntgwu8JV4=}804c6e8efebf4e91f88e3baf9fd383e28a21378c', true);
|
||||||
|
insert into users (username, password, enabled)
|
||||||
|
VALUES ('scrypt user',
|
||||||
|
'{scrypt}$e0801$fUx3MxN07zdH3UyARJqOwv3WiWCvE7f6qRm9A5KQfNo5ovSwxMHknQ4vERO4csj/I3imG2HJQg1HHp7Rqzbp7g==$Fm5F9PSoE/jBYLOmnCJcvX1Euf952r5b3BjAl+SwQMs=',
|
||||||
|
true);
|
||||||
@@ -9,24 +9,25 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
|||||||
|
|
||||||
class UserDetailsMapperTest {
|
class UserDetailsMapperTest {
|
||||||
|
|
||||||
private UserDetailsMapper userDetailsMapper = new UserDetailsMapper();
|
private UserDetailsMapper userDetailsMapper = new UserDetailsMapper();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void toUserDetails() {
|
void toUserDetails() {
|
||||||
// given
|
// given
|
||||||
UserCredentials userCredentials = UserCredentials.builder()
|
UserCredentials userCredentials =
|
||||||
.enabled(true)
|
UserCredentials.builder()
|
||||||
.password("password")
|
.enabled(true)
|
||||||
.username("user")
|
.password("password")
|
||||||
.roles(Set.of("USER", "ADMIN"))
|
.username("user")
|
||||||
.build();
|
.roles(Set.of("USER", "ADMIN"))
|
||||||
|
.build();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
UserDetails userDetails = userDetailsMapper.toUserDetails(userCredentials);
|
UserDetails userDetails = userDetailsMapper.toUserDetails(userCredentials);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(userDetails.getUsername()).isEqualTo("user");
|
assertThat(userDetails.getUsername()).isEqualTo("user");
|
||||||
assertThat(userDetails.getPassword()).isEqualTo("password");
|
assertThat(userDetails.getPassword()).isEqualTo("password");
|
||||||
assertThat(userDetails.isEnabled()).isTrue();
|
assertThat(userDetails.isEnabled()).isTrue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,18 +9,17 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
|||||||
@DataJpaTest
|
@DataJpaTest
|
||||||
class UserRepositoryTest {
|
class UserRepositoryTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired private UserRepository userRepository;
|
||||||
private UserRepository userRepository;
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void findUserByUsername() {
|
void findUserByUsername() {
|
||||||
// given
|
// given
|
||||||
String username = "user";
|
String username = "user";
|
||||||
|
|
||||||
// when
|
// when
|
||||||
UserCredentials userCredentials = userRepository.findByUsername(username);
|
UserCredentials userCredentials = userRepository.findByUsername(username);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(userCredentials).isNotNull();
|
assertThat(userCredentials).isNotNull();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
|||||||
|
|
||||||
class Argon2ExampleTest {
|
class Argon2ExampleTest {
|
||||||
|
|
||||||
private Argon2Example argon2Example = new Argon2Example();
|
private Argon2Example argon2Example = new Argon2Example();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void encode() {
|
void encode() {
|
||||||
// given
|
// given
|
||||||
String plainPassword = "password";
|
String plainPassword = "password";
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String actual = argon2Example.encode(plainPassword);
|
String actual = argon2Example.encode(plainPassword);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(actual).startsWith("$argon2id$v=19$m=4096,t=3,p=1");
|
assertThat(actual).startsWith("$argon2id$v=19$m=4096,t=3,p=1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
|||||||
|
|
||||||
class BCryptExampleTest {
|
class BCryptExampleTest {
|
||||||
|
|
||||||
private BCryptExample bcryptExample = new BCryptExample();
|
private BCryptExample bcryptExample = new BCryptExample();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void encode() {
|
void encode() {
|
||||||
// given
|
// given
|
||||||
String plainPassword = "password";
|
String plainPassword = "password";
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String encoded = bcryptExample.encode(plainPassword);
|
String encoded = bcryptExample.encode(plainPassword);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(encoded).startsWith("$2a$10");
|
assertThat(encoded).startsWith("$2a$10");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
|||||||
|
|
||||||
class Pbkdf2ExampleTest {
|
class Pbkdf2ExampleTest {
|
||||||
|
|
||||||
private Pbkdf2Example pbkdf2Example = new Pbkdf2Example();
|
private Pbkdf2Example pbkdf2Example = new Pbkdf2Example();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void encode() {
|
void encode() {
|
||||||
// given
|
// given
|
||||||
String plainPassword = "plainPassword";
|
String plainPassword = "plainPassword";
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String actual = pbkdf2Example.encode(plainPassword);
|
String actual = pbkdf2Example.encode(plainPassword);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(actual).hasSize(80);
|
assertThat(actual).hasSize(80);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,18 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
|||||||
|
|
||||||
class SCryptExampleTest {
|
class SCryptExampleTest {
|
||||||
|
|
||||||
private SCryptExample sCryptExample = new SCryptExample();
|
private SCryptExample sCryptExample = new SCryptExample();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void encode() {
|
void encode() {
|
||||||
// given
|
// given
|
||||||
String plainPassword = "password";
|
String plainPassword = "password";
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String actual = sCryptExample.encode(plainPassword);
|
String actual = sCryptExample.encode(plainPassword);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(actual).hasSize(140);
|
assertThat(actual).hasSize(140);
|
||||||
assertThat(actual).startsWith("$e0801");
|
assertThat(actual).startsWith("$e0801");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,73 +22,70 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
@Transactional
|
@Transactional
|
||||||
class CarResourcesTest {
|
class CarResourcesTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired private MockMvc mockMvc;
|
||||||
private MockMvc mockMvc;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired private ObjectMapper objectMapper;
|
||||||
private ObjectMapper objectMapper;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired private UserRepository userRepository;
|
||||||
private UserRepository userRepository;
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getCarsShouldReturnUnauthorizedIfTheRequestHasNoBasicAuthentication() throws Exception {
|
void getCarsShouldReturnUnauthorizedIfTheRequestHasNoBasicAuthentication() throws Exception {
|
||||||
mockMvc.perform(get("/cars"))
|
mockMvc.perform(get("/cars")).andExpect(status().isUnauthorized());
|
||||||
.andExpect(status().isUnauthorized());
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getCarsShouldReturnCarsForTheAuthenticatedUser() throws Exception {
|
void getCarsShouldReturnCarsForTheAuthenticatedUser() throws Exception {
|
||||||
mockMvc.perform(get("/cars")
|
mockMvc.perform(get("/cars").with(httpBasic("user", "password"))).andExpect(status().isOk());
|
||||||
.with(httpBasic("user", "password")))
|
}
|
||||||
.andExpect(status().isOk());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void registrationShouldReturnCreated() throws Exception {
|
void registrationShouldReturnCreated() throws Exception {
|
||||||
|
|
||||||
// register
|
// register
|
||||||
UserCredentialsDto userCredentialsDto = UserCredentialsDto.builder().username("toyota").password("my secret").build();
|
UserCredentialsDto userCredentialsDto =
|
||||||
mockMvc.perform(post("/registration")
|
UserCredentialsDto.builder().username("toyota").password("my secret").build();
|
||||||
|
mockMvc
|
||||||
|
.perform(
|
||||||
|
post("/registration")
|
||||||
.contentType(MediaType.APPLICATION_JSON_VALUE)
|
.contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||||
.content(objectMapper.writeValueAsString(userCredentialsDto)))
|
.content(objectMapper.writeValueAsString(userCredentialsDto)))
|
||||||
.andExpect(status().isCreated());
|
.andExpect(status().isCreated());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void registrationShouldReturnUnauthorizedWithWrongCredentials() throws Exception {
|
void registrationShouldReturnUnauthorizedWithWrongCredentials() throws Exception {
|
||||||
|
|
||||||
mockMvc.perform(get("/cars")
|
mockMvc
|
||||||
.with(httpBasic("user", "wrong password")))
|
.perform(get("/cars").with(httpBasic("user", "wrong password")))
|
||||||
.andExpect(status().isUnauthorized());
|
.andExpect(status().isUnauthorized());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getCarsShouldUpdatePasswordFromWorkingFactor5toHigherValue() throws Exception {
|
void getCarsShouldUpdatePasswordFromWorkingFactor5toHigherValue() throws Exception {
|
||||||
mockMvc.perform(get("/cars")
|
mockMvc
|
||||||
.with(httpBasic("user with working factor 5", "password")))
|
.perform(get("/cars").with(httpBasic("user with working factor 5", "password")))
|
||||||
.andExpect(status().isOk());
|
.andExpect(status().isOk());
|
||||||
|
|
||||||
UserCredentials userCredentials = userRepository.findByUsername("user with working factor 5");
|
UserCredentials userCredentials = userRepository.findByUsername("user with working factor 5");
|
||||||
// we don't know what strength the BcCryptWorkFactorService returns,
|
// we don't know what strength the BcCryptWorkFactorService returns,
|
||||||
// but it should be more than 5
|
// but it should be more than 5
|
||||||
assertThat(userCredentials.getPassword()).doesNotStartWith("{bcrypt}$2a$05");
|
assertThat(userCredentials.getPassword()).doesNotStartWith("{bcrypt}$2a$05");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getCarsShouldUpdateSha1PasswordToBcrypt() throws Exception {
|
void getCarsShouldUpdateSha1PasswordToBcrypt() throws Exception {
|
||||||
mockMvc.perform(get("/cars")
|
mockMvc
|
||||||
.with(httpBasic("user with sha1 encoding", "password")))
|
.perform(get("/cars").with(httpBasic("user with sha1 encoding", "password")))
|
||||||
.andExpect(status().isOk());
|
.andExpect(status().isOk());
|
||||||
|
|
||||||
UserCredentials userCredentials = userRepository.findByUsername("user with sha1 encoding");
|
UserCredentials userCredentials = userRepository.findByUsername("user with sha1 encoding");
|
||||||
assertThat(userCredentials.getPassword()).startsWith("{bcrypt}");
|
assertThat(userCredentials.getPassword()).startsWith("{bcrypt}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getCarsShouldReturnOkForScryptUser() throws Exception {
|
void getCarsShouldReturnOkForScryptUser() throws Exception {
|
||||||
mockMvc.perform(get("/cars")
|
mockMvc
|
||||||
.with(httpBasic("scrypt user", "password")))
|
.perform(get("/cars").with(httpBasic("scrypt user", "password")))
|
||||||
.andExpect(status().isOk());
|
.andExpect(status().isOk());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,121 +1,119 @@
|
|||||||
package io.reflectoring.passwordencoding.workfactor;
|
package io.reflectoring.passwordencoding.workfactor;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||||
|
|
||||||
class BcCryptWorkFactorServiceTest {
|
class BcCryptWorkFactorServiceTest {
|
||||||
|
|
||||||
private BcCryptWorkFactorService bcCryptWorkFactorService = new BcCryptWorkFactorService();
|
private BcCryptWorkFactorService bcCryptWorkFactorService = new BcCryptWorkFactorService();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void calculateStrength() {
|
void calculateStrength() {
|
||||||
// given
|
// given
|
||||||
|
|
||||||
// when
|
// when
|
||||||
int strength = bcCryptWorkFactorService.calculateStrength();
|
int strength = bcCryptWorkFactorService.calculateStrength();
|
||||||
|
|
||||||
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(strength);
|
// then
|
||||||
|
assertThat(strength).isBetween(4, 31);
|
||||||
|
}
|
||||||
|
|
||||||
// then
|
@Test
|
||||||
assertThat(strength).isBetween(4, 31);
|
void calculateStrengthBi() {
|
||||||
}
|
// given
|
||||||
|
|
||||||
@Test
|
// when
|
||||||
void calculateRounds() {
|
BcryptWorkFactor bcryptWorkFactor =
|
||||||
// given
|
bcCryptWorkFactorService.calculateStrengthDivideAndConquer();
|
||||||
|
|
||||||
// when
|
// then
|
||||||
int strength = bcCryptWorkFactorService.calculateStrengthClosestToTimeGoal();
|
assertThat(bcryptWorkFactor.getStrength()).isBetween(4, 31);
|
||||||
|
}
|
||||||
|
|
||||||
// then
|
@Test
|
||||||
assertThat(strength).isBetween(4, 31);
|
void findCloserToShouldReturnNumber1IfItCloserToGoalThanNumber2() {
|
||||||
}
|
// given
|
||||||
|
int number1 = 950;
|
||||||
|
int number2 = 1051;
|
||||||
|
|
||||||
@Test
|
// when
|
||||||
void findCloserToShouldReturnNumber1IfItCloserToGoalThanNumber2() {
|
boolean actual = bcCryptWorkFactorService.isPreviousDurationCloserToGoal(number1, number2);
|
||||||
// given
|
|
||||||
int number1 = 950;
|
|
||||||
int number2 = 1051;
|
|
||||||
|
|
||||||
// when
|
// then
|
||||||
boolean actual = bcCryptWorkFactorService.isPreviousDurationCloserToGoal(number1, number2);
|
assertThat(actual).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
// then
|
@Test
|
||||||
assertThat(actual).isTrue();
|
void findCloserToShouldReturnNUmber2IfItCloserToGoalThanNumber1() {
|
||||||
}
|
// given
|
||||||
|
int number1 = 1002;
|
||||||
|
int number2 = 999;
|
||||||
|
|
||||||
@Test
|
// when
|
||||||
void findCloserToShouldReturnNUmber2IfItCloserToGoalThanNumber1() {
|
boolean actual = bcCryptWorkFactorService.isPreviousDurationCloserToGoal(number1, number2);
|
||||||
// given
|
|
||||||
int number1 = 1002;
|
|
||||||
int number2 = 999;
|
|
||||||
|
|
||||||
// when
|
// then
|
||||||
boolean actual = bcCryptWorkFactorService.isPreviousDurationCloserToGoal(number1, number2);
|
assertThat(actual).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
// then
|
@Test
|
||||||
assertThat(actual).isFalse();
|
void findCloserToShouldReturnGoalIfNumber2IsEqualGoal() {
|
||||||
}
|
// given
|
||||||
|
int number1 = 999;
|
||||||
|
int number2 = 1000;
|
||||||
|
|
||||||
@Test
|
// when
|
||||||
void findCloserToShouldReturnGoalIfNumber2IsEqualGoal() {
|
boolean actual = bcCryptWorkFactorService.isPreviousDurationCloserToGoal(number1, number2);
|
||||||
// given
|
|
||||||
int number1 = 999;
|
|
||||||
int number2 = 1000;
|
|
||||||
|
|
||||||
// when
|
// then
|
||||||
boolean actual = bcCryptWorkFactorService.isPreviousDurationCloserToGoal(number1, number2);
|
assertThat(actual).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
// then
|
@Test
|
||||||
assertThat(actual).isFalse();
|
void findCloserToShouldReturnGoalIfNumber1IsEqualGoal() {
|
||||||
}
|
// given
|
||||||
|
int number1 = 1000;
|
||||||
|
int number2 = 1001;
|
||||||
|
|
||||||
@Test
|
// when
|
||||||
void findCloserToShouldReturnGoalIfNumber1IsEqualGoal() {
|
boolean actual = bcCryptWorkFactorService.isPreviousDurationCloserToGoal(number1, number2);
|
||||||
// given
|
|
||||||
int number1 = 1000;
|
|
||||||
int number2 = 1001;
|
|
||||||
|
|
||||||
// when
|
// then
|
||||||
boolean actual = bcCryptWorkFactorService.isPreviousDurationCloserToGoal(number1, number2);
|
assertThat(actual).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
// then
|
@Test
|
||||||
assertThat(actual).isTrue();
|
void getStrengthShouldReturn4IfStrengthIs4() {
|
||||||
}
|
// given
|
||||||
|
int currentStrength = 4;
|
||||||
|
|
||||||
@Test
|
// when
|
||||||
void getStrengthShouldReturn4IfStrengthIs4() {
|
int actual = bcCryptWorkFactorService.getStrength(0, 0, currentStrength);
|
||||||
// given
|
|
||||||
int currentStrength = 4;
|
|
||||||
|
|
||||||
// when
|
// then
|
||||||
int actual = bcCryptWorkFactorService.getStrength(0, 0, currentStrength);
|
assertThat(actual).isEqualTo(4);
|
||||||
|
}
|
||||||
|
|
||||||
// then
|
@Test
|
||||||
assertThat(actual).isEqualTo(4);
|
void getStrengthShouldReturnPreviousStrengthIfPreviousDurationCloserToGoal() {
|
||||||
}
|
// given
|
||||||
|
|
||||||
@Test
|
// when
|
||||||
void getStrengthShouldReturnPreviousStrengthIfPreviousDurationCloserToGoal() {
|
int actual = bcCryptWorkFactorService.getStrength(980, 1021, 5);
|
||||||
// given
|
|
||||||
|
|
||||||
// when
|
// then
|
||||||
int actual = bcCryptWorkFactorService.getStrength(980, 1021, 5);
|
assertThat(actual).isEqualTo(4);
|
||||||
|
}
|
||||||
|
|
||||||
// then
|
@Test
|
||||||
assertThat(actual).isEqualTo(4);
|
void getStrengthShouldReturnCurrentStrengthIfCurrentDurationCloserToGoal() {
|
||||||
}
|
// given
|
||||||
|
|
||||||
@Test
|
// when
|
||||||
void getStrengthShouldReturnCurrentStrengthIfCurrentDurationCloserToGoal() {
|
int actual = bcCryptWorkFactorService.getStrength(960, 1021, 5);
|
||||||
// given
|
|
||||||
|
|
||||||
// when
|
// then
|
||||||
int actual = bcCryptWorkFactorService.getStrength(960, 1021, 5);
|
assertThat(actual).isEqualTo(5);
|
||||||
|
}
|
||||||
// then
|
}
|
||||||
assertThat(actual).isEqualTo(5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,17 +6,16 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
|||||||
|
|
||||||
class Pbkdf2WorkFactorServiceTest {
|
class Pbkdf2WorkFactorServiceTest {
|
||||||
|
|
||||||
|
private Pbkdf2WorkFactorService pbkdf2WorkFactorService = new Pbkdf2WorkFactorService();
|
||||||
|
|
||||||
private Pbkdf2WorkFactorService pbkdf2WorkFactorService = new Pbkdf2WorkFactorService();
|
@Test
|
||||||
|
void calculateIteration() {
|
||||||
|
// given
|
||||||
|
|
||||||
@Test
|
// when
|
||||||
void calculateIteration() {
|
int iterationNumber = pbkdf2WorkFactorService.calculateIteration();
|
||||||
// given
|
|
||||||
|
|
||||||
// when
|
// then
|
||||||
int iterationNumber = pbkdf2WorkFactorService.calculateIteration();
|
assertThat(iterationNumber).isGreaterThanOrEqualTo(150000);
|
||||||
|
}
|
||||||
// then
|
}
|
||||||
assertThat(iterationNumber).isGreaterThanOrEqualTo(150000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user