Skip to content

Commit

Permalink
Update default configuration for SCryptPasswordEncoder
Browse files Browse the repository at this point in the history
The recommended minimums for scrypt, as per OWASP Cheat Sheet Series (https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html), are:
Use scrypt with a minimum CPU/memory cost parameter of (2^16), a minimum block size of 8 (1024 bytes), and a parallelization parameter of 1.

Previous default configuration:
cpuCost=16384, memoryCost=8, parallelism=1

New default configuration:
cpuCost=65536, memoryCost=8, parallelism=1

The default salt length was also updated from 64 to 16.

Issue spring-projectsgh-10506
  • Loading branch information
jgrandja committed Oct 12, 2022
1 parent 2ea62d0 commit f841900
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ private PasswordEncoderFactories() {
* <li>noop -
* {@link org.springframework.security.crypto.password.NoOpPasswordEncoder}</li>
* <li>pbkdf2 - {@link Pbkdf2PasswordEncoder}</li>
* <li>scrypt - {@link SCryptPasswordEncoder}</li>
* <li>scrypt - {@link SCryptPasswordEncoder#defaultsForSpringSecurity_v4_1()}</li>
* <li>scrypt@SpringSecurity_v5_8 -
* {@link SCryptPasswordEncoder#defaultsForSpringSecurity_v5_8()}</li>
* <li>SHA-1 - {@code new MessageDigestPasswordEncoder("SHA-1")}</li>
* <li>SHA-256 - {@code new MessageDigestPasswordEncoder("SHA-256")}</li>
* <li>sha256 -
Expand All @@ -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"));
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -29,23 +29,23 @@ 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();
}

@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();
}

@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();
}
Expand All @@ -60,35 +60,35 @@ 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();
}

@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();
}

Expand Down Expand Up @@ -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();
}
Expand All @@ -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"));
}

}

0 comments on commit f841900

Please sign in to comment.