diff --git a/crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java b/crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java index dd9beafab2c..1813953f429 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java +++ b/crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java @@ -53,7 +53,9 @@ private PasswordEncoderFactories() { *
  • noop - * {@link org.springframework.security.crypto.password.NoOpPasswordEncoder}
  • *
  • pbkdf2 - {@link Pbkdf2PasswordEncoder}
  • - *
  • scrypt - {@link SCryptPasswordEncoder}
  • + *
  • scrypt - {@link SCryptPasswordEncoder#defaultsForSpringSecurity_v4_1()}
  • + *
  • scrypt@SpringSecurity_v5_8 - + * {@link SCryptPasswordEncoder#defaultsForSpringSecurity_v5_8()}
  • *
  • SHA-1 - {@code new MessageDigestPasswordEncoder("SHA-1")}
  • *
  • SHA-256 - {@code new MessageDigestPasswordEncoder("SHA-256")}
  • *
  • sha256 - @@ -74,7 +76,8 @@ public static PasswordEncoder createDelegatingPasswordEncoder() { 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("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1()); + encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()); 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")); 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 98bafd4be24..43ec6e3d1ad 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,6 +58,16 @@ */ public class SCryptPasswordEncoder implements PasswordEncoder { + private static final int DEFAULT_CPU_COST = 65536; + + private static final int DEFAULT_MEMORY_COST = 8; + + private static final int DEFAULT_PARALLELISM = 1; + + private static final int DEFAULT_KEY_LENGTH = 32; + + private static final int DEFAULT_SALT_LENGTH = 16; + private final Log logger = LogFactory.getLog(getClass()); private final int cpuCost; @@ -70,14 +80,20 @@ public class SCryptPasswordEncoder implements PasswordEncoder { private final BytesKeyGenerator saltGenerator; + /** + * Constructs a SCrypt password encoder with cpu cost of 16,384, memory cost of 8, + * parallelization of 1, a key length of 32 and a salt length of 64 bytes. + * @deprecated Use {@link #defaultsForSpringSecurity_v4_1()} instead + */ + @Deprecated public SCryptPasswordEncoder() { this(16384, 8, 1, 32, 64); } /** - * Creates a new instance + * Constructs a SCrypt password encoder with the provided parameters. * @param cpuCost cpu cost of the algorithm (as defined in scrypt this is N). must be - * power of 2 greater than 1. Default is currently 16,384 or 2^14) + * power of 2 greater than 1. Default is currently 65,536 or 2^16) * @param memoryCost memory cost of the algorithm (as defined in scrypt this is r) * Default is currently 8. * @param parallelization the parallelization of the algorithm (as defined in scrypt @@ -86,7 +102,7 @@ public SCryptPasswordEncoder() { * @param keyLength key length for the algorithm (as defined in scrypt this is dkLen). * The default is currently 32. * @param saltLength salt length (as defined in scrypt this is the length of S). The - * default is currently 64. + * default is currently 16. */ public SCryptPasswordEncoder(int cpuCost, int memoryCost, int parallelization, int keyLength, int saltLength) { if (cpuCost <= 1) { @@ -116,6 +132,29 @@ public SCryptPasswordEncoder(int cpuCost, int memoryCost, int parallelization, i this.saltGenerator = KeyGenerators.secureRandom(saltLength); } + /** + * Constructs a SCrypt password encoder with cpu cost of 16,384, memory cost of 8, + * parallelization of 1, a key length of 32 and a salt length of 64 bytes. + * @return the {@link SCryptPasswordEncoder} + * @since 5.8 + * @deprecated Use {@link #defaultsForSpringSecurity_v5_8()} instead + */ + @Deprecated + public static SCryptPasswordEncoder defaultsForSpringSecurity_v4_1() { + return new SCryptPasswordEncoder(16384, 8, 1, 32, 64); + } + + /** + * Constructs a SCrypt password encoder with cpu cost of 65,536, memory cost of 8, + * parallelization of 1, a key length of 32 and a salt length of 16 bytes. + * @return the {@link SCryptPasswordEncoder} + * @since 5.8 + */ + public static SCryptPasswordEncoder defaultsForSpringSecurity_v5_8() { + return new SCryptPasswordEncoder(DEFAULT_CPU_COST, DEFAULT_MEMORY_COST, DEFAULT_PARALLELISM, DEFAULT_KEY_LENGTH, + DEFAULT_SALT_LENGTH); + } + @Override public String encode(CharSequence rawPassword) { return digest(rawPassword, this.saltGenerator.generateKey()); diff --git a/crypto/src/test/java/org/springframework/security/crypto/factory/PasswordEncoderFactoriesTests.java b/crypto/src/test/java/org/springframework/security/crypto/factory/PasswordEncoderFactoriesTests.java index d28f89dfae3..c8c19553d2b 100644 --- a/crypto/src/test/java/org/springframework/security/crypto/factory/PasswordEncoderFactoriesTests.java +++ b/crypto/src/test/java/org/springframework/security/crypto/factory/PasswordEncoderFactoriesTests.java @@ -81,6 +81,12 @@ public void matchesWhenSCryptThenWorks() { assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue(); } + @Test + public void matchesWhenSCryptSpringSecurity_v5_8ThenWorks() { + String encodedPassword = "{scrypt@SpringSecurity_v5_8}$e0801$vSriIassJwvdNBF1vpSoCenqBxvpT4e+NcLKVsrOVpaZfyRfpUJ6KctkpmketuacWelLU5njpILXM9LLkMXLMw==$vIQQljL257HOcnumyiy1hJBGYHmoXgENIh+NkFvmrGY="; + assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue(); + } + @Test public void matchesWhenSHA1ThenWorks() { String encodedPassword = "{SHA-1}{6581QepZz2qd8jVrT2QYPVtK8DuM2n45dVslmc3UTWc=}4f31573948ddbfb8ac9dd80107dfad13fd8f2454"; 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 ad43ffc5e2e..59a6d48e677 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ public class SCryptPasswordEncoderTests { @Test public void matches() { - SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); + SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1(); String result = encoder.encode("password"); assertThat(result).isNotEqualTo("password"); assertThat(encoder.matches("password", result)).isTrue(); @@ -37,7 +37,7 @@ public void matches() { @Test public void unicode() { - SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); + SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1(); String result = encoder.encode("passw\u9292rd"); assertThat(encoder.matches("pass\u9292\u9292rd", result)).isFalse(); assertThat(encoder.matches("passw\u9292rd", result)).isTrue(); @@ -45,7 +45,7 @@ public void unicode() { @Test public void notMatches() { - SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); + SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1(); String result = encoder.encode("password"); assertThat(encoder.matches("bogus", result)).isFalse(); } @@ -60,15 +60,15 @@ public void customParameters() { @Test public void differentPasswordHashes() { - SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); + SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1(); String password = "secret"; assertThat(encoder.encode(password)).isNotEqualTo(encoder.encode(password)); } @Test public void samePasswordWithDifferentParams() { - SCryptPasswordEncoder oldEncoder = new SCryptPasswordEncoder(16384, 8, 1, 32, 64); - SCryptPasswordEncoder newEncoder = new SCryptPasswordEncoder(); + SCryptPasswordEncoder oldEncoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1(); + SCryptPasswordEncoder newEncoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8(); String password = "secret"; String oldEncodedPassword = oldEncoder.encode(password); assertThat(newEncoder.matches(password, oldEncodedPassword)).isTrue(); @@ -76,19 +76,19 @@ public void samePasswordWithDifferentParams() { @Test public void doesntMatchNullEncodedValue() { - SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); + SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1(); assertThat(encoder.matches("password", null)).isFalse(); } @Test public void doesntMatchEmptyEncodedValue() { - SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); + SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1(); assertThat(encoder.matches("password", "")).isFalse(); } @Test public void doesntMatchBogusEncodedValue() { - SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); + SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1(); assertThat(encoder.matches("password", "012345678901234567890123456789")).isFalse(); } @@ -122,19 +122,19 @@ public void invalidKeyLengthParameter() { @Test public void upgradeEncodingWhenNullThenFalse() { - SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); + SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1(); assertThat(encoder.upgradeEncoding(null)).isFalse(); } @Test public void upgradeEncodingWhenEmptyThenFalse() { - SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); + SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1(); assertThat(encoder.upgradeEncoding("")).isFalse(); } @Test public void upgradeEncodingWhenSameEncoderThenFalse() { - SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); + SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1(); String encoded = encoder.encode("password"); assertThat(encoder.upgradeEncoding(encoded)).isFalse(); } @@ -159,8 +159,8 @@ public void upgradeEncodingWhenStrongerToWeakerThenTrue() { @Test public void upgradeEncodingWhenInvalidInputThenException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new SCryptPasswordEncoder().upgradeEncoding("not-a-scrypt-password")); + assertThatIllegalArgumentException().isThrownBy( + () -> SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1().upgradeEncoding("not-a-scrypt-password")); } }