From 2ea62d0f8b8c08e2320974e7c485bf61c34cc1c9 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Mon, 26 Sep 2022 10:58:07 -0400 Subject: [PATCH] Update default configuration for Argon2PasswordEncoder The recommended minimums for Argon2, as per OWASP Cheat Sheet Series (https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html), are: Use Argon2id with a minimum configuration of 15 MiB of memory, an iteration count of 2, and 1 degree of parallelism. Previous default configuration: memory=4, iterations=3, parallelism=1 New default configuration: memory=16, iterations=2, parallelism=1 Issue gh-10506 --- .../crypto/argon2/Argon2PasswordEncoder.java | 45 +++++++++++++++++-- .../factory/PasswordEncoderFactories.java | 9 ++-- .../argon2/Argon2PasswordEncoderTests.java | 15 +++++-- .../PasswordEncoderFactoriesTests.java | 8 +++- 4 files changed, 66 insertions(+), 11 deletions(-) diff --git a/crypto/src/main/java/org/springframework/security/crypto/argon2/Argon2PasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/argon2/Argon2PasswordEncoder.java index b3991cb8c22..e7248776a5d 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/argon2/Argon2PasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/argon2/Argon2PasswordEncoder.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. @@ -52,9 +52,9 @@ public class Argon2PasswordEncoder implements PasswordEncoder { private static final int DEFAULT_PARALLELISM = 1; - private static final int DEFAULT_MEMORY = 1 << 12; + private static final int DEFAULT_MEMORY = 1 << 14; - private static final int DEFAULT_ITERATIONS = 3; + private static final int DEFAULT_ITERATIONS = 2; private final Log logger = LogFactory.getLog(getClass()); @@ -68,10 +68,24 @@ public class Argon2PasswordEncoder implements PasswordEncoder { private final BytesKeyGenerator saltGenerator; + /** + * Constructs an Argon2 password encoder with a salt length of 16 bytes, a hash length + * of 32 bytes, parallelism of 1, memory cost of 1 << 12 and 3 iterations. + * @deprecated Use {@link #defaultsForSpringSecurity_v5_2()} instead + */ + @Deprecated public Argon2PasswordEncoder() { - this(DEFAULT_SALT_LENGTH, DEFAULT_HASH_LENGTH, DEFAULT_PARALLELISM, DEFAULT_MEMORY, DEFAULT_ITERATIONS); + this(16, 32, 1, 1 << 12, 3); } + /** + * Constructs an Argon2 password encoder with the provided parameters. + * @param saltLength the salt length (in bytes) + * @param hashLength the hash length (in bytes) + * @param parallelism the parallelism + * @param memory the memory cost + * @param iterations the number of iterations + */ public Argon2PasswordEncoder(int saltLength, int hashLength, int parallelism, int memory, int iterations) { this.hashLength = hashLength; this.parallelism = parallelism; @@ -80,6 +94,29 @@ public Argon2PasswordEncoder(int saltLength, int hashLength, int parallelism, in this.saltGenerator = KeyGenerators.secureRandom(saltLength); } + /** + * Constructs an Argon2 password encoder with a salt length of 16 bytes, a hash length + * of 32 bytes, parallelism of 1, memory cost of 1 << 12 and 3 iterations. + * @return the {@link Argon2PasswordEncoder} + * @since 5.8 + * @deprecated Use {@link #defaultsForSpringSecurity_v5_8()} instead + */ + @Deprecated + public static Argon2PasswordEncoder defaultsForSpringSecurity_v5_2() { + return new Argon2PasswordEncoder(16, 32, 1, 1 << 12, 3); + } + + /** + * Constructs an Argon2 password encoder with a salt length of 16 bytes, a hash length + * of 32 bytes, parallelism of 1, memory cost of 1 << 14 and 2 iterations. + * @return the {@link Argon2PasswordEncoder} + * @since 5.8 + */ + public static Argon2PasswordEncoder defaultsForSpringSecurity_v5_8() { + return new Argon2PasswordEncoder(DEFAULT_SALT_LENGTH, DEFAULT_HASH_LENGTH, DEFAULT_PARALLELISM, DEFAULT_MEMORY, + DEFAULT_ITERATIONS); + } + @Override public String encode(CharSequence rawPassword) { byte[] salt = this.saltGenerator.generateKey(); 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 eb27d4a058c..dd9beafab2c 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 @@ -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. @@ -58,7 +58,9 @@ private PasswordEncoderFactories() { *
  • SHA-256 - {@code new MessageDigestPasswordEncoder("SHA-256")}
  • *
  • sha256 - * {@link org.springframework.security.crypto.password.StandardPasswordEncoder}
  • - *
  • argon2 - {@link Argon2PasswordEncoder}
  • + *
  • argon2 - {@link Argon2PasswordEncoder#defaultsForSpringSecurity_v5_2()}
  • + *
  • argon2@SpringSecurity_v5_8 - + * {@link Argon2PasswordEncoder#defaultsForSpringSecurity_v5_8()}
  • * * @return the {@link PasswordEncoder} to use */ @@ -77,7 +79,8 @@ public static PasswordEncoder createDelegatingPasswordEncoder() { 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()); + encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2()); + encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()); return new DelegatingPasswordEncoder(encodingId, encoders); } diff --git a/crypto/src/test/java/org/springframework/security/crypto/argon2/Argon2PasswordEncoderTests.java b/crypto/src/test/java/org/springframework/security/crypto/argon2/Argon2PasswordEncoderTests.java index 6aaa6904912..9fb2a7412e8 100644 --- a/crypto/src/test/java/org/springframework/security/crypto/argon2/Argon2PasswordEncoderTests.java +++ b/crypto/src/test/java/org/springframework/security/crypto/argon2/Argon2PasswordEncoderTests.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. @@ -39,7 +39,7 @@ public class Argon2PasswordEncoderTests { @Mock private BytesKeyGenerator keyGeneratorMock; - private Argon2PasswordEncoder encoder = new Argon2PasswordEncoder(); + private Argon2PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2(); @Test public void encodeDoesNotEqualPassword() { @@ -127,6 +127,15 @@ public void encodeWhenUsingPredictableSaltWithCustomParamsThenEqualTestHash() th "$argon2id$v=19$m=512,t=5,p=4$QUFBQUFBQUFBQUFBQUFBQQ$PNv4C3K50bz3rmON+LtFpdisD7ePieLNq+l5iUHgc1k"); } + @Test + public void encodeWhenUsingPredictableSaltWithDefaultsForSpringSecurity_v5_8ThenEqualTestHash() throws Exception { + this.encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8(); + injectPredictableSaltGen(); + String hash = this.encoder.encode("sometestpassword"); + assertThat(hash).isEqualTo( + "$argon2id$v=19$m=16384,t=2,p=1$QUFBQUFBQUFBQUFBQUFBQQ$zGt5MiNPSUOo4/7jBcJMayCPfcsLJ4c0WUxhwGDIYPw"); + } + @Test public void upgradeEncodingWhenSameEncodingThenFalse() { String hash = this.encoder.encode("password"); @@ -135,7 +144,7 @@ public void upgradeEncodingWhenSameEncodingThenFalse() { @Test public void upgradeEncodingWhenSameStandardParamsThenFalse() { - Argon2PasswordEncoder newEncoder = new Argon2PasswordEncoder(); + Argon2PasswordEncoder newEncoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2(); String hash = this.encoder.encode("password"); assertThat(newEncoder.upgradeEncoding(hash)).isFalse(); } 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 ab2ca94a94c..d28f89dfae3 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 @@ -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. @@ -105,4 +105,10 @@ public void matchesWhenArgon2ThenWorks() { assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue(); } + @Test + public void matchesWhenArgon2SpringSecurity_v5_8ThenWorks() { + String encodedPassword = "{argon2@SpringSecurity_v5_8}$argon2id$v=19$m=16384,t=2,p=1$v7fN5p91BQbdbA2HfdSPRg$MULpa02CO/6FKfqwuerCFvS7OhMxGFCKUOoWfzt86Rc"; + assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue(); + } + }