diff --git a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java index 49a011341..bef417057 100644 --- a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java +++ b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java @@ -1,6 +1,6 @@ package fr.xephi.authme.security; -import org.apache.commons.lang.ObjectUtils.Null; +import fr.xephi.authme.security.crypts.EncryptionMethod; /** */ @@ -33,21 +33,21 @@ public enum HashAlgorithm { CRAZYCRYPT1(fr.xephi.authme.security.crypts.CRAZYCRYPT1.class), BCRYPT2Y(fr.xephi.authme.security.crypts.BCRYPT2Y.class), SALTEDSHA512(fr.xephi.authme.security.crypts.SALTEDSHA512.class), - CUSTOM(Null.class); + CUSTOM(null); - final Class classe; + final Class clazz; /** * Constructor for HashAlgorithm. * - * @param classe The class of the hash implementation. + * @param clazz The class of the hash implementation. */ - HashAlgorithm(Class classe) { - this.classe = classe; + HashAlgorithm(Class clazz) { + this.clazz = clazz; } - public Class getclasse() { - return classe; + public Class getClazz() { + return clazz; } } diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index 9f96083f6..99f579b91 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -36,7 +36,7 @@ public class PasswordSecurity { EncryptionMethod method; try { if (alg != HashAlgorithm.CUSTOM) - method = (EncryptionMethod) alg.getclasse().newInstance(); + method = alg.getClazz().newInstance(); else method = null; } catch (InstantiationException | IllegalAccessException e) { throw new NoSuchAlgorithmException("Problem with hash algorithm '" + alg + "'", e); @@ -131,10 +131,11 @@ public class PasswordSecurity { HashAlgorithm algorithm = Settings.getPasswordHash; EncryptionMethod method; try { - if (algorithm != HashAlgorithm.CUSTOM) - method = (EncryptionMethod) algorithm.getclasse().newInstance(); - else + if (algorithm != HashAlgorithm.CUSTOM) { + method = algorithm.getClazz().newInstance(); + } else { method = null; + } PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName); Bukkit.getPluginManager().callEvent(event); @@ -161,7 +162,7 @@ public class PasswordSecurity { for (HashAlgorithm algo : HashAlgorithm.values()) { if (algo != HashAlgorithm.CUSTOM) { try { - EncryptionMethod method = (EncryptionMethod) algo.getclasse().newInstance(); + EncryptionMethod method = algo.getClazz().newInstance(); if (method.comparePassword(hash, password, playerName)) { PlayerAuth nAuth = AuthMe.getInstance().database.getAuth(playerName); if (nAuth != null) { diff --git a/src/main/java/fr/xephi/authme/security/RandomString.java b/src/main/java/fr/xephi/authme/security/RandomString.java index daab18e6b..8b1b8d639 100644 --- a/src/main/java/fr/xephi/authme/security/RandomString.java +++ b/src/main/java/fr/xephi/authme/security/RandomString.java @@ -1,20 +1,21 @@ package fr.xephi.authme.security; +import java.security.SecureRandom; import java.util.Calendar; import java.util.Random; -/** - * @author Xephi59 - */ public class RandomString { private static final char[] chars = new char[36]; + private static final Random RANDOM = new SecureRandom(); static { - for (int idx = 0; idx < 10; ++idx) + for (int idx = 0; idx < 10; ++idx) { chars[idx] = (char) ('0' + idx); - for (int idx = 10; idx < 36; ++idx) + } + for (int idx = 10; idx < 36; ++idx) { chars[idx] = (char) ('a' + idx - 10); + } } private final Random random = new Random(); @@ -34,4 +35,15 @@ public class RandomString { return new String(buf); } + public static String generate(int length) { + if (length < 0) { + throw new IllegalArgumentException("Length must be positive but was " + length); + } + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; ++i) { + sb.append(chars[RANDOM.nextInt(chars.length)]); + } + return sb.toString(); + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java index ebec319c1..48c508ce8 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java @@ -150,7 +150,8 @@ public class BCRYPT implements EncryptionMethod { * @param s the string to decode * @param maxolen the maximum number of bytes to decode * - * @return an array containing the decoded bytes * @throws IllegalArgumentException if maxolen is invalid * @throws IllegalArgumentException + * @return an array containing the decoded bytes + * @throws IllegalArgumentException if maxolen is invalid */ private static byte[] decode_base64(String s, int maxolen) throws IllegalArgumentException { diff --git a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java new file mode 100644 index 000000000..d223f9079 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java @@ -0,0 +1,100 @@ +package fr.xephi.authme.security.crypts; + +import fr.xephi.authme.security.PasswordSecurity; +import org.junit.Test; + +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Test for implementations of {@link EncryptionMethod}. + */ +// TODO #358: Remove NoSuchAlgorithm try-catch-es when no longer necessary +public abstract class AbstractEncryptionMethodTest { + + public static final String USERNAME = "Test_Player00"; + public static final String[] GIVEN_PASSWORDS = {"password", "PassWord1", "&^%te$t?Pw@_", "âË_3(íù*"}; + private static final String[] INTERNAL_PASSWORDS = {"test1234", "Ab_C73", "(!#&$~`_-Aa0", "Ûïé1&?+A"}; + + private EncryptionMethod method; + private Map hashes; + + public AbstractEncryptionMethodTest(EncryptionMethod method, String hash0, String hash1, + String hash2, String hash3) { + this.method = method; + hashes = new HashMap<>(); + hashes.put(GIVEN_PASSWORDS[0], hash0); + hashes.put(GIVEN_PASSWORDS[1], hash1); + hashes.put(GIVEN_PASSWORDS[2], hash2); + hashes.put(GIVEN_PASSWORDS[3], hash3); + } + + @Test + public void testGivenPasswords() { + for (String password : GIVEN_PASSWORDS) { + try { + assertTrue("Hash for password '" + password + "' should match", + method.comparePassword(hashes.get(password), password, USERNAME)); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("EncryptionMethod '" + method + "' threw exception", e); + } + } + } + + @Test + public void testPasswordEquality() { + for (String password : INTERNAL_PASSWORDS) { + try { + String hash = method.getHash(password, getSalt(method), USERNAME); + assertTrue("Generated hash for '" + password + "' should match password (hash = '" + hash + "')", + method.comparePassword(hash, password, USERNAME)); + if (!password.equals(password.toLowerCase())) { + assertFalse("Lower-case of '" + password + "' should not match generated hash '" + hash + "'", + method.comparePassword(hash, password.toLowerCase(), USERNAME)); + } + if (!password.equals(password.toUpperCase())) { + assertFalse("Upper-case of '" + password + "' should not match generated hash '" + hash + "'", + method.comparePassword(hash, password.toUpperCase(), USERNAME)); + } + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("EncryptionMethod '" + method + "' threw exception", e); + } + } + } + + static void generateHashes(EncryptionMethod method) { + System.out.println("AbstractEncryptionMethodTest.testGivenPasswords(method,"); + for (String password : GIVEN_PASSWORDS) { + try { + System.out.println("\t\"" + method.getHash(password, getSalt(method), "USERNAME") + + "\", // " + password); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Could not generate hash", e); + } + } + System.out.println(");"); + } + + // TODO #358: Remove this method and use the new salt method on the interface + private static String getSalt(EncryptionMethod method) { + try { + if (method instanceof BCRYPT) { + return BCRYPT.gensalt(); + } else if (method instanceof MD5) { + return ""; + } else if (method instanceof JOOMLA) { + return PasswordSecurity.createSalt(32); + } else if (method instanceof SHA256) { + return PasswordSecurity.createSalt(16); + } + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + throw new RuntimeException("Unknown EncryptionMethod for salt generation"); + } + +} diff --git a/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java b/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java new file mode 100644 index 000000000..2d133d407 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java @@ -0,0 +1,17 @@ +package fr.xephi.authme.security.crypts; + +/** + * Test for {@link BCRYPT}. + */ +public class BcryptTest extends AbstractEncryptionMethodTest { + + public BcryptTest() { + super(new BCRYPT(), + "$2a$10$6iATmYgwJVc3YONhVcZFve3Cfb5GnwvKhJ20r.hMjmcNkIT9.Uh9K", // password + "$2a$10$LOhUxhEcS0vgDPv/jkXvCurNb7LjP9xUlEolJGk.Uhgikqc6FtIOi", // PassWord1 + "$2a$10$j9da7SGiaakWhzIms9BtwemLUeIhSEphGUQ3XSlvYgpYsGnGCKRBa", // &^%te$t?Pw@_ + "$2a$10$mkmO3SNzQT/SA5fG/8P8PePz/DI/kKpIH8vd1Owf/fQfFu6F0QyWO" // âË_3(íù* + ); + } + +} diff --git a/src/test/java/fr/xephi/authme/security/crypts/JoomlaTest.java b/src/test/java/fr/xephi/authme/security/crypts/JoomlaTest.java new file mode 100644 index 000000000..f46d51d83 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/JoomlaTest.java @@ -0,0 +1,17 @@ +package fr.xephi.authme.security.crypts; + +/** + * Test for {@link JOOMLA}. + */ +public class JoomlaTest extends AbstractEncryptionMethodTest { + + public JoomlaTest() { + super(new JOOMLA(), + "b18c99813cd96df3a706652f47177490:377c4aaf92c5ed57711306909e6065ca", // password + "c5af71da91a8841d95937ba24a5b7fdb:07068e5850930b794526a614438cafc7", // PassWord1 + "f5fccd5166af7080833d7c7a6a531295:7cb6eeabcfac67ffe1341ec43375a9e6", // &^%te$t?Pw@_ + "dce946c6864d2223caeed9d80f356bcc:0c55fa3eca8c42557a989700ac1c4b8e" // âË_3(íù* + ); + } + +} diff --git a/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java b/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java new file mode 100644 index 000000000..761fe7bb3 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java @@ -0,0 +1,17 @@ +package fr.xephi.authme.security.crypts; + +/** + * Test for {@link MD5}. + */ +public class Md5Test extends AbstractEncryptionMethodTest { + + public Md5Test() { + super(new MD5(), + "5f4dcc3b5aa765d61d8327deb882cf99", // password + "f2126d405f46ed603ff5b2950f062c96", // PassWord1 + "0833dcd2bc741f90c46bbac5498fd08f", // &^%te$t?Pw@_ + "d1accd961cb7b688c87278191c1dfed3" // âË_3(íù* + ); + } + +} diff --git a/src/test/java/fr/xephi/authme/security/crypts/Sha256Test.java b/src/test/java/fr/xephi/authme/security/crypts/Sha256Test.java new file mode 100644 index 000000000..3257fe1f5 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/Sha256Test.java @@ -0,0 +1,17 @@ +package fr.xephi.authme.security.crypts; + +/** + * Test for {@link SHA256}. + */ +public class Sha256Test extends AbstractEncryptionMethodTest { + + public Sha256Test() { + super(new SHA256(), + "$SHA$11aa0706173d7272$dbba96681c2ae4e0bfdf226d70fbbc5e4ee3d8071faa613bc533fe8a64817d10", // password + "$SHA$3c72a18a29b08d40$8e50a7a4f69a80f4893dc921eac84bd74b3f9ebfa22908302c9965eac3aa45e5", // PassWord1 + "$SHA$584cea1cfab90030$adc006330e73d81e463fe02a4fe9b17bdbbcc05955bff72fb27cf2089f0b3859", // &^%te$t?Pw@_ + "$SHA$0b503d90dd9949d4$ba70c330242e0daa9a154ec9f4cce7f01dd05aff489d37c653e36a507c74d84f" // âË_3(íù* + ); + } + +}