#685 Allow to configure number of rounds for PBKDF2

This commit is contained in:
ljacqu 2016-11-26 18:41:04 +01:00
parent 86db805c15
commit 79a3858b29
6 changed files with 60 additions and 12 deletions

View File

@ -1,8 +1,6 @@
package fr.xephi.authme.security;
import fr.xephi.authme.security.crypts.EncryptionMethod;
import fr.xephi.authme.security.crypts.Pbkdf2;
import fr.xephi.authme.security.crypts.Pbkdf2Django;
/**
* Hash algorithms supported by AuthMe.
@ -19,8 +17,8 @@ public enum HashAlgorithm {
MD5(fr.xephi.authme.security.crypts.MD5.class),
MD5VB(fr.xephi.authme.security.crypts.MD5VB.class),
MYBB(fr.xephi.authme.security.crypts.MYBB.class),
PBKDF2(Pbkdf2.class),
PBKDF2DJANGO(Pbkdf2Django.class),
PBKDF2(fr.xephi.authme.security.crypts.Pbkdf2.class),
PBKDF2DJANGO(fr.xephi.authme.security.crypts.Pbkdf2Django.class),
PHPBB(fr.xephi.authme.security.crypts.PHPBB.class),
PHPFUSION(fr.xephi.authme.security.crypts.PHPFUSION.class),
@Deprecated

View File

@ -3,18 +3,30 @@ package fr.xephi.authme.security.crypts;
import de.rtner.misc.BinTools;
import de.rtner.security.auth.spi.PBKDF2Engine;
import de.rtner.security.auth.spi.PBKDF2Parameters;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import javax.inject.Inject;
@Recommendation(Usage.RECOMMENDED)
public class Pbkdf2 extends HexSaltedMethod {
private static final int NUMBER_OF_ITERATIONS = 10_000;
private static final int DEFAULT_ROUNDS = 10_000;
private int numberOfRounds;
@Inject
Pbkdf2(Settings settings) {
int configuredRounds = settings.getProperty(SecuritySettings.PBKDF2_NUMBER_OF_ROUNDS);
this.numberOfRounds = configuredRounds > 0 ? configuredRounds : DEFAULT_ROUNDS;
}
@Override
public String computeHash(String password, String salt, String name) {
String result = "pbkdf2_sha256$" + NUMBER_OF_ITERATIONS + "$" + salt + "$";
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "UTF-8", salt.getBytes(), NUMBER_OF_ITERATIONS);
String result = "pbkdf2_sha256$" + numberOfRounds + "$" + salt + "$";
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "UTF-8", salt.getBytes(), numberOfRounds);
PBKDF2Engine engine = new PBKDF2Engine(params);
return result + BinTools.bin2hex(engine.deriveKey(password, 64));
@ -26,9 +38,16 @@ public class Pbkdf2 extends HexSaltedMethod {
if (line.length != 4) {
return false;
}
int iterations;
try {
iterations = Integer.parseInt(line[1]);
} catch (NumberFormatException e) {
ConsoleLogger.logException("Cannot read number of rounds for Pbkdf2", e);
return false;
}
String salt = line[2];
byte[] derivedKey = BinTools.hex2bin(line[3]);
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "UTF-8", salt.getBytes(), 10000, derivedKey);
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "UTF-8", salt.getBytes(), iterations, derivedKey);
PBKDF2Engine engine = new PBKDF2Engine(params);
return engine.verifyKey(password);
}

View File

@ -4,7 +4,6 @@ import de.rtner.security.auth.spi.PBKDF2Engine;
import de.rtner.security.auth.spi.PBKDF2Parameters;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.security.crypts.description.AsciiRestricted;
import fr.xephi.authme.util.StringUtils;
import javax.xml.bind.DatatypeConverter;
@ -32,8 +31,7 @@ public class Pbkdf2Django extends HexSaltedMethod {
try {
iterations = Integer.parseInt(line[1]);
} catch (NumberFormatException e) {
ConsoleLogger.warning("Could not read number of rounds for Pbkdf2Django:"
+ StringUtils.formatException(e));
ConsoleLogger.logException("Could not read number of rounds for Pbkdf2Django:", e);
return false;
}
String salt = line[2];

View File

@ -86,6 +86,10 @@ public class SecuritySettings implements SettingsHolder {
public static final Property<List<String>> LEGACY_HASHES =
new EnumSetProperty<>(HashAlgorithm.class, "settings.security.legacyHashes");
@Comment("Number of rounds to use if passwordHash is set to PBKDF2. Default is 10000")
public static final Property<Integer> PBKDF2_NUMBER_OF_ROUNDS =
newProperty("settings.security.pbkdf2Rounds", 10000);
@Comment({"Prevent unsafe passwords from being used; put them in lowercase!",
"You should always set 'help' as unsafePassword due to possible conflicts.",
"unsafePasswords:",

View File

@ -32,6 +32,7 @@ public class HashAlgorithmIntegrationTest {
Settings settings = mock(Settings.class);
given(settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND)).willReturn(8);
given(settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH)).willReturn(16);
given(settings.getProperty(SecuritySettings.PBKDF2_NUMBER_OF_ROUNDS)).willReturn(10_000);
injector = new InjectorBuilder().addDefaultHandlers("fr.xephi.authme").create();
injector.register(Settings.class, settings);
}

View File

@ -1,16 +1,44 @@
package fr.xephi.authme.security.crypts;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Test for {@link Pbkdf2}.
*/
public class Pbkdf2Test extends AbstractEncryptionMethodTest {
public Pbkdf2Test() {
super(new Pbkdf2(),
super(new Pbkdf2(mockSettings()),
"pbkdf2_sha256$10000$b25801311edf$093E38B16DFF13FCE5CD64D5D888EE6E0376A3E572FE5DA6749515EA0F384413223A21C464B0BE899E64084D1FFEFD44F2AC768453C87F41B42CC6954C416900", // password
"pbkdf2_sha256$10000$fe705da06c57$A41527BD58FED9C9E6F452FC1BA8B0C4C4224ECC63E37F71EB1A0865D2AB81BBFEBCA9B7B6A6E8AEF4717B43F8EB6FB4EDEFFBB399D9D991EF7E23013595BAF0", // PassWord1
"pbkdf2_sha256$10000$05603593cdda$1D30D1D90D826C866755969F06C312E21CC3E8DA0B777E2C764700E4E1FD890B731FAF44753D68F3FC025D3EAA709E800FBF2AF61DB23464311FCE7D35353A30", // &^%te$t?Pw@_
"pbkdf2_sha256$10000$fb944d66d754$F7E3BF8CB07CE3B3C8C5C534F803252F7B4FD58832E33BA62BA46CA06F23BAE12BE03A9CB5874BCFD4469E42972406F920E59F002247B23C22A8CF3D0E7BFFE0"); // âË_3(íù*
}
@Test
public void shouldDetectMatchForHashWithOtherRoundNumber() {
// given
Pbkdf2 pbkdf2 = new Pbkdf2(mockSettings());
String hash = "pbkdf2_sha256$4128$3469b0d48b702046$DC8A54351008C6054E12FB19E0BF8A4EA6D4165E0EDC97A1ECD15231037C382DE5BF85D07D5BC9D1ADF9BBFE4CE257C6059FB1B9FF65DB69D8B205F064BE0DA9";
String clearText = "PassWord1";
// when
boolean isMatch = pbkdf2.comparePassword(clearText, new HashedPassword(hash), "");
// then
assertThat(isMatch, equalTo(true));
}
private static Settings mockSettings() {
Settings settings = mock(Settings.class);
given(settings.getProperty(SecuritySettings.PBKDF2_NUMBER_OF_ROUNDS)).willReturn(4128);
return settings;
}
}