diff --git a/crypto/src/main/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoder.java index 198f91adf12..1acc13085bc 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoder.java @@ -134,6 +134,30 @@ public boolean matches(CharSequence rawPassword, String encodedPassword) { return decodeAndCheckMatches(rawPassword, encodedPassword); } + @Override + public boolean upgradeEncoding(String encodedPassword) { + if (encodedPassword == null || encodedPassword.isEmpty()) { + return false; + } + + String[] parts = encodedPassword.split("\\$"); + + if (parts.length != 4) { + throw new IllegalArgumentException("Encoded password does not look like SCrypt: " + encodedPassword); + } + + long params = Long.parseLong(parts[1], 16); + + int cpuCost = (int) Math.pow(2, params >> 16 & 0xffff); + int memoryCost = (int) params >> 8 & 0xff; + int parallelization = (int) params & 0xff; + + return cpuCost < this.cpuCost + || memoryCost < this.memoryCost + || parallelization < this.parallelization; + + } + private boolean decodeAndCheckMatches(CharSequence rawPassword, String encodedPassword) { String[] parts = encodedPassword.split("\\$"); diff --git a/crypto/src/test/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoderTests.java b/crypto/src/test/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoderTests.java index d93f285a1e0..207677f63c5 100644 --- a/crypto/src/test/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoderTests.java +++ b/crypto/src/test/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoderTests.java @@ -116,5 +116,35 @@ public void invalidKeyLengthParameter() { new SCryptPasswordEncoder(2, 8, 1, -1, 16); } + @Test + public void upgradeEncoding_nullOrEmptyInput() { + SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); + assertThat(encoder.upgradeEncoding(null)).isFalse(); + assertThat(encoder.upgradeEncoding("")).isFalse(); + } + + @Test + public void upgradeEncoding_sameEncoder() { + SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); + String encoded = encoder.encode("password"); + assertThat(encoder.upgradeEncoding(encoded)).isFalse(); + } + + @Test + public void upgradeEncoding_weakerToStronger() { + SCryptPasswordEncoder weakEncoder = new SCryptPasswordEncoder((int) Math.pow(2, 10), 4, 1, 32, 64); + SCryptPasswordEncoder strongEncoder = new SCryptPasswordEncoder((int) Math.pow(2, 16), 8, 1, 32, 64); + + String weakPassword = weakEncoder.encode("password"); + String strongPassword = strongEncoder.encode("password"); + + assertThat(strongEncoder.upgradeEncoding(weakPassword)).isTrue(); + assertThat(weakEncoder.upgradeEncoding(strongPassword)).isFalse(); + } + + @Test(expected = IllegalArgumentException.class) + public void upgradeEncoding_invalidInput() { + new SCryptPasswordEncoder().upgradeEncoding("not-a-scrypt-password"); + } }