From 90a03251945624cd8d00cfc2b3ba8fc1261babb1 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 26 Dec 2015 23:59:32 +0100 Subject: [PATCH 01/18] #358 Add future interface methods, remove exception throwing - Create Utils class for a common implementation of md5/sha1 - Create "foolproof" way of getting the MessageDigest for md5 etc. (MessageDigestAlgorithm enum) - Create description annotations to annotate algorithms with usage recommendation and salt type --- .../events/PasswordEncryptionEvent.java | 35 ------------ .../fr/xephi/authme/security/HashUtils.java | 53 +++++++++++++++++++ .../security/MessageDigestAlgorithm.java | 30 +++++++++++ .../xephi/authme/security/crypts/BCRYPT.java | 24 ++++++--- .../authme/security/crypts/BCRYPT2Y.java | 16 +++--- .../authme/security/crypts/CRAZYCRYPT1.java | 46 +++++++++------- .../authme/security/crypts/CryptPBKDF2.java | 5 +- .../security/crypts/CryptPBKDF2Django.java | 24 ++++++--- .../authme/security/crypts/DOUBLEMD5.java | 41 +++++++------- .../security/crypts/EncryptionMethod.java | 4 ++ .../xephi/authme/security/crypts/JOOMLA.java | 47 ++++++++-------- .../fr/xephi/authme/security/crypts/MD5.java | 37 ++++++------- .../xephi/authme/security/crypts/MD5VB.java | 43 +++++++-------- .../fr/xephi/authme/security/crypts/SHA1.java | 37 +++++++------ .../authme/security/crypts/WHIRLPOOL.java | 23 +++++--- .../authme/security/crypts/WORDPRESS.java | 26 ++++++--- .../xephi/authme/security/crypts/XAUTH.java | 24 ++++++--- .../security/crypts/description/HasSalt.java | 13 +++++ .../crypts/description/Recommendation.java | 10 ++++ .../security/crypts/description/SaltType.java | 17 ++++++ .../security/crypts/description/Usage.java | 20 +++++++ .../crypts/AbstractEncryptionMethodTest.java | 2 +- .../security/crypts/CRAZYCRYPT1Test.java | 16 ++++++ 23 files changed, 387 insertions(+), 206 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/security/HashUtils.java create mode 100644 src/main/java/fr/xephi/authme/security/MessageDigestAlgorithm.java create mode 100644 src/main/java/fr/xephi/authme/security/crypts/description/HasSalt.java create mode 100644 src/main/java/fr/xephi/authme/security/crypts/description/Recommendation.java create mode 100644 src/main/java/fr/xephi/authme/security/crypts/description/SaltType.java create mode 100644 src/main/java/fr/xephi/authme/security/crypts/description/Usage.java create mode 100644 src/test/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1Test.java diff --git a/src/main/java/fr/xephi/authme/events/PasswordEncryptionEvent.java b/src/main/java/fr/xephi/authme/events/PasswordEncryptionEvent.java index f20fd7fd..8519778b 100644 --- a/src/main/java/fr/xephi/authme/events/PasswordEncryptionEvent.java +++ b/src/main/java/fr/xephi/authme/events/PasswordEncryptionEvent.java @@ -5,14 +5,10 @@ import org.bukkit.event.Event; import org.bukkit.event.HandlerList; /** - *

* This event is called when we need to compare or get an hash password, for set * a custom EncryptionMethod - *

* * @author Xephi59 - * @version $Revision: 1.0 $ - * @see fr.xephi.authme.security.crypts.EncryptionMethod */ public class PasswordEncryptionEvent extends Event { @@ -20,60 +16,29 @@ public class PasswordEncryptionEvent extends Event { private EncryptionMethod method = null; private String playerName = ""; - /** - * Constructor for PasswordEncryptionEvent. - * - * @param method EncryptionMethod - * @param playerName String - */ public PasswordEncryptionEvent(EncryptionMethod method, String playerName) { super(false); this.method = method; this.playerName = playerName; } - /** - * Method getHandlerList. - * - * @return HandlerList - */ public static HandlerList getHandlerList() { return handlers; } - /** - * Method getHandlers. - * - * @return HandlerList - */ @Override public HandlerList getHandlers() { return handlers; } - /** - * Method getMethod. - * - * @return EncryptionMethod - */ public EncryptionMethod getMethod() { return method; } - /** - * Method setMethod. - * - * @param method EncryptionMethod - */ public void setMethod(EncryptionMethod method) { this.method = method; } - /** - * Method getPlayerName. - * - * @return String - */ public String getPlayerName() { return playerName; } diff --git a/src/main/java/fr/xephi/authme/security/HashUtils.java b/src/main/java/fr/xephi/authme/security/HashUtils.java new file mode 100644 index 00000000..0f4f7318 --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/HashUtils.java @@ -0,0 +1,53 @@ +package fr.xephi.authme.security; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + + +public final class HashUtils { + + private static final SecureRandom RANDOM = new SecureRandom(); + + private HashUtils() { + } + + public static String hash(String message, MessageDigestAlgorithm algorithm) { + MessageDigest md = getDigest(algorithm); + md.reset(); + md.update(message.getBytes()); + byte[] digest = md.digest(); + return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + } + + public static String sha1(String message) { + return hash(message, MessageDigestAlgorithm.SHA1); + } + + public static String md5(String message) { + return hash(message, MessageDigestAlgorithm.MD5); + } + + // Only works for length up to 40! + public static String generateSalt(int length) { + byte[] msg = new byte[40]; + RANDOM.nextBytes(msg); + MessageDigest sha1 = getDigest(MessageDigestAlgorithm.SHA1); + sha1.reset(); + byte[] digest = sha1.digest(msg); + return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)).substring(0, length); + } + + public static MessageDigest getDigest(MessageDigestAlgorithm algorithm) { + try { + return MessageDigest.getInstance(algorithm.getKey()); + } catch (NoSuchAlgorithmException e) { + throw new UnsupportedOperationException("Your system seems not to support the hash algorithm '" + + algorithm.getKey() + "'"); + } + } + + + +} diff --git a/src/main/java/fr/xephi/authme/security/MessageDigestAlgorithm.java b/src/main/java/fr/xephi/authme/security/MessageDigestAlgorithm.java new file mode 100644 index 00000000..51ff2a5d --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/MessageDigestAlgorithm.java @@ -0,0 +1,30 @@ +package fr.xephi.authme.security; + +import java.security.MessageDigest; + +/** + * The Java-supported names to get a {@link MessageDigest} instance with. + * + * @see + * Crypto Spec Appendix A: Standard Names + */ +public enum MessageDigestAlgorithm { + + MD5("MD5"), + + SHA1("SHA-1"), + + SHA256("SHA-256"), + + SHA512("SHA-512"); + + private final String key; + + MessageDigestAlgorithm(String key) { + this.key = key; + } + + public String getKey() { + return key; + } +} 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 c94644e9..beea0fe4 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java @@ -13,8 +13,12 @@ // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. package fr.xephi.authme.security.crypts; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Usage; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; + import java.io.UnsupportedEncodingException; -import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; /** @@ -59,11 +63,13 @@ import java.security.SecureRandom; * @author Damien Miller * @version 0.2 */ +@Recommendation(Usage.RECOMMENDED) +@HasSalt(value = SaltType.TEXT, length = BCRYPT.BCRYPT_SALT_LEN) public class BCRYPT implements EncryptionMethod { // BCrypt parameters private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10; - private static final int BCRYPT_SALT_LEN = 16; + protected static final int BCRYPT_SALT_LEN = 16; // Blowfish parameters private static final int BLOWFISH_NUM_ROUNDS = 16; @@ -508,14 +514,20 @@ public class BCRYPT implements EncryptionMethod { } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { return hashpw(password, salt); } + public String computeHash(String password, String name) { + return hashpw(password, generateSalt()); + } + @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String hash, String password, String playerName) { return checkpw(password, hash); } + + public String generateSalt() { + return BCRYPT.gensalt(); + } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java index d90af6e4..90901cf9 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java @@ -1,26 +1,22 @@ package fr.xephi.authme.security.crypts; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; -/** - */ +@Recommendation(Usage.DOES_NOT_WORK) public class BCRYPT2Y implements EncryptionMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { if (salt.length() == 22) salt = "$2y$10$" + salt; return (BCRYPT.hashpw(password, salt)); } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String hash, String password, String playerName) { String ok = hash.substring(0, 29); - if (ok.length() != 29) - return false; - return hash.equals(computeHash(password, ok, playerName)); + return ok.length() == 29 && hash.equals(computeHash(password, ok, playerName)); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java index 336d8a4a..a48da8f1 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java @@ -1,18 +1,24 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.MessageDigestAlgorithm; +import fr.xephi.authme.security.crypts.description.Usage; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.HasSalt; + import java.nio.charset.Charset; import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -/** - */ +@Recommendation(Usage.DO_NOT_USE) +@HasSalt(SaltType.USERNAME) public class CRAZYCRYPT1 implements EncryptionMethod { - private static final char[] CRYPTCHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - protected final Charset charset = Charset.forName("UTF-8"); + private static final char[] CRYPTCHARS = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + private final Charset charset = Charset.forName("UTF-8"); - - public static String byteArrayToHexString(final byte... args) { + private static String byteArrayToHexString(final byte... args) { final char[] chars = new char[args.length * 2]; for (int i = 0; i < args.length; i++) { chars[i * 2] = CRYPTCHARS[(args[i] >> 4) & 0xF]; @@ -22,21 +28,23 @@ public class CRAZYCRYPT1 implements EncryptionMethod { } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { + return computeHash(password, name); + } + + public String computeHash(String password, String name) { final String text = "ÜÄaeut//&/=I " + password + "7421€547" + name + "__+IÄIH§%NK " + password; - try { - final MessageDigest md = MessageDigest.getInstance("SHA-512"); - md.update(text.getBytes(charset), 0, text.length()); - return byteArrayToHexString(md.digest()); - } catch (final NoSuchAlgorithmException e) { - return null; - } + final MessageDigest md = HashUtils.getDigest(MessageDigestAlgorithm.SHA512); + md.update(text.getBytes(charset), 0, text.length()); + return byteArrayToHexString(md.digest()); } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(computeHash(password, null, playerName)); + public boolean comparePassword(String hash, String password, String playerName) { + return hash.equals(computeHash(password, playerName)); + } + + public String generateSalt() { + return null; } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java index 728023be..e375ae15 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java @@ -1,13 +1,14 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; import fr.xephi.authme.security.pbkdf2.PBKDF2Engine; import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters; import java.security.NoSuchAlgorithmException; import java.util.Arrays; -/** - */ +@Recommendation(Usage.DOES_NOT_WORK) public class CryptPBKDF2 implements EncryptionMethod { @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java index 36e6dfe3..c4df8ab4 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java @@ -1,18 +1,21 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; import fr.xephi.authme.security.pbkdf2.PBKDF2Engine; import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters; import javax.xml.bind.DatatypeConverter; -import java.security.NoSuchAlgorithmException; -/** - */ +@Recommendation(Usage.OK) +@HasSalt(value = SaltType.TEXT, length = 12) public class CryptPBKDF2Django implements EncryptionMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { String result = "pbkdf2_sha256$15000$" + salt + "$"; PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 15000); PBKDF2Engine engine = new PBKDF2Engine(params); @@ -20,9 +23,12 @@ public class CryptPBKDF2Django implements EncryptionMethod { return result + String.valueOf(DatatypeConverter.printBase64Binary(engine.deriveKey(password, 32))); } + public String computeHash(String password, String name) { + return computeHash(password, generateSalt(), null); + } + @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String hash, String password, String playerName) { String[] line = hash.split("\\$"); String salt = line[2]; byte[] derivedKey = DatatypeConverter.parseBase64Binary(line[3]); @@ -31,4 +37,8 @@ public class CryptPBKDF2Django implements EncryptionMethod { return engine.verifyKey(password); } + public String generateSalt() { + return HashUtils.generateSalt(12); + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java index 58d30a66..e1cfff9a 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java @@ -1,32 +1,31 @@ package fr.xephi.authme.security.crypts; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; -/** - */ +@Recommendation(Usage.DO_NOT_USE) +@HasSalt(SaltType.NONE) public class DOUBLEMD5 implements EncryptionMethod { - private static String getMD5(String message) - throws NoSuchAlgorithmException { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.reset(); - md5.update(message.getBytes()); - byte[] digest = md5.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return computeHash(password, null); + } + + public String computeHash(String password, String name) { + return HashUtils.md5(HashUtils.md5(password)); + } + + public String generateSalt() { + return null; } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(getMD5(password)); - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(computeHash(password, "", "")); + public boolean comparePassword(String hash, String password, String playerName) { + return hash.equals(computeHash(password, null, null)); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java index f925c10d..04add2be 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java @@ -32,4 +32,8 @@ public interface EncryptionMethod { boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException; + // String generateSalt(); + + // String computeHash(String password, String name); + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java index 41f6fe35..36c09244 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java +++ b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java @@ -1,32 +1,35 @@ package fr.xephi.authme.security.crypts; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; -/** - */ +@Recommendation(Usage.OK) +@HasSalt(value = SaltType.TEXT, length = 32) public class JOOMLA implements EncryptionMethod { - private static String getMD5(String message) - throws NoSuchAlgorithmException { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.reset(); - md5.update(message.getBytes()); - byte[] digest = md5.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return HashUtils.md5(password + salt) + ":" + salt; + } + + public String computeHash(String password, String name) { + return computeHash(password, generateSalt(), null); + } + + public String generateSalt() { + return HashUtils.generateSalt(32); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(password + salt) + ":" + salt; - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = hash.split(":")[1]; - return hash.equals(getMD5(password + salt) + ":" + salt); + public boolean comparePassword(String hash, String password, String playerName) { + String[] hashParts = hash.split(":"); + if (hashParts.length != 2) { + return false; + } + String salt = hashParts[1]; + return hash.equals(computeHash(password, salt, null)); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5.java b/src/main/java/fr/xephi/authme/security/crypts/MD5.java index 8e20dbf4..c3110c94 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5.java @@ -1,31 +1,26 @@ package fr.xephi.authme.security.crypts; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; -/** - */ +@Recommendation(Usage.DO_NOT_USE) +@HasSalt(SaltType.NONE) public class MD5 implements EncryptionMethod { - private static String getMD5(String message) - throws NoSuchAlgorithmException { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.reset(); - md5.update(message.getBytes()); - byte[] digest = md5.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return computeHash(password, null); + } + + public String computeHash(String password, String name) { + return HashUtils.md5(password); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(password); - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(computeHash(password, "", "")); + public boolean comparePassword(String hash, String password, String playerName) { + return hash.equals(computeHash(password, null)); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java index e54f8ad3..cbd30eb2 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java @@ -1,33 +1,34 @@ package fr.xephi.authme.security.crypts; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; -/** - */ +import static fr.xephi.authme.security.HashUtils.md5; + +@Recommendation(Usage.OK) +@HasSalt(value = SaltType.TEXT, length = 16) public class MD5VB implements EncryptionMethod { - private static String getMD5(String message) - throws NoSuchAlgorithmException { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.reset(); - md5.update(message.getBytes()); - byte[] digest = md5.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return "$MD5vb$" + salt + "$" + md5(md5(password) + salt); + } + + public String computeHash(String password, String name) { + return computeHash(password, generateSalt(), null); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return "$MD5vb$" + salt + "$" + getMD5(getMD5(password) + salt); - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String hash, String password, String playerName) { String[] line = hash.split("\\$"); - return hash.equals(computeHash(password, line[2], "")); + return line.length == 4 && hash.equals(computeHash(password, line[2], "")); + } + + public String generateSalt() { + return HashUtils.generateSalt(16); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java index f41c01e9..2803b1c2 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java @@ -1,32 +1,31 @@ package fr.xephi.authme.security.crypts; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; -/** - */ +@Recommendation(Usage.DO_NOT_USE) +@HasSalt(SaltType.NONE) public class SHA1 implements EncryptionMethod { - private static String getSHA1(String message) - throws NoSuchAlgorithmException { - MessageDigest sha1 = MessageDigest.getInstance("SHA1"); - sha1.reset(); - sha1.update(message.getBytes()); - byte[] digest = sha1.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return computeHash(password, null); + } + + public String computeHash(String password, String name) { + return HashUtils.sha1(password); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getSHA1(password); + public boolean comparePassword(String hash, String password, String playerName) { + return hash.equals(computeHash(password, null)); } - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(computeHash(password, "", "")); + public String generateSalt() { + return null; } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java index 0ce0c2e5..74229adb 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java @@ -59,11 +59,15 @@ package fr.xephi.authme.security.crypts; * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; + import java.util.Arrays; -/** - */ +@Recommendation(Usage.DO_NOT_USE) +@HasSalt(SaltType.NONE) public class WHIRLPOOL implements EncryptionMethod { /** @@ -383,8 +387,11 @@ public class WHIRLPOOL implements EncryptionMethod { } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { + return computeHash(password, null); + } + + public String computeHash(String password, String name) { byte[] digest = new byte[DIGESTBYTES]; NESSIEinit(); NESSIEadd(password); @@ -393,8 +400,8 @@ public class WHIRLPOOL implements EncryptionMethod { } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(computeHash(password, "", "")); + public boolean comparePassword(String hash, String password, String playerName) { + return hash.equals(computeHash(password, null, null)); } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java index 3147ddbb..65059119 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java @@ -1,13 +1,18 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; + import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; -/** - */ +@Recommendation(Usage.OK) +@HasSalt(value = SaltType.TEXT, length = 9) public class WORDPRESS implements EncryptionMethod { private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; @@ -102,18 +107,25 @@ public class WORDPRESS implements EncryptionMethod { } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { byte random[] = new byte[6]; - this.randomGen.nextBytes(random); + randomGen.nextBytes(random); return crypt(password, gensaltPrivate(stringToUtf8(new String(random)))); } + public String computeHash(String password, String name) { + return computeHash(password, null, null); + } + @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String hash, String password, String playerName) { String comparedHash = crypt(password, hash); return comparedHash.equals(hash); } + public String generateSalt() { + // This hash uses a salt, but it is not exposed to the outside + return null; + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java index aa287ddd..da0591aa 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java @@ -1,9 +1,13 @@ package fr.xephi.authme.security.crypts; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; -/** - */ +@Recommendation(Usage.RECOMMENDED) +@HasSalt(value = SaltType.TEXT, length = 12) public class XAUTH implements EncryptionMethod { public static String getWhirlpool(String message) { @@ -16,19 +20,25 @@ public class XAUTH implements EncryptionMethod { } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { String hash = getWhirlpool(salt + password).toLowerCase(); int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length()); return hash.substring(0, saltPos) + salt + hash.substring(saltPos); } + public String computeHash(String password, String name) { + return computeHash(password, generateSalt(), null); + } + @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String hash, String password, String playerName) { int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length()); String salt = hash.substring(saltPos, saltPos + 12); return hash.equals(computeHash(password, salt, "")); } + public String generateSalt() { + return HashUtils.generateSalt(12); + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/HasSalt.java b/src/main/java/fr/xephi/authme/security/crypts/description/HasSalt.java new file mode 100644 index 00000000..b903a88d --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/description/HasSalt.java @@ -0,0 +1,13 @@ +package fr.xephi.authme.security.crypts.description; + +/** + * Describes the type of salt the encryption algorithm uses. This is purely for documentation + * purposes and is ignored by the code. + */ +public @interface HasSalt { + + SaltType value(); + + int length() default 0; + +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/Recommendation.java b/src/main/java/fr/xephi/authme/security/crypts/description/Recommendation.java new file mode 100644 index 00000000..06ebc22c --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/description/Recommendation.java @@ -0,0 +1,10 @@ +package fr.xephi.authme.security.crypts.description; + +/** + * Annotation to mark a hash algorithm with the usage recommendation, see {@link Usage}. + */ +public @interface Recommendation { + + Usage value(); + +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/SaltType.java b/src/main/java/fr/xephi/authme/security/crypts/description/SaltType.java new file mode 100644 index 00000000..c7d3ba1d --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/description/SaltType.java @@ -0,0 +1,17 @@ +package fr.xephi.authme.security.crypts.description; + +/** + * The type of salt used by an encryption algorithms. + */ +public enum SaltType { + + /** Random, newly generated text. */ + TEXT, + + /** The username, including variations or repetitions. */ + USERNAME, + + /** No salt. */ + NONE + +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java b/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java new file mode 100644 index 00000000..990ee298 --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java @@ -0,0 +1,20 @@ +package fr.xephi.authme.security.crypts.description; + +/** + * Usage recommendation that can be provided for a hash algorithm. + */ +public enum Usage { + + /** The hash algorithm appears to be cryptographically secure and is one of the algorithms recommended by AuthMe. */ + RECOMMENDED, + + /** There are safer algorithms that can be chosen but using the algorithm is generally OK. */ + OK, + + /** Hash algorithm is not recommended to be used. Use only if required by another system. */ + DO_NOT_USE, + + /** The algorithm does not work properly; do not use. */ + DOES_NOT_WORK + +} diff --git a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java index ea18df9a..d573b288 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java @@ -134,7 +134,7 @@ public abstract class AbstractEncryptionMethodTest { return BCRYPT.gensalt(); } else if (method instanceof MD5 || method instanceof WORDPRESS || method instanceof SMF || method instanceof SHA512 || method instanceof SHA1 || method instanceof ROYALAUTH - || method instanceof DOUBLEMD5) { + || method instanceof DOUBLEMD5 || method instanceof CRAZYCRYPT1) { return ""; } else if (method instanceof JOOMLA || method instanceof SALTEDSHA512) { return PasswordSecurity.createSalt(32); diff --git a/src/test/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1Test.java b/src/test/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1Test.java new file mode 100644 index 00000000..fa27de3b --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1Test.java @@ -0,0 +1,16 @@ +package fr.xephi.authme.security.crypts; + +/** + * Test for {@link CRAZYCRYPT1}. + */ +public class CRAZYCRYPT1Test extends AbstractEncryptionMethodTest { + + public CRAZYCRYPT1Test() { + super(new CRAZYCRYPT1(), + "d5c76eb36417d4e97ec62609619e40a9e549a2598d0dab5a7194fd997a9305af78de2b93f958e150d19dd1e7f821043379ddf5f9c7f352bf27df91ae4913f3e8", // password + "49c63f827c88196871e344e589bd46cc4fa6db3c27801bbad5374c0d216381977627c1d76f2114667d5dd117e046f7493eb06e4f461f4f848aa08f6f40a3e934", // PassWord1 + "6fefb0233bab6e6efb9c16f82cb0d8f569488905e2dae0e7c9dde700e7363da67213d37c44bc15f4a05854c9c21e5688389d416413c7309398aa96cb1f341d08", // &^%te$t?Pw@_ + "46f51cde7657fdec9848bad0fd8e7fb97783cf5335f94dbb5260899ab0b04022a52d651b1c45345328850178e7165308c8c213040b0864de66018a0b769d37cb"); // âË_3(íù* + } + +} From 513ff9a9289463d8f08f2ddf7387bcdabedee182 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 27 Dec 2015 22:16:16 +0100 Subject: [PATCH 02/18] #358 Make RandomString static & generate all rand. strings with it - Remove dubious random String generator on HashUtils - Make further hash classes use HashUtils --- .../executable/captcha/CaptchaCommand.java | 2 +- .../executable/email/RecoverEmailCommand.java | 3 +- .../process/login/AsynchronousLogin.java | 13 +++--- .../fr/xephi/authme/security/HashUtils.java | 37 +++++++--------- .../xephi/authme/security/RandomString.java | 30 ++++++------- .../security/crypts/CryptPBKDF2Django.java | 6 +-- .../xephi/authme/security/crypts/JOOMLA.java | 5 ++- .../xephi/authme/security/crypts/MD5VB.java | 6 +-- .../xephi/authme/security/crypts/SHA256.java | 43 ++++++++++--------- .../xephi/authme/security/crypts/SHA512.java | 39 ++++++++--------- .../fr/xephi/authme/security/crypts/SMF.java | 37 ++++++++-------- .../authme/security/crypts/WORDPRESS.java | 2 +- .../xephi/authme/security/crypts/XAUTH.java | 4 +- .../security/crypts/description/HasSalt.java | 9 ++++ .../crypts/description/Recommendation.java | 8 ++++ .../security/crypts/description/SaltType.java | 2 +- .../security/crypts/description/Usage.java | 2 +- 17 files changed, 125 insertions(+), 123 deletions(-) diff --git a/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java b/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java index b9e23681..d21bac77 100644 --- a/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java @@ -41,7 +41,7 @@ public class CaptchaCommand extends PlayerCommand { if (Settings.useCaptcha && !captcha.equals(plugin.cap.get(playerNameLowerCase))) { plugin.cap.remove(playerNameLowerCase); - String randStr = new RandomString(Settings.captchaLength).nextString(); + String randStr = RandomString.generate(Settings.captchaLength); plugin.cap.put(playerNameLowerCase, randStr); commandService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, plugin.cap.get(playerNameLowerCase)); return; diff --git a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java index 63a8825d..07f05bfc 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java @@ -38,8 +38,7 @@ public class RecoverEmailCommand extends PlayerCommand { return; } try { - RandomString rand = new RandomString(Settings.getRecoveryPassLength); - String thePass = rand.nextString(); + String thePass = RandomString.generate(Settings.getRecoveryPassLength); String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, thePass, playerName); PlayerAuth auth; if (PlayerCache.getInstance().isAuthenticated(playerName)) { diff --git a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java index fe47be51..e0983d22 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -26,12 +26,11 @@ import java.util.List; */ public class AsynchronousLogin { - private static final RandomString rdm = new RandomString(Settings.captchaLength); - protected final Player player; - protected final String name; - protected final String realName; - protected final String password; - protected final boolean forceLogin; + private final Player player; + private final String name; + private final String realName; + private final String password; + private final boolean forceLogin; private final AuthMe plugin; private final DataSource database; private final Messages m; @@ -70,7 +69,7 @@ public class AsynchronousLogin { plugin.captcha.putIfAbsent(name, i); } if (plugin.captcha.containsKey(name) && plugin.captcha.get(name) > Settings.maxLoginTry) { - plugin.cap.putIfAbsent(name, rdm.nextString()); + plugin.cap.putIfAbsent(name, RandomString.generate(Settings.captchaLength)); m.send(player, MessageKey.USAGE_CAPTCHA, plugin.cap.get(name)); return true; } diff --git a/src/main/java/fr/xephi/authme/security/HashUtils.java b/src/main/java/fr/xephi/authme/security/HashUtils.java index 0f4f7318..c5ae4f89 100644 --- a/src/main/java/fr/xephi/authme/security/HashUtils.java +++ b/src/main/java/fr/xephi/authme/security/HashUtils.java @@ -3,40 +3,27 @@ package fr.xephi.authme.security; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; public final class HashUtils { - private static final SecureRandom RANDOM = new SecureRandom(); - private HashUtils() { } - public static String hash(String message, MessageDigestAlgorithm algorithm) { - MessageDigest md = getDigest(algorithm); - md.reset(); - md.update(message.getBytes()); - byte[] digest = md.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); - } - public static String sha1(String message) { return hash(message, MessageDigestAlgorithm.SHA1); } - public static String md5(String message) { - return hash(message, MessageDigestAlgorithm.MD5); + public static String sha256(String message) { + return hash(message, MessageDigestAlgorithm.SHA256); } - // Only works for length up to 40! - public static String generateSalt(int length) { - byte[] msg = new byte[40]; - RANDOM.nextBytes(msg); - MessageDigest sha1 = getDigest(MessageDigestAlgorithm.SHA1); - sha1.reset(); - byte[] digest = sha1.digest(msg); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)).substring(0, length); + public static String sha512(String message) { + return hash(message, MessageDigestAlgorithm.SHA512); + } + + public static String md5(String message) { + return hash(message, MessageDigestAlgorithm.MD5); } public static MessageDigest getDigest(MessageDigestAlgorithm algorithm) { @@ -48,6 +35,12 @@ public final class HashUtils { } } - + private static String hash(String message, MessageDigestAlgorithm algorithm) { + MessageDigest md = getDigest(algorithm); + md.reset(); + md.update(message.getBytes()); + byte[] digest = md.digest(); + return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + } } diff --git a/src/main/java/fr/xephi/authme/security/RandomString.java b/src/main/java/fr/xephi/authme/security/RandomString.java index 8b1b8d63..c34a6465 100644 --- a/src/main/java/fr/xephi/authme/security/RandomString.java +++ b/src/main/java/fr/xephi/authme/security/RandomString.java @@ -1,13 +1,13 @@ package fr.xephi.authme.security; import java.security.SecureRandom; -import java.util.Calendar; import java.util.Random; -public class RandomString { +public final class RandomString { private static final char[] chars = new char[36]; private static final Random RANDOM = new SecureRandom(); + private static final int HEX_MAX_INDEX = 15; static { for (int idx = 0; idx < 10; ++idx) { @@ -18,30 +18,24 @@ public class RandomString { } } - private final Random random = new Random(); - - private final char[] buf; - - public RandomString(int length) { - if (length < 1) - throw new IllegalArgumentException("length < 1: " + length); - buf = new char[length]; - random.setSeed(Calendar.getInstance().getTimeInMillis()); - } - - public String nextString() { - for (int idx = 0; idx < buf.length; ++idx) - buf[idx] = chars[random.nextInt(chars.length)]; - return new String(buf); + private RandomString() { } public static String generate(int length) { + return generate(length, chars.length); + } + + public static String generateHex(int length) { + return generate(length, HEX_MAX_INDEX); + } + + private static String generate(int length, int maxIndex) { 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)]); + sb.append(chars[RANDOM.nextInt(maxIndex)]); } return sb.toString(); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java index c4df8ab4..ab436697 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java @@ -1,6 +1,6 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.RandomString; import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.SaltType; @@ -10,7 +10,7 @@ import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters; import javax.xml.bind.DatatypeConverter; -@Recommendation(Usage.OK) +@Recommendation(Usage.ACCEPTABLE) @HasSalt(value = SaltType.TEXT, length = 12) public class CryptPBKDF2Django implements EncryptionMethod { @@ -38,7 +38,7 @@ public class CryptPBKDF2Django implements EncryptionMethod { } public String generateSalt() { - return HashUtils.generateSalt(12); + return RandomString.generateHex(12); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java index 36c09244..22371171 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java +++ b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java @@ -1,12 +1,13 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.RandomString; import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; -@Recommendation(Usage.OK) +@Recommendation(Usage.ACCEPTABLE) @HasSalt(value = SaltType.TEXT, length = 32) public class JOOMLA implements EncryptionMethod { @@ -20,7 +21,7 @@ public class JOOMLA implements EncryptionMethod { } public String generateSalt() { - return HashUtils.generateSalt(32); + return RandomString.generateHex(32); } @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java index cbd30eb2..a994f7ea 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java @@ -1,6 +1,6 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.RandomString; import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.SaltType; @@ -8,7 +8,7 @@ import fr.xephi.authme.security.crypts.description.Usage; import static fr.xephi.authme.security.HashUtils.md5; -@Recommendation(Usage.OK) +@Recommendation(Usage.ACCEPTABLE) @HasSalt(value = SaltType.TEXT, length = 16) public class MD5VB implements EncryptionMethod { @@ -28,7 +28,7 @@ public class MD5VB implements EncryptionMethod { } public String generateSalt() { - return HashUtils.generateSalt(16); + return RandomString.generateHex(16); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java index 834b208f..5024072d 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java @@ -1,33 +1,34 @@ package fr.xephi.authme.security.crypts; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.RandomString; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; -/** - */ +import static fr.xephi.authme.security.HashUtils.sha256; + +@Recommendation(Usage.RECOMMENDED) +@HasSalt(value = SaltType.TEXT, length = 16) public class SHA256 implements EncryptionMethod { - private static String getSHA256(String message) - throws NoSuchAlgorithmException { - MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); - sha256.reset(); - sha256.update(message.getBytes()); - byte[] digest = sha256.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return "$SHA$" + salt + "$" + sha256(sha256(password) + salt); + } + + public String computeHash(String password, String name) { + return computeHash(password, generateSalt(), name); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return "$SHA$" + salt + "$" + getSHA256(getSHA256(password) + salt); - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String hash, String password, String playerName) { String[] line = hash.split("\\$"); - return hash.equals(computeHash(password, line[2], "")); + return line.length == 4 && hash.equals(computeHash(password, line[2], "")); + } + + public String generateSalt() { + return RandomString.generateHex(16); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java index 888406a1..10843205 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java @@ -1,31 +1,30 @@ package fr.xephi.authme.security.crypts; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; -/** - */ +@Recommendation(Usage.DO_NOT_USE) +@HasSalt(SaltType.NONE) public class SHA512 implements EncryptionMethod { - private static String getSHA512(String message) - throws NoSuchAlgorithmException { - MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); - sha512.reset(); - sha512.update(message.getBytes()); - byte[] digest = sha512.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return computeHash(password, name); + } + + public String computeHash(String password, String name) { + return HashUtils.sha512(password); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getSHA512(password); - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String hash, String password, String playerName) { return hash.equals(computeHash(password, "", "")); } + + public String generateSalt() { + return null; + } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SMF.java b/src/main/java/fr/xephi/authme/security/crypts/SMF.java index d2d4f74d..0a2346eb 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SMF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SMF.java @@ -1,31 +1,30 @@ package fr.xephi.authme.security.crypts; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; -/** - */ +@Recommendation(Usage.DO_NOT_USE) +@HasSalt(SaltType.USERNAME) public class SMF implements EncryptionMethod { - private static String getSHA1(String message) - throws NoSuchAlgorithmException { - MessageDigest sha1 = MessageDigest.getInstance("SHA1"); - sha1.reset(); - sha1.update(message.getBytes()); - byte[] digest = sha1.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return computeHash(password, name); + } + + public String computeHash(String password, String name) { + return HashUtils.sha1(name.toLowerCase() + password); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getSHA1(name.toLowerCase() + password); + public boolean comparePassword(String hash, String password, String playerName) { + return hash.equals(computeHash(password, playerName)); } - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(computeHash(password, null, playerName)); + public String generateSalt() { + return null; } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java index 65059119..f9a17632 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java @@ -11,7 +11,7 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; -@Recommendation(Usage.OK) +@Recommendation(Usage.ACCEPTABLE) @HasSalt(value = SaltType.TEXT, length = 9) public class WORDPRESS implements EncryptionMethod { diff --git a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java index da0591aa..b3f07752 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java @@ -1,6 +1,6 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.RandomString; import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.SaltType; @@ -38,7 +38,7 @@ public class XAUTH implements EncryptionMethod { } public String generateSalt() { - return HashUtils.generateSalt(12); + return RandomString.generateHex(12); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/HasSalt.java b/src/main/java/fr/xephi/authme/security/crypts/description/HasSalt.java index b903a88d..1baf1e19 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/description/HasSalt.java +++ b/src/main/java/fr/xephi/authme/security/crypts/description/HasSalt.java @@ -1,13 +1,22 @@ package fr.xephi.authme.security.crypts.description; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * Describes the type of salt the encryption algorithm uses. This is purely for documentation * purposes and is ignored by the code. */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) public @interface HasSalt { + /** The type of the salt. */ SaltType value(); + /** For text salts, the length of the salt. */ int length() default 0; } diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/Recommendation.java b/src/main/java/fr/xephi/authme/security/crypts/description/Recommendation.java index 06ebc22c..f37c3eac 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/description/Recommendation.java +++ b/src/main/java/fr/xephi/authme/security/crypts/description/Recommendation.java @@ -1,10 +1,18 @@ package fr.xephi.authme.security.crypts.description; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * Annotation to mark a hash algorithm with the usage recommendation, see {@link Usage}. */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) public @interface Recommendation { + /** The recommendation for using the hash algorithm. */ Usage value(); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/SaltType.java b/src/main/java/fr/xephi/authme/security/crypts/description/SaltType.java index c7d3ba1d..f91ca61c 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/description/SaltType.java +++ b/src/main/java/fr/xephi/authme/security/crypts/description/SaltType.java @@ -8,7 +8,7 @@ public enum SaltType { /** Random, newly generated text. */ TEXT, - /** The username, including variations or repetitions. */ + /** Salt is based on the username, including variations and repetitions. */ USERNAME, /** No salt. */ diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java b/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java index 990ee298..ecf37a98 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java +++ b/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java @@ -9,7 +9,7 @@ public enum Usage { RECOMMENDED, /** There are safer algorithms that can be chosen but using the algorithm is generally OK. */ - OK, + ACCEPTABLE, /** Hash algorithm is not recommended to be used. Use only if required by another system. */ DO_NOT_USE, From 804a670e080c29f78d44f7854aceb7ded3ffad72 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 27 Dec 2015 23:37:07 +0100 Subject: [PATCH 03/18] Create test for HashUtils and RandomString --- .../xephi/authme/security/RandomString.java | 2 +- .../xephi/authme/security/HashUtilsTest.java | 116 ++++++++++++++++++ .../authme/security/RandomStringTest.java | 56 +++++++++ 3 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/test/java/fr/xephi/authme/security/HashUtilsTest.java create mode 100644 src/test/java/fr/xephi/authme/security/RandomStringTest.java diff --git a/src/main/java/fr/xephi/authme/security/RandomString.java b/src/main/java/fr/xephi/authme/security/RandomString.java index c34a6465..cff87ef0 100644 --- a/src/main/java/fr/xephi/authme/security/RandomString.java +++ b/src/main/java/fr/xephi/authme/security/RandomString.java @@ -7,7 +7,7 @@ public final class RandomString { private static final char[] chars = new char[36]; private static final Random RANDOM = new SecureRandom(); - private static final int HEX_MAX_INDEX = 15; + private static final int HEX_MAX_INDEX = 16; static { for (int idx = 0; idx < 10; ++idx) { diff --git a/src/test/java/fr/xephi/authme/security/HashUtilsTest.java b/src/test/java/fr/xephi/authme/security/HashUtilsTest.java new file mode 100644 index 00000000..02237cde --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/HashUtilsTest.java @@ -0,0 +1,116 @@ +package fr.xephi.authme.security; + +import org.junit.Test; + +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; + +/** + * Test for {@link HashUtils}. + */ +public class HashUtilsTest { + + /** + * List of passwords whose hash is provided to the class to test against. + */ + public static final String[] GIVEN_PASSWORDS = {"", "password", "PassWord1", "&^%te$t?Pw@_"}; + + @Test + public void shouldHashMd5() { + // given + String[] correctHashes = { + "d41d8cd98f00b204e9800998ecf8427e", // empty string + "5f4dcc3b5aa765d61d8327deb882cf99", // password + "f2126d405f46ed603ff5b2950f062c96", // PassWord1 + "0833dcd2bc741f90c46bbac5498fd08f" // &^%te$t?Pw@_ + }; + + // when + List result = new ArrayList<>(); + for (String password : GIVEN_PASSWORDS) { + result.add(HashUtils.md5(password)); + } + + // then + assertThat(result, contains(correctHashes)); + } + + @Test + public void shouldHashSha1() { + // given + String[] correctHashes = { + "da39a3ee5e6b4b0d3255bfef95601890afd80709", // empty string + "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", // password + "285d0c707f9644b75e1a87a62f25d0efb56800f0", // PassWord1 + "a42ef8e61e890af80461ca5dcded25cbfcf407a4" // &^%te$t?Pw@_ + }; + + // when + List result = new ArrayList<>(); + for (String password : GIVEN_PASSWORDS) { + result.add(HashUtils.sha1(password)); + } + + // then + assertThat(result, contains(correctHashes)); + } + + @Test + public void shouldHashSha256() { + // given + String[] correctHashes = { + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", // empty string + "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8", // password + "c04265d72b749debd67451c083785aa572742e3222e86884de16485fa14b55e7", // PassWord1 + "005e3d7439d3e9a60a9d74aa1c763b36bfebec8e434ab6c5efab3df37eb2dae6" // &^%te$t?Pw@_ + }; + + // when + List result = new ArrayList<>(); + for (String password : GIVEN_PASSWORDS) { + result.add(HashUtils.sha256(password)); + } + + // then + assertThat(result, contains(correctHashes)); + } + + + @Test + public void shouldHashSha512() { + // given + String[] correctHashes = { + "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", // empty string + "b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86", // password + "ae9942149995a8171391625b36da134d5e288c721650d7c8d2d464fb49a49f3f551e4916ab1e097d9dd1201b01d69b1dccdefa3d2524a66092fb61b3df6e7e71", // PassWord1 + "8c4f3df78db191142d819a72c16058b9e1ea41ae9b1649e1184eb89e30344c51c9c71039c483cf2f1b76b51480d8459d7eb3cfbaa24b07f2041d1551af4ead75" // &^%te$t?Pw@_ + }; + + // when + List result = new ArrayList<>(); + for (String password : GIVEN_PASSWORDS) { + result.add(HashUtils.sha512(password)); + } + + // then + assertThat(result, contains(correctHashes)); + } + + @Test + public void shouldRetrieveMd5Instance() { + // given + MessageDigestAlgorithm algorithm = MessageDigestAlgorithm.MD5; + + // when + MessageDigest digest = HashUtils.getDigest(algorithm); + + // then + assertThat(digest.getAlgorithm(), equalTo("MD5")); + } + +} diff --git a/src/test/java/fr/xephi/authme/security/RandomStringTest.java b/src/test/java/fr/xephi/authme/security/RandomStringTest.java new file mode 100644 index 00000000..9c40e9c3 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/RandomStringTest.java @@ -0,0 +1,56 @@ +package fr.xephi.authme.security; + +import org.junit.Test; + +import java.util.regex.Pattern; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +/** + * Test for {@link RandomString}. + */ +public class RandomStringTest { + + @Test + public void shouldGenerateRandomStrings() { + // given + int[] lengths = {0, 1, 19, 142, 1872}; + Pattern badChars = Pattern.compile(".*[^0-9a-z].*"); + + // when / then + for (int length : lengths) { + String result = RandomString.generate(length); + assertThat("Result '" + result + "' should have length " + length, + result.length(), equalTo(length)); + assertThat("Result '" + result + "' should only have characters a-z, 0-9", + badChars.matcher(result).matches(), equalTo(false)); + } + } + + @Test + public void shouldGenerateRandomHexString() { + // given + int[] lengths = {0, 1, 21, 160, 1784}; + Pattern badChars = Pattern.compile(".*[^0-9a-f].*"); + + // when / then + for (int length : lengths) { + String result = RandomString.generateHex(length); + assertThat("Result '" + result + "' should have length " + length, + result.length(), equalTo(length)); + assertThat("Result '" + result + "' should only have characters a-z, 0-9", + badChars.matcher(result).matches(), equalTo(false)); + System.out.println(result); + } + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowForInvalidLength() { + // given/when + RandomString.generate(-3); + + // then - throw exception + } + +} From 1cabc47ff93e96818c5157e545a47acc56229dce Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 27 Dec 2015 23:40:32 +0100 Subject: [PATCH 04/18] Minor - remove debug println from test --- src/test/java/fr/xephi/authme/security/RandomStringTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/fr/xephi/authme/security/RandomStringTest.java b/src/test/java/fr/xephi/authme/security/RandomStringTest.java index 9c40e9c3..78e54d2f 100644 --- a/src/test/java/fr/xephi/authme/security/RandomStringTest.java +++ b/src/test/java/fr/xephi/authme/security/RandomStringTest.java @@ -41,7 +41,6 @@ public class RandomStringTest { result.length(), equalTo(length)); assertThat("Result '" + result + "' should only have characters a-z, 0-9", badChars.matcher(result).matches(), equalTo(false)); - System.out.println(result); } } From 31730699acec0547081c7ff2dc37f5a64d89fa54 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 28 Dec 2015 16:23:08 +0100 Subject: [PATCH 05/18] #358 Start refactoring PasswordSecurity - Add new methods temporarily to NewEncrMethod interface - No data source access within EncryptionMethod implementations - Generate the salt within the EncryptionMethod implementation - Deprecate static methods on PasswordSecurity - Adjust AbstractEncryptionMethodTest to test the classes with the new interface - Add getter for data source instead of accessing field directly --- src/main/java/fr/xephi/authme/AuthMe.java | 28 ++--- .../xephi/authme/command/CommandService.java | 3 +- .../process/login/AsynchronousLogin.java | 3 +- .../xephi/authme/security/HashAlgorithm.java | 5 +- .../authme/security/PasswordSecurity.java | 78 ++++++++++--- .../security/crypts/EncryptionMethod.java | 5 +- .../authme/security/crypts/HashResult.java | 26 +++++ .../authme/security/crypts/NewEncrMethod.java | 17 +++ .../authme/security/crypts/SALTED2MD5.java | 43 +++++++- .../authme/security/RandomStringTest.java | 2 +- .../crypts/AbstractEncryptionMethodTest.java | 104 ++++++++++++++---- .../security/crypts/SALTED2MD5Test.java | 26 +++++ 12 files changed, 272 insertions(+), 68 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/security/crypts/HashResult.java create mode 100644 src/main/java/fr/xephi/authme/security/crypts/NewEncrMethod.java create mode 100644 src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 32188d23..efe629a2 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -190,7 +190,6 @@ public class AuthMe extends JavaPlugin { /** * Method called when the server enables the plugin. - * @see org.bukkit.plugin.Plugin#onEnable() */ @Override public void onEnable() { @@ -504,11 +503,6 @@ public class AuthMe extends JavaPlugin { } } - /** - * Method onDisable. - * - * @see org.bukkit.plugin.Plugin#onDisable() - */ @Override public void onDisable() { // Save player data @@ -537,16 +531,13 @@ public class AuthMe extends JavaPlugin { // Stop/unload the server/plugin as defined in the configuration public void stopOrUnload() { if (Settings.isStopEnabled) { - ConsoleLogger.showError("THE SERVER IS GOING TO SHUTDOWN AS DEFINED IN THE CONFIGURATION!"); + ConsoleLogger.showError("THE SERVER IS GOING TO SHUT DOWN AS DEFINED IN THE CONFIGURATION!"); server.shutdown(); } else { server.getPluginManager().disablePlugin(AuthMe.getInstance()); } } - /** - * Method setupDatabase. - */ public void setupDatabase() throws Exception { if (database != null) database.close(); @@ -713,8 +704,8 @@ public class AuthMe extends JavaPlugin { } // Save Player Data - public void savePlayer(Player player) { - if ((Utils.isNPC(player)) || (Utils.isUnrestricted(player))) { + private void savePlayer(Player player) { + if (Utils.isNPC(player) || Utils.isUnrestricted(player)) { return; } String name = player.getName().toLowerCase(); @@ -829,10 +820,10 @@ public class AuthMe extends JavaPlugin { // Return the AuthMe spawn point private Location getAuthMeSpawn(Player player) { - if ((!database.isAuthAvailable(player.getName().toLowerCase()) || !player.hasPlayedBefore()) && (Spawn.getInstance().getFirstSpawn() != null)) { + if ((!database.isAuthAvailable(player.getName().toLowerCase()) || !player.hasPlayedBefore()) + && (Spawn.getInstance().getFirstSpawn() != null)) { return Spawn.getInstance().getFirstSpawn(); - } - if (Spawn.getInstance().getSpawn() != null) { + } else if (Spawn.getInstance().getSpawn() != null) { return Spawn.getInstance().getSpawn(); } return player.getWorld().getSpawnLocation(); @@ -882,8 +873,7 @@ public class AuthMe extends JavaPlugin { * */ @Deprecated - public void getVerygamesIp(final Player player) - { + public void getVerygamesIp(final Player player) { final String name = player.getName().toLowerCase(); Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable(){ @Override @@ -979,4 +969,8 @@ public class AuthMe extends JavaPlugin { return management; } + public DataSource getDataSource() { + return database; + } + } diff --git a/src/main/java/fr/xephi/authme/command/CommandService.java b/src/main/java/fr/xephi/authme/command/CommandService.java index 85cf9b22..4e70a1fc 100644 --- a/src/main/java/fr/xephi/authme/command/CommandService.java +++ b/src/main/java/fr/xephi/authme/command/CommandService.java @@ -88,8 +88,7 @@ public class CommandService { * @return The used data source */ public DataSource getDataSource() { - // TODO ljacqu 20151222: Add getter for .database and rename the field to dataSource - return authMe.database; + return authMe.getDataSource(); } /** diff --git a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java index e0983d22..c2bbbd64 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -119,8 +119,7 @@ public class AsynchronousLogin { return null; } - if (Settings.preventOtherCase && !player.getName().equals(pAuth.getRealName())) - { + if (Settings.preventOtherCase && !player.getName().equals(pAuth.getRealName())) { // TODO: Add a message like : MessageKey.INVALID_NAME_CASE m.send(player, MessageKey.USERNAME_ALREADY_ONLINE_ERROR); return null; diff --git a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java index 315fa9fd..ad212767 100644 --- a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java +++ b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java @@ -3,6 +3,9 @@ package fr.xephi.authme.security; import fr.xephi.authme.security.crypts.EncryptionMethod; /** + * The list of hash algorithms supported by AuthMe. The implementing class must define a public + * constructor which takes either no arguments, or a DataSource object (when the salt is stored + * separately, writes to the database are necessary). */ public enum HashAlgorithm { @@ -36,7 +39,7 @@ public enum HashAlgorithm { SALTEDSHA512(fr.xephi.authme.security.crypts.SALTEDSHA512.class), CUSTOM(null); - final Class clazz; + private final Class clazz; /** * Constructor for HashAlgorithm. diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index 1ffa423d..e8ddecbc 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -2,37 +2,37 @@ package fr.xephi.authme.security; import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.PasswordEncryptionEvent; import fr.xephi.authme.security.crypts.BCRYPT; import fr.xephi.authme.security.crypts.EncryptionMethod; +import fr.xephi.authme.security.crypts.HashResult; +import fr.xephi.authme.security.crypts.NewEncrMethod; import fr.xephi.authme.settings.Settings; import org.bukkit.Bukkit; -import java.math.BigInteger; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; import java.util.HashMap; /** */ public class PasswordSecurity { + @Deprecated public static final HashMap userSalt = new HashMap<>(); - private static final SecureRandom rnd = new SecureRandom(); + private final DataSource dataSource; - public static String createSalt(int length) - throws NoSuchAlgorithmException { - byte[] msg = new byte[40]; - rnd.nextBytes(msg); - MessageDigest sha1 = MessageDigest.getInstance("SHA1"); - sha1.reset(); - byte[] digest = sha1.digest(msg); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)).substring(0, length); + public PasswordSecurity(DataSource dataSource) { + this.dataSource = dataSource; } - public static String getHash(HashAlgorithm alg, String password, - String playerName) throws NoSuchAlgorithmException { + @Deprecated + public static String createSalt(int length) { + return RandomString.generateHex(length); + } + + @Deprecated + public static String getHash(HashAlgorithm alg, String password, String playerName) throws NoSuchAlgorithmException { EncryptionMethod method; try { if (alg != HashAlgorithm.CUSTOM) @@ -126,6 +126,7 @@ public class PasswordSecurity { return method.computeHash(password, salt, playerName); } + @Deprecated public static boolean comparePasswordWithHash(String password, String hash, String playerName) throws NoSuchAlgorithmException { HashAlgorithm algorithm = Settings.getPasswordHash; @@ -157,6 +158,55 @@ public class PasswordSecurity { return false; } + public HashResult computeHash(HashAlgorithm algorithm, String password, String playerName) { + EncryptionMethod method1 = initializeEncryptionMethod(algorithm, playerName); + // TODO #358: Remove this check: + NewEncrMethod method; + if (method1 instanceof NewEncrMethod) { + method = (NewEncrMethod) method1; + } else { + throw new RuntimeException("TODO #358: Class not yet extended with NewEncrMethod methods"); + } + + return method.computeHash(password, playerName); + } + + public boolean comparePassword(HashAlgorithm algorithm, String hash, String password, String playerName) { + EncryptionMethod method1 = initializeEncryptionMethod(algorithm, playerName); + // TODO #358: Remove this check: + NewEncrMethod method; + if (method1 instanceof NewEncrMethod) { + method = (NewEncrMethod) method1; + } else { + throw new RuntimeException("TODO #358: Class not yet extended with NewEncrMethod methods"); + } + + String salt = null; + if (method.hasSeparateSalt()) { + PlayerAuth auth = dataSource.getAuth(playerName); + salt = (auth != null) ? auth.getSalt() : null; + } + return method.comparePassword(hash, password, salt, playerName); + // TODO #358: Add logic for Settings.supportOldPassword + } + + private EncryptionMethod initializeEncryptionMethod(HashAlgorithm algorithm, String playerName) { + EncryptionMethod method; + try { + method = HashAlgorithm.CUSTOM.equals(algorithm) + ? null + : algorithm.getClazz().newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalStateException("Constructor for '" + algorithm.getClazz() + + "' could not be invoked. (Is it public with no arguments?)", e); + } + + PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName); + Bukkit.getPluginManager().callEvent(event); + return event.getMethod(); + } + + @Deprecated private static boolean compareWithAllEncryptionMethod(String password, String hash, String playerName) { for (HashAlgorithm algo : HashAlgorithm.values()) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java index 04add2be..d0acd622 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java @@ -29,11 +29,8 @@ public interface EncryptionMethod { * * @return True if the password matches, false otherwise */ + @Deprecated boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException; - // String generateSalt(); - - // String computeHash(String password, String name); - } diff --git a/src/main/java/fr/xephi/authme/security/crypts/HashResult.java b/src/main/java/fr/xephi/authme/security/crypts/HashResult.java new file mode 100644 index 00000000..8ace3038 --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/HashResult.java @@ -0,0 +1,26 @@ +package fr.xephi.authme.security.crypts; + +/** + * The result of a hash computation. + */ +public class HashResult { + + /** The generated hash. */ + private final String hash; + /** The generated salt; may be null if no salt is used or if the salt is included in the hash output. */ + private final String salt; + + public HashResult(String hash, String salt) { + this.hash = hash; + this.salt = salt; + } + + public String getHash() { + return hash; + } + + public String getSalt() { + return salt; + } + +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/NewEncrMethod.java b/src/main/java/fr/xephi/authme/security/crypts/NewEncrMethod.java new file mode 100644 index 00000000..1a0bde0e --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/NewEncrMethod.java @@ -0,0 +1,17 @@ +package fr.xephi.authme.security.crypts; + +/** + * Temporary interface for additional methods that will be added to {@link EncryptionMethod}. + * TODO #358: Move methods to EncryptionMethod interface and delete this. + */ +public interface NewEncrMethod extends EncryptionMethod { + + HashResult computeHash(String password, String name); + + String generateSalt(); + + boolean hasSeparateSalt(); + + boolean comparePassword(String hash, String password, String salt, String name); + +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java index 1de37d50..60664214 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java @@ -1,14 +1,20 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.AuthMe; +import fr.xephi.authme.security.RandomString; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; +import fr.xephi.authme.settings.Settings; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -/** - */ -public class SALTED2MD5 implements EncryptionMethod { +@Recommendation(Usage.ACCEPTABLE) // presuming that length is something sensible (>= 8) +@HasSalt(value = SaltType.TEXT) // length defined by Settings.saltLength +public class SALTED2MD5 implements NewEncrMethod { private static String getMD5(String message) throws NoSuchAlgorithmException { @@ -25,10 +31,41 @@ public class SALTED2MD5 implements EncryptionMethod { return getMD5(getMD5(password) + salt); } + @Override + public HashResult computeHash(String password, String name) { + try { + String salt = generateSalt(); + return new HashResult(computeHash(password, salt, name), salt); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); // TODO #358: Remove try-catch clause + } + } + @Override public boolean comparePassword(String hash, String password, String playerName) throws NoSuchAlgorithmException { String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); return hash.equals(getMD5(getMD5(password) + salt)); } + + @Override + public boolean comparePassword(String hash, String password, String salt, String name) { + try { + return hash.equals(computeHash(password, salt, name)); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + // TODO #358: Remove try-catch + } + } + + @Override + public String generateSalt() { + return RandomString.generateHex(Settings.saltLength); + } + + @Override + public boolean hasSeparateSalt() { + return true; + } + } diff --git a/src/test/java/fr/xephi/authme/security/RandomStringTest.java b/src/test/java/fr/xephi/authme/security/RandomStringTest.java index 78e54d2f..938f095c 100644 --- a/src/test/java/fr/xephi/authme/security/RandomStringTest.java +++ b/src/test/java/fr/xephi/authme/security/RandomStringTest.java @@ -39,7 +39,7 @@ public class RandomStringTest { String result = RandomString.generateHex(length); assertThat("Result '" + result + "' should have length " + length, result.length(), equalTo(length)); - assertThat("Result '" + result + "' should only have characters a-z, 0-9", + assertThat("Result '" + result + "' should only have characters a-f, 0-9", badChars.matcher(result).matches(), equalTo(false)); } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java index d573b288..6e812886 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java @@ -7,7 +7,9 @@ import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; /** @@ -33,6 +35,8 @@ public abstract class AbstractEncryptionMethodTest { private EncryptionMethod method; /** Map with the hashes against which the entries in GIVEN_PASSWORDS are tested. */ private Map hashes; + /** The accompanying salts for the hashes in {@link #hashes} if necessary. Can be empty otherwise. */ + private Map salts; /** * Create a new test for the given encryption method. @@ -45,12 +49,32 @@ public abstract class AbstractEncryptionMethodTest { */ public AbstractEncryptionMethodTest(EncryptionMethod method, String hash0, String hash1, String hash2, String hash3) { + // TODO #358: Throw if method.hasSeparateSalt() is true 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); + salts = new HashMap<>(); + } + + public AbstractEncryptionMethodTest(EncryptionMethod method, HashResult result0, HashResult result1, + HashResult result2, HashResult result3) { + // TODO #358: Throw if method.hasSeparateSalt() is false + this.method = method; + + hashes = new HashMap<>(); + hashes.put(GIVEN_PASSWORDS[0], result0.getHash()); + hashes.put(GIVEN_PASSWORDS[1], result1.getHash()); + hashes.put(GIVEN_PASSWORDS[2], result2.getHash()); + hashes.put(GIVEN_PASSWORDS[3], result3.getHash()); + + salts = new HashMap<>(); + salts.put(GIVEN_PASSWORDS[0], result0.getSalt()); + salts.put(GIVEN_PASSWORDS[1], result1.getSalt()); + salts.put(GIVEN_PASSWORDS[2], result2.getSalt()); + salts.put(GIVEN_PASSWORDS[3], result3.getSalt()); } @Test @@ -74,7 +98,33 @@ public abstract class AbstractEncryptionMethodTest { } @Test - public void testPasswordEquality() { + public void testPasswordEquality() throws NoSuchAlgorithmException { + // TODO #358: Remove "throws NoSuchAlgorithmException" on method declaration + // TODO #358: Remove instanceof and use this code always + if (method instanceof NewEncrMethod) { + NewEncrMethod method1 = (NewEncrMethod) method; + for (String password : INTERNAL_PASSWORDS) { + HashResult result = method1.computeHash(password, USERNAME); + final String hash = result.getHash(); + final String salt = result.getSalt(); + + // Check that the computeHash(password, salt, name) method has the same output for the returned salt + assertThat(hash, equalTo(method1.computeHash(password, salt, USERNAME))); + + assertTrue("Generated hash for '" + password + "' should match password (hash = '" + hash + "')", + method1.comparePassword(hash, password, salt, USERNAME)); + if (!password.equals(password.toLowerCase())) { + assertFalse("Lower-case of '" + password + "' should not match generated hash '" + hash + "'", + method1.comparePassword(hash, password.toLowerCase(), salt, USERNAME)); + } + if (!password.equals(password.toUpperCase())) { + assertFalse("Upper-case of '" + password + "' should not match generated hash '" + hash + "'", + method1.comparePassword(hash, password.toUpperCase(), salt, USERNAME)); + } + } + return; + } + for (String password : INTERNAL_PASSWORDS) { try { String hash = method.computeHash(password, getSalt(method), USERNAME); @@ -95,6 +145,15 @@ public abstract class AbstractEncryptionMethodTest { } private boolean doesGivenHashMatch(String password, EncryptionMethod method) { + // TODO #358: Remove casting checks and remove old code below + if (method instanceof NewEncrMethod) { + NewEncrMethod method1 = (NewEncrMethod) method; + String hash = hashes.get(password); + String salt = salts.get(password); + return method1.comparePassword(hash, password, salt, USERNAME); + } + + try { return method.comparePassword(hashes.get(password), password, USERNAME); } catch (NoSuchAlgorithmException e) { @@ -129,30 +188,27 @@ public abstract class AbstractEncryptionMethodTest { // 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 || method instanceof WORDPRESS || method instanceof SMF - || method instanceof SHA512 || method instanceof SHA1 || method instanceof ROYALAUTH - || method instanceof DOUBLEMD5 || method instanceof CRAZYCRYPT1) { - return ""; - } else if (method instanceof JOOMLA || method instanceof SALTEDSHA512) { - return PasswordSecurity.createSalt(32); - } else if (method instanceof SHA256 || method instanceof PHPBB || method instanceof WHIRLPOOL - || method instanceof MD5VB || method instanceof BCRYPT2Y) { - return PasswordSecurity.createSalt(16); - } else if (method instanceof WBB3) { - return PasswordSecurity.createSalt(40); - } else if (method instanceof XAUTH || method instanceof CryptPBKDF2Django - || method instanceof CryptPBKDF2) { - return PasswordSecurity.createSalt(12); - } else if (method instanceof WBB4) { - return BCRYPT.gensalt(8); - } - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); + if (method instanceof BCRYPT) { + return BCRYPT.gensalt(); + } else if (method instanceof MD5 || method instanceof WORDPRESS || method instanceof SMF + || method instanceof SHA512 || method instanceof SHA1 || method instanceof ROYALAUTH + || method instanceof DOUBLEMD5 || method instanceof CRAZYCRYPT1) { + return ""; + } else if (method instanceof JOOMLA || method instanceof SALTEDSHA512) { + return PasswordSecurity.createSalt(32); + } else if (method instanceof SHA256 || method instanceof PHPBB || method instanceof WHIRLPOOL + || method instanceof MD5VB || method instanceof BCRYPT2Y) { + return PasswordSecurity.createSalt(16); + } else if (method instanceof WBB3) { + return PasswordSecurity.createSalt(40); + } else if (method instanceof XAUTH || method instanceof CryptPBKDF2Django + || method instanceof CryptPBKDF2) { + return PasswordSecurity.createSalt(12); + } else if (method instanceof WBB4) { + return BCRYPT.gensalt(8); } - throw new IllegalStateException("Unknown EncryptionMethod for salt generation"); + System.out.println("Note: Cannot generate salt for unknown encryption method '" + method + "'"); + return ""; } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java b/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java new file mode 100644 index 00000000..338238fd --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java @@ -0,0 +1,26 @@ +package fr.xephi.authme.security.crypts; + +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.util.WrapperMock; +import org.junit.Before; + +/** + * Test for {@link SALTED2MD5}. + */ +public class SALTED2MD5Test extends AbstractEncryptionMethodTest { + + @Before + public void setUpAlgorithm() { + WrapperMock.createInstance(); + Settings.saltLength = 8; + } + + public SALTED2MD5Test() { + super(new SALTED2MD5(), + new HashResult("9f3d13dc01a6fe61fd669954174399f3", "9b5f5749"), // password + new HashResult("b28c32f624a4eb161d6adc9acb5bfc5b", "f750ba32"), // PassWord1 + new HashResult("38dcb83cc68424afe3cda012700c2bb1", "eb2c3394"), // &^%te$t?Pw@_ + new HashResult("ad25606eae5b760c8a2469d65578ac39", "04eee598")); // âË_3(íù*) + } + +} From 48d0a6572458bda2875bc3a155b41db43bd0445c Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 28 Dec 2015 20:10:45 +0100 Subject: [PATCH 06/18] #358 Create encryption method supertypes, add new methods --- .../authme/security/PasswordSecurity.java | 2 +- .../xephi/authme/security/crypts/BCRYPT.java | 26 +++++-- .../authme/security/crypts/CRAZYCRYPT1.java | 18 +---- .../authme/security/crypts/CryptPBKDF2.java | 6 +- .../security/crypts/CryptPBKDF2Django.java | 20 ++--- .../authme/security/crypts/DOUBLEMD5.java | 27 +------ .../security/crypts/EncryptionMethod.java | 8 +- .../authme/security/crypts/HashResult.java | 4 + .../security/crypts/HexSaltedMethod.java | 46 +++++++++++ .../fr/xephi/authme/security/crypts/IPB3.java | 48 ++++++++---- .../xephi/authme/security/crypts/JOOMLA.java | 28 +++---- .../fr/xephi/authme/security/crypts/MD5.java | 14 +--- .../xephi/authme/security/crypts/MD5VB.java | 25 ++---- .../fr/xephi/authme/security/crypts/MYBB.java | 42 ++++++---- .../xephi/authme/security/crypts/PHPBB.java | 25 +++--- .../authme/security/crypts/PHPFUSION.java | 53 ++++++++----- .../authme/security/crypts/PLAINTEXT.java | 16 +--- .../authme/security/crypts/ROYALAUTH.java | 33 ++------ .../authme/security/crypts/SALTED2MD5.java | 51 ++---------- .../authme/security/crypts/SALTEDSHA512.java | 36 +++------ .../fr/xephi/authme/security/crypts/SHA1.java | 23 +----- .../xephi/authme/security/crypts/SHA256.java | 17 ++-- .../xephi/authme/security/crypts/SHA512.java | 22 +----- .../fr/xephi/authme/security/crypts/SMF.java | 25 +----- .../security/crypts/SeparateSaltMethod.java | 35 +++++++++ .../security/crypts/UnsaltedMethod.java | 47 +++++++++++ .../security/crypts/UsernameSaltMethod.java | 45 +++++++++++ .../fr/xephi/authme/security/crypts/WBB3.java | 31 ++------ .../fr/xephi/authme/security/crypts/WBB4.java | 6 +- .../authme/security/crypts/WHIRLPOOL.java | 21 +---- .../authme/security/crypts/WORDPRESS.java | 17 ++-- .../xephi/authme/security/crypts/XAUTH.java | 25 +++--- .../fr/xephi/authme/security/crypts/XF.java | 39 ++++++++-- .../crypts/AbstractEncryptionMethodTest.java | 78 +++++-------------- .../authme/security/crypts/BcryptTest.java | 10 +++ .../authme/security/crypts/IPB3Test.java | 16 ++++ .../authme/security/crypts/MYBBTest.java | 16 ++++ .../xephi/authme/security/crypts/Md5Test.java | 9 +-- .../authme/security/crypts/PHPFUSIONTest.java | 20 +++++ .../security/crypts/SALTEDSHA512Test.java | 12 +-- .../authme/security/crypts/WBB3Test.java | 12 +-- .../authme/security/crypts/WORDPRESSTest.java | 4 + .../xephi/authme/security/crypts/XFTest.java | 17 ++++ 43 files changed, 551 insertions(+), 524 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java create mode 100644 src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java create mode 100644 src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java create mode 100644 src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java create mode 100644 src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java create mode 100644 src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java create mode 100644 src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java create mode 100644 src/test/java/fr/xephi/authme/security/crypts/XFTest.java diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index e8ddecbc..8fd8365b 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -198,7 +198,7 @@ public class PasswordSecurity { : algorithm.getClazz().newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new IllegalStateException("Constructor for '" + algorithm.getClazz() - + "' could not be invoked. (Is it public with no arguments?)", e); + + "' could not be invoked. (Is there no default constructor?)", e); } PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName); 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 beea0fe4..29ed5c11 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java @@ -17,6 +17,7 @@ import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Usage; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.settings.Settings; import java.io.UnsupportedEncodingException; import java.security.SecureRandom; @@ -63,9 +64,9 @@ import java.security.SecureRandom; * @author Damien Miller * @version 0.2 */ -@Recommendation(Usage.RECOMMENDED) -@HasSalt(value = SaltType.TEXT, length = BCRYPT.BCRYPT_SALT_LEN) -public class BCRYPT implements EncryptionMethod { +@Recommendation(Usage.RECOMMENDED) // provided the salt length is >= 8 +@HasSalt(value = SaltType.TEXT) // length depends on Settings.bCryptLog2Rounds +public class BCRYPT implements NewEncrMethod { // BCrypt parameters private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10; @@ -518,8 +519,10 @@ public class BCRYPT implements EncryptionMethod { return hashpw(password, salt); } - public String computeHash(String password, String name) { - return hashpw(password, generateSalt()); + @Override + public HashResult computeHash(String password, String name) { + String salt = generateSalt(); + return new HashResult(hashpw(password, salt), salt); } @Override @@ -527,7 +530,18 @@ public class BCRYPT implements EncryptionMethod { return checkpw(password, hash); } + @Override + public boolean comparePassword(String hash, String password, String salt, String name) { + return comparePassword(hash, password, name); + } + + @Override public String generateSalt() { - return BCRYPT.gensalt(); + return BCRYPT.gensalt(Settings.bCryptLog2Rounds); + } + + @Override + public boolean hasSeparateSalt() { + return false; } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java index a48da8f1..c5d0cd13 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java @@ -12,7 +12,7 @@ import java.security.MessageDigest; @Recommendation(Usage.DO_NOT_USE) @HasSalt(SaltType.USERNAME) -public class CRAZYCRYPT1 implements EncryptionMethod { +public class CRAZYCRYPT1 extends UsernameSaltMethod { private static final char[] CRYPTCHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; @@ -28,23 +28,11 @@ public class CRAZYCRYPT1 implements EncryptionMethod { } @Override - public String computeHash(String password, String salt, String name) { - return computeHash(password, name); - } - - public String computeHash(String password, String name) { + public HashResult computeHash(String password, String name) { final String text = "ÜÄaeut//&/=I " + password + "7421€547" + name + "__+IÄIH§%NK " + password; final MessageDigest md = HashUtils.getDigest(MessageDigestAlgorithm.SHA512); md.update(text.getBytes(charset), 0, text.length()); - return byteArrayToHexString(md.digest()); + return new HashResult(byteArrayToHexString(md.digest())); } - @Override - public boolean comparePassword(String hash, String password, String playerName) { - return hash.equals(computeHash(password, playerName)); - } - - public String generateSalt() { - return null; - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java index e375ae15..9e1e6a0f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java @@ -12,8 +12,7 @@ import java.util.Arrays; public class CryptPBKDF2 implements EncryptionMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { String result = "pbkdf2_sha256$10000$" + salt + "$"; PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 10000); PBKDF2Engine engine = new PBKDF2Engine(params); @@ -22,8 +21,7 @@ public class CryptPBKDF2 implements EncryptionMethod { } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String hash, String password, String playerName) { String[] line = hash.split("\\$"); String salt = line[2]; String derivedKey = line[3]; diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java index ab436697..dd20c1de 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java @@ -1,18 +1,11 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.security.RandomString; -import fr.xephi.authme.security.crypts.description.HasSalt; -import fr.xephi.authme.security.crypts.description.Recommendation; -import fr.xephi.authme.security.crypts.description.SaltType; -import fr.xephi.authme.security.crypts.description.Usage; import fr.xephi.authme.security.pbkdf2.PBKDF2Engine; import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters; import javax.xml.bind.DatatypeConverter; -@Recommendation(Usage.ACCEPTABLE) -@HasSalt(value = SaltType.TEXT, length = 12) -public class CryptPBKDF2Django implements EncryptionMethod { +public class CryptPBKDF2Django extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { @@ -23,12 +16,8 @@ public class CryptPBKDF2Django implements EncryptionMethod { return result + String.valueOf(DatatypeConverter.printBase64Binary(engine.deriveKey(password, 32))); } - public String computeHash(String password, String name) { - return computeHash(password, generateSalt(), null); - } - @Override - public boolean comparePassword(String hash, String password, String playerName) { + public boolean comparePassword(String hash, String password, String unusedSalt, String unusedName) { String[] line = hash.split("\\$"); String salt = line[2]; byte[] derivedKey = DatatypeConverter.parseBase64Binary(line[3]); @@ -37,8 +26,9 @@ public class CryptPBKDF2Django implements EncryptionMethod { return engine.verifyKey(password); } - public String generateSalt() { - return RandomString.generateHex(12); + @Override + public int getSaltLength() { + return 12; } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java index e1cfff9a..b7823a2b 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java @@ -1,31 +1,12 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.security.HashUtils; -import fr.xephi.authme.security.crypts.description.HasSalt; -import fr.xephi.authme.security.crypts.description.Recommendation; -import fr.xephi.authme.security.crypts.description.SaltType; -import fr.xephi.authme.security.crypts.description.Usage; +import static fr.xephi.authme.security.HashUtils.md5; -@Recommendation(Usage.DO_NOT_USE) -@HasSalt(SaltType.NONE) -public class DOUBLEMD5 implements EncryptionMethod { +public class DOUBLEMD5 extends UnsaltedMethod { @Override - public String computeHash(String password, String salt, String name) { - return computeHash(password, null); - } - - public String computeHash(String password, String name) { - return HashUtils.md5(HashUtils.md5(password)); - } - - public String generateSalt() { - return null; - } - - @Override - public boolean comparePassword(String hash, String password, String playerName) { - return hash.equals(computeHash(password, null, null)); + public String computeHash(String password) { + return md5(md5(password)); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java index d0acd622..5e0d26da 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java @@ -1,7 +1,5 @@ package fr.xephi.authme.security.crypts; -import java.security.NoSuchAlgorithmException; - /** * Public interface for custom password encryption methods. */ @@ -16,8 +14,7 @@ public interface EncryptionMethod { * * @return The hashed password */ - String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException; + String computeHash(String password, String salt, String name); /** * Check whether a given hash matches the clear-text password. @@ -30,7 +27,6 @@ public interface EncryptionMethod { * @return True if the password matches, false otherwise */ @Deprecated - boolean comparePassword(String hash, String password, String playerName) - throws NoSuchAlgorithmException; + boolean comparePassword(String hash, String password, String playerName); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/HashResult.java b/src/main/java/fr/xephi/authme/security/crypts/HashResult.java index 8ace3038..48cd7c56 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/HashResult.java +++ b/src/main/java/fr/xephi/authme/security/crypts/HashResult.java @@ -14,6 +14,10 @@ public class HashResult { this.hash = hash; this.salt = salt; } + + public HashResult(String hash) { + this(hash, null); + } public String getHash() { return hash; diff --git a/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java b/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java new file mode 100644 index 00000000..0c8fa6be --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java @@ -0,0 +1,46 @@ +package fr.xephi.authme.security.crypts; + +import fr.xephi.authme.security.RandomString; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; + +/** + * Common type for encryption methods which use a random String of hexadecimal characters + * and store the salt with the hash itself. + */ +@Recommendation(Usage.ACCEPTABLE) +@HasSalt(SaltType.TEXT) // See saltLength() for length +public abstract class HexSaltedMethod implements NewEncrMethod { + + public abstract int getSaltLength(); + + @Override + public abstract String computeHash(String password, String salt, String name); + + @Override + public HashResult computeHash(String password, String name) { + String salt = generateSalt(); + return new HashResult(computeHash(password, salt, null)); + } + + @Override + public abstract boolean comparePassword(String hash, String password, String salt, String name); + + @Override + @Deprecated + public boolean comparePassword(String hash, String password, String playerName) { + return false; + } + + @Override + public String generateSalt() { + return RandomString.generateHex(getSaltLength()); + } + + @Override + public boolean hasSeparateSalt() { + return false; + } +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java index 93abdaf1..cf401943 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java @@ -1,34 +1,52 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.AuthMe; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.RandomString; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -/** - */ -public class IPB3 implements EncryptionMethod { +import static fr.xephi.authme.security.HashUtils.md5; - private static String getMD5(String message) - throws NoSuchAlgorithmException { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.reset(); - md5.update(message.getBytes()); - byte[] digest = md5.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); +@Recommendation(Usage.DO_NOT_USE) +@HasSalt(value = SaltType.TEXT, length = 5) +public class IPB3 implements NewEncrMethod { + + @Override + public String computeHash(String password, String salt, String name) { + return md5(md5(salt) + md5(password)); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(getMD5(salt) + getMD5(password)); + public HashResult computeHash(String password, String name) { + String salt = generateSalt(); + return new HashResult(computeHash(password, salt, name), salt); } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String hash, String password, String playerName) { String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); return hash.equals(computeHash(password, salt, playerName)); } + + @Override + public boolean comparePassword(String hash, String password, String salt, String name) { + return hash.equals(computeHash(password, salt, name)); + } + + @Override + public String generateSalt() { + return RandomString.generateHex(5); + } + + @Override + public boolean hasSeparateSalt() { + return true; + } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java index 22371171..7abc0536 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java +++ b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java @@ -1,36 +1,26 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; -import fr.xephi.authme.security.RandomString; -import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; -import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; -@Recommendation(Usage.ACCEPTABLE) -@HasSalt(value = SaltType.TEXT, length = 32) -public class JOOMLA implements EncryptionMethod { +@Recommendation(Usage.RECOMMENDED) +public class JOOMLA extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { return HashUtils.md5(password + salt) + ":" + salt; } - public String computeHash(String password, String name) { - return computeHash(password, generateSalt(), null); - } - - public String generateSalt() { - return RandomString.generateHex(32); + @Override + public boolean comparePassword(String hash, String password, String unusedSalt, String unusedName) { + String[] hashParts = hash.split(":"); + return hashParts.length == 2 && hash.equals(computeHash(password, hashParts[1], null)); } @Override - public boolean comparePassword(String hash, String password, String playerName) { - String[] hashParts = hash.split(":"); - if (hashParts.length != 2) { - return false; - } - String salt = hashParts[1]; - return hash.equals(computeHash(password, salt, null)); + public int getSaltLength() { + return 32; } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5.java b/src/main/java/fr/xephi/authme/security/crypts/MD5.java index c3110c94..22164a53 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5.java @@ -6,21 +6,11 @@ import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; -@Recommendation(Usage.DO_NOT_USE) -@HasSalt(SaltType.NONE) -public class MD5 implements EncryptionMethod { +public class MD5 extends UnsaltedMethod { @Override - public String computeHash(String password, String salt, String name) { - return computeHash(password, null); - } - - public String computeHash(String password, String name) { + public String computeHash(String password) { return HashUtils.md5(password); } - @Override - public boolean comparePassword(String hash, String password, String playerName) { - return hash.equals(computeHash(password, null)); - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java index a994f7ea..8fb92e60 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java @@ -1,34 +1,23 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.security.RandomString; -import fr.xephi.authme.security.crypts.description.HasSalt; -import fr.xephi.authme.security.crypts.description.Recommendation; -import fr.xephi.authme.security.crypts.description.SaltType; -import fr.xephi.authme.security.crypts.description.Usage; - import static fr.xephi.authme.security.HashUtils.md5; -@Recommendation(Usage.ACCEPTABLE) -@HasSalt(value = SaltType.TEXT, length = 16) -public class MD5VB implements EncryptionMethod { +public class MD5VB extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { return "$MD5vb$" + salt + "$" + md5(md5(password) + salt); } - public String computeHash(String password, String name) { - return computeHash(password, generateSalt(), null); + @Override + public boolean comparePassword(String hash, String password, String salt, String name) { + String[] line = hash.split("\\$"); + return line.length == 4 && hash.equals(computeHash(password, line[2], name)); } @Override - public boolean comparePassword(String hash, String password, String playerName) { - String[] line = hash.split("\\$"); - return line.length == 4 && hash.equals(computeHash(password, line[2], "")); - } - - public String generateSalt() { - return RandomString.generateHex(16); + public int getSaltLength() { + return 16; } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java index 7232dd18..2ad60c3a 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java @@ -1,34 +1,46 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.AuthMe; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.RandomString; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -/** - */ -public class MYBB implements EncryptionMethod { +import static fr.xephi.authme.security.HashUtils.md5; - private static String getMD5(String message) - throws NoSuchAlgorithmException { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.reset(); - md5.update(message.getBytes()); - byte[] digest = md5.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); +public class MYBB implements NewEncrMethod { + + @Override + public String computeHash(String password, String salt, String name) { + return md5(md5(salt) + md5(password)); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(getMD5(salt) + getMD5(password)); + public HashResult computeHash(String password, String name) { + String salt = generateSalt(); + return new HashResult(computeHash(password, salt, name), salt); } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String hash, String password, String playerName) { String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); return hash.equals(computeHash(password, salt, playerName)); } + + @Override + public boolean comparePassword(String hash, String password, String salt, String name) { + return hash.equals(computeHash(password, salt, name)); + } + + @Override + public String generateSalt() { + return RandomString.generateHex(8); + } + + @Override + public boolean hasSeparateSalt() { + return true; + } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java index ff2e256e..cd039615 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java @@ -4,19 +4,20 @@ */ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.security.RandomString; + import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException; import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; /** * @author stefano */ -public class PHPBB implements EncryptionMethod { +public class PHPBB extends HexSaltedMethod { private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - public static String md5(String data) { + private static String md5(String data) { try { byte[] bytes = data.getBytes("ISO-8859-1"); MessageDigest md5er = MessageDigest.getInstance("MD5"); @@ -58,7 +59,7 @@ public class PHPBB implements EncryptionMethod { return buf.toString(); } - public String phpbb_hash(String password, String salt) { + private String phpbb_hash(String password, String salt) { String random_state = salt; StringBuilder random = new StringBuilder(); int count = 6; @@ -109,7 +110,7 @@ public class PHPBB implements EncryptionMethod { return output.toString(); } - String _hash_crypt_private(String password, String setting) { + private String _hash_crypt_private(String password, String setting) { String output = "*"; if (!setting.substring(0, 3).equals("$H$")) return output; @@ -130,21 +131,25 @@ public class PHPBB implements EncryptionMethod { return output; } - public boolean phpbb_check_hash(String password, String hash) { + private boolean phpbb_check_hash(String password, String hash) { if (hash.length() == 34) return _hash_crypt_private(password, hash).equals(hash); else return md5(password).equals(hash); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { return phpbb_hash(password, salt); } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String hash, String password, String salt, String name) { return phpbb_check_hash(password, hash); } + + @Override + public int getSaltLength() { + return 16; + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java index 4b8646c0..c6135a9d 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java @@ -1,6 +1,10 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.AuthMe; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.RandomString; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; @@ -10,27 +14,15 @@ import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -/** - */ -public class PHPFUSION implements EncryptionMethod { - - private static String getSHA1(String message) - throws NoSuchAlgorithmException { - MessageDigest sha1 = MessageDigest.getInstance("SHA1"); - sha1.reset(); - sha1.update(message.getBytes()); - byte[] digest = sha1.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); - } +@Recommendation(Usage.DO_NOT_USE) +public class PHPFUSION implements NewEncrMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - String digest = null; + public String computeHash(String password, String salt, String name) { String algo = "HmacSHA256"; - String keyString = getSHA1(salt); + String keyString = HashUtils.sha1(salt); try { - SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"), algo); + SecretKeySpec key = new SecretKeySpec(keyString.getBytes("UTF-8"), algo); Mac mac = Mac.getInstance(algo); mac.init(key); byte[] bytes = mac.doFinal(password.getBytes("ASCII")); @@ -42,19 +34,38 @@ public class PHPFUSION implements EncryptionMethod { } hash.append(hex); } - digest = hash.toString(); + return hash.toString(); } catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException e) { - //ingore + throw new UnsupportedOperationException("Cannot create PHPFUSION hash for " + name, e); } + } - return digest; + @Override + public HashResult computeHash(String password, String name) { + String salt = generateSalt(); + return new HashResult(computeHash(password, salt, name), salt); } @Override public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + String playerName) { String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); return hash.equals(computeHash(password, salt, "")); } + @Override + public boolean comparePassword(String hash, String password, String salt, String name) { + return hash.equals(computeHash(password, salt, name)); + } + + @Override + public String generateSalt() { + return RandomString.generateHex(12); + } + + @Override + public boolean hasSeparateSalt() { + return true; + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java b/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java index 8dec71ad..dc2cb3b4 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java @@ -1,21 +1,11 @@ package fr.xephi.authme.security.crypts; -import java.security.NoSuchAlgorithmException; - -/** - */ -public class PLAINTEXT implements EncryptionMethod { +@Deprecated +public class PLAINTEXT extends UnsaltedMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password) { return password; } - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(password); - } - } diff --git a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java index 41e658ec..db089e36 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java @@ -1,35 +1,16 @@ package fr.xephi.authme.security.crypts; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.HashUtils; -/** - */ -public class ROYALAUTH implements EncryptionMethod { +public class ROYALAUTH extends UnsaltedMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - for (int i = 0; i < 25; i++) - password = hash(password, salt); + public String computeHash(String password) { + for (int i = 0; i < 25; i++) { + // TODO ljacqu 20151228: HashUtils#sha512 gets a new message digest each time... + password = HashUtils.sha512(password); + } return password; } - public String hash(String password, String salt) - throws NoSuchAlgorithmException { - MessageDigest md = MessageDigest.getInstance("SHA-512"); - md.update(password.getBytes()); - byte byteData[] = md.digest(); - StringBuilder sb = new StringBuilder(); - for (byte aByteData : byteData) - sb.append(Integer.toString((aByteData & 0xff) + 0x100, 16).substring(1)); - return sb.toString(); - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equalsIgnoreCase(computeHash(password, "", "")); - } - } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java index 60664214..331c33fa 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java @@ -1,6 +1,7 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.AuthMe; +import fr.xephi.authme.security.HashUtils; import fr.xephi.authme.security.RandomString; import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; @@ -12,50 +13,15 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import static fr.xephi.authme.security.HashUtils.md5; + @Recommendation(Usage.ACCEPTABLE) // presuming that length is something sensible (>= 8) @HasSalt(value = SaltType.TEXT) // length defined by Settings.saltLength -public class SALTED2MD5 implements NewEncrMethod { - - private static String getMD5(String message) - throws NoSuchAlgorithmException { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.reset(); - md5.update(message.getBytes()); - byte[] digest = md5.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); - } +public class SALTED2MD5 extends SeparateSaltMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(getMD5(password) + salt); - } - - @Override - public HashResult computeHash(String password, String name) { - try { - String salt = generateSalt(); - return new HashResult(computeHash(password, salt, name), salt); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); // TODO #358: Remove try-catch clause - } - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(getMD5(getMD5(password) + salt)); - } - - @Override - public boolean comparePassword(String hash, String password, String salt, String name) { - try { - return hash.equals(computeHash(password, salt, name)); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - // TODO #358: Remove try-catch - } + public String computeHash(String password, String salt, String name) { + return md5(md5(password) + salt); } @Override @@ -63,9 +29,4 @@ public class SALTED2MD5 implements NewEncrMethod { return RandomString.generateHex(Settings.saltLength); } - @Override - public boolean hasSeparateSalt() { - return true; - } - } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java index e336a13f..d9952ee4 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java @@ -1,34 +1,20 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.AuthMe; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.RandomString; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +@Recommendation(Usage.RECOMMENDED) +public class SALTEDSHA512 extends SeparateSaltMethod { -/** - */ -public class SALTEDSHA512 implements EncryptionMethod { - - private static String getSHA512(String message) - throws NoSuchAlgorithmException { - MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); - sha512.reset(); - sha512.update(message.getBytes()); - byte[] digest = sha512.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return HashUtils.sha512(password + salt); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getSHA512(password + salt); - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(computeHash(password, salt, "")); + public String generateSalt() { + return RandomString.generateHex(32); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java index 2803b1c2..080910ec 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java @@ -1,31 +1,12 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; -import fr.xephi.authme.security.crypts.description.HasSalt; -import fr.xephi.authme.security.crypts.description.Recommendation; -import fr.xephi.authme.security.crypts.description.SaltType; -import fr.xephi.authme.security.crypts.description.Usage; -@Recommendation(Usage.DO_NOT_USE) -@HasSalt(SaltType.NONE) -public class SHA1 implements EncryptionMethod { +public class SHA1 extends UnsaltedMethod { @Override - public String computeHash(String password, String salt, String name) { - return computeHash(password, null); - } - - public String computeHash(String password, String name) { + public String computeHash(String password) { return HashUtils.sha1(password); } - @Override - public boolean comparePassword(String hash, String password, String playerName) { - return hash.equals(computeHash(password, null)); - } - - public String generateSalt() { - return null; - } - } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java index 5024072d..cc28507c 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java @@ -1,34 +1,27 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.security.RandomString; -import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; -import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; import static fr.xephi.authme.security.HashUtils.sha256; @Recommendation(Usage.RECOMMENDED) -@HasSalt(value = SaltType.TEXT, length = 16) -public class SHA256 implements EncryptionMethod { +public class SHA256 extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { return "$SHA$" + salt + "$" + sha256(sha256(password) + salt); } - public String computeHash(String password, String name) { - return computeHash(password, generateSalt(), name); - } - @Override - public boolean comparePassword(String hash, String password, String playerName) { + public boolean comparePassword(String hash, String password, String salt, String playerName) { String[] line = hash.split("\\$"); return line.length == 4 && hash.equals(computeHash(password, line[2], "")); } - public String generateSalt() { - return RandomString.generateHex(16); + @Override + public int getSaltLength() { + return 16; } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java index 10843205..81f1e026 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java @@ -1,30 +1,12 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; -import fr.xephi.authme.security.crypts.description.HasSalt; -import fr.xephi.authme.security.crypts.description.Recommendation; -import fr.xephi.authme.security.crypts.description.SaltType; -import fr.xephi.authme.security.crypts.description.Usage; -@Recommendation(Usage.DO_NOT_USE) -@HasSalt(SaltType.NONE) -public class SHA512 implements EncryptionMethod { +public class SHA512 extends UnsaltedMethod { @Override - public String computeHash(String password, String salt, String name) { - return computeHash(password, name); - } - - public String computeHash(String password, String name) { + public String computeHash(String password) { return HashUtils.sha512(password); } - @Override - public boolean comparePassword(String hash, String password, String playerName) { - return hash.equals(computeHash(password, "", "")); - } - - public String generateSalt() { - return null; - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SMF.java b/src/main/java/fr/xephi/authme/security/crypts/SMF.java index 0a2346eb..10361811 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SMF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SMF.java @@ -1,30 +1,11 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; -import fr.xephi.authme.security.crypts.description.HasSalt; -import fr.xephi.authme.security.crypts.description.Recommendation; -import fr.xephi.authme.security.crypts.description.SaltType; -import fr.xephi.authme.security.crypts.description.Usage; -@Recommendation(Usage.DO_NOT_USE) -@HasSalt(SaltType.USERNAME) -public class SMF implements EncryptionMethod { +public class SMF extends UsernameSaltMethod { - @Override - public String computeHash(String password, String salt, String name) { - return computeHash(password, name); + public HashResult computeHash(String password, String name) { + return new HashResult(HashUtils.sha1(name.toLowerCase() + password)); } - public String computeHash(String password, String name) { - return HashUtils.sha1(name.toLowerCase() + password); - } - - @Override - public boolean comparePassword(String hash, String password, String playerName) { - return hash.equals(computeHash(password, playerName)); - } - - public String generateSalt() { - return null; - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java new file mode 100644 index 00000000..ad2db489 --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java @@ -0,0 +1,35 @@ +package fr.xephi.authme.security.crypts; + +/** + * Common supertype for encryption methods which store their salt separately from the hash. + */ +public abstract class SeparateSaltMethod implements NewEncrMethod { + + @Override + public abstract String computeHash(String password, String salt, String name); + + @Override + public abstract String generateSalt(); + + @Override + public HashResult computeHash(String password, String name) { + String salt = generateSalt(); + return new HashResult(computeHash(password, salt, name), salt); + } + + @Override + public boolean comparePassword(String hash, String password, String salt, String name) { + return hash.equals(computeHash(password, salt, null)); + } + + @Override + public boolean hasSeparateSalt() { + return true; + } + + @Override + @Deprecated + public boolean comparePassword(String hash, String password, String playerName) { + return false; + } +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java b/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java new file mode 100644 index 00000000..85321529 --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java @@ -0,0 +1,47 @@ +package fr.xephi.authme.security.crypts; + +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; + +/** + * Common type for encryption methods which do not use any salt whatsoever. + */ +@Recommendation(Usage.DO_NOT_USE) +@HasSalt(SaltType.NONE) +public abstract class UnsaltedMethod implements NewEncrMethod { + + public abstract String computeHash(String password); + + @Override + public HashResult computeHash(String password, String name) { + return new HashResult(computeHash(password)); + } + + @Override + public String computeHash(String password, String salt, String name) { + return computeHash(password); + } + + @Override + @Deprecated + public boolean comparePassword(String hash, String password, String playerName) { + return false; + } + + @Override + public boolean comparePassword(String hash, String password, String salt, String name) { + return hash.equals(computeHash(password)); + } + + @Override + public String generateSalt() { + return null; + } + + @Override + public boolean hasSeparateSalt() { + return false; + } +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java new file mode 100644 index 00000000..2ce8983f --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java @@ -0,0 +1,45 @@ +package fr.xephi.authme.security.crypts; + +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; + +/** + * Common supertype of encryption methods that use a player's username + * (or something based on it) as salt. + */ +@Recommendation(Usage.DO_NOT_USE) +@HasSalt(SaltType.USERNAME) +public abstract class UsernameSaltMethod implements NewEncrMethod { + + @Override + public abstract HashResult computeHash(String password, String name); + + @Override + public boolean comparePassword(String hash, String password, String salt, String name) { + return hash.equals(computeHash(password, name).getHash()); + } + + @Override + @Deprecated + public boolean comparePassword(String hash, String password, String playerName) { + return false; + } + + @Override + public String computeHash(String password, String salt, String name) { + return computeHash(password, name).getHash(); + } + + @Override + public String generateSalt() { + return null; + } + + @Override + public boolean hasSeparateSalt() { + return false; + } + +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java b/src/main/java/fr/xephi/authme/security/crypts/WBB3.java index a3fe0d5e..6cc12300 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WBB3.java @@ -1,34 +1,19 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.AuthMe; +import fr.xephi.authme.security.RandomString; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import static fr.xephi.authme.security.HashUtils.sha1; -/** - */ -public class WBB3 implements EncryptionMethod { +public class WBB3 extends SeparateSaltMethod { - private static String getSHA1(String message) - throws NoSuchAlgorithmException { - MessageDigest sha1 = MessageDigest.getInstance("SHA1"); - sha1.reset(); - sha1.update(message.getBytes()); - byte[] digest = sha1.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return sha1(salt.concat(sha1(salt.concat(sha1(password))))); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getSHA1(salt.concat(getSHA1(salt.concat(getSHA1(password))))); + public String generateSalt() { + return RandomString.generateHex(40); } - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(computeHash(password, salt, "")); - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java index 05272886..f4d1c2d3 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java @@ -7,14 +7,12 @@ import java.security.NoSuchAlgorithmException; public class WBB4 implements EncryptionMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { return BCRYPT.getDoubleHash(password, salt); } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String hash, String password, String playerName) { return BCRYPT.checkpw(password, hash, 2); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java index 74229adb..522fb45b 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java @@ -59,16 +59,9 @@ package fr.xephi.authme.security.crypts; * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import fr.xephi.authme.security.crypts.description.HasSalt; -import fr.xephi.authme.security.crypts.description.Recommendation; -import fr.xephi.authme.security.crypts.description.SaltType; -import fr.xephi.authme.security.crypts.description.Usage; - import java.util.Arrays; -@Recommendation(Usage.DO_NOT_USE) -@HasSalt(SaltType.NONE) -public class WHIRLPOOL implements EncryptionMethod { +public class WHIRLPOOL extends UnsaltedMethod { /** * The message digest size (in bits) @@ -386,12 +379,7 @@ public class WHIRLPOOL implements EncryptionMethod { } } - @Override - public String computeHash(String password, String salt, String name) { - return computeHash(password, null); - } - - public String computeHash(String password, String name) { + public String computeHash(String password) { byte[] digest = new byte[DIGESTBYTES]; NESSIEinit(); NESSIEadd(password); @@ -399,9 +387,4 @@ public class WHIRLPOOL implements EncryptionMethod { return display(digest); } - @Override - public boolean comparePassword(String hash, String password, String playerName) { - return hash.equals(computeHash(password, null, null)); - } - } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java index f9a17632..7d06aac6 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java @@ -13,7 +13,9 @@ import java.util.Arrays; @Recommendation(Usage.ACCEPTABLE) @HasSalt(value = SaltType.TEXT, length = 9) -public class WORDPRESS implements EncryptionMethod { +// Note ljacqu 20151228: Wordpress is actually a salted algorithm but salt generation is handled internally +// and isn't exposed to the outside, so we treat it as an unsalted implementation +public class WORDPRESS extends UnsaltedMethod { private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private final SecureRandom randomGen = new SecureRandom(); @@ -107,25 +109,16 @@ public class WORDPRESS implements EncryptionMethod { } @Override - public String computeHash(String password, String salt, String name) { + public String computeHash(String password) { byte random[] = new byte[6]; randomGen.nextBytes(random); return crypt(password, gensaltPrivate(stringToUtf8(new String(random)))); } - public String computeHash(String password, String name) { - return computeHash(password, null, null); - } - @Override - public boolean comparePassword(String hash, String password, String playerName) { + public boolean comparePassword(String hash, String password, String salt, String name) { String comparedHash = crypt(password, hash); return comparedHash.equals(hash); } - public String generateSalt() { - // This hash uses a salt, but it is not exposed to the outside - return null; - } - } diff --git a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java index b3f07752..f0ce068d 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java @@ -1,16 +1,12 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.security.RandomString; -import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; -import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; @Recommendation(Usage.RECOMMENDED) -@HasSalt(value = SaltType.TEXT, length = 12) -public class XAUTH implements EncryptionMethod { +public class XAUTH extends HexSaltedMethod { - public static String getWhirlpool(String message) { + private static String getWhirlpool(String message) { WHIRLPOOL w = new WHIRLPOOL(); byte[] digest = new byte[WHIRLPOOL.DIGESTBYTES]; w.NESSIEinit(); @@ -26,19 +22,16 @@ public class XAUTH implements EncryptionMethod { return hash.substring(0, saltPos) + salt + hash.substring(saltPos); } - public String computeHash(String password, String name) { - return computeHash(password, generateSalt(), null); + @Override + public boolean comparePassword(String hash, String password, String salt, String playerName) { + int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length()); + String saltFromHash = hash.substring(saltPos, saltPos + 12); + return hash.equals(computeHash(password, saltFromHash, null)); } @Override - public boolean comparePassword(String hash, String password, String playerName) { - int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length()); - String salt = hash.substring(saltPos, saltPos + 12); - return hash.equals(computeHash(password, salt, "")); - } - - public String generateSalt() { - return RandomString.generateHex(12); + public int getSaltLength() { + return 12; } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/XF.java b/src/main/java/fr/xephi/authme/security/crypts/XF.java index 00a23c8e..7c9d8a70 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XF.java @@ -11,23 +11,37 @@ import java.util.regex.Pattern; /** */ -public class XF implements EncryptionMethod { +public class XF implements NewEncrMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { return getSha256(getSha256(password) + regmatch("\"salt\";.:..:\"(.*)\";.:.:\"hashFunc\"", salt)); } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public HashResult computeHash(String password, String name) { + String salt = generateSalt(); + return new HashResult(computeHash(password, salt, null), salt); + } + + @Override + public boolean comparePassword(String hash, String password, String playerName) { String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); return hash.equals(regmatch("\"hash\";.:..:\"(.*)\";.:.:\"salt\"", salt)); } - private String getSha256(String password) throws NoSuchAlgorithmException { - MessageDigest md = MessageDigest.getInstance("SHA-256"); + public boolean comparePassword(String hash, String password, String salt, String name) { + return hash.equals(regmatch("\"hash\";.:..:\"(.*)\";.:.:\"salt\"", salt)); + } + + private String getSha256(String password) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + // TODO #358: Handle exception properly + throw new RuntimeException(e); + } md.update(password.getBytes()); byte byteData[] = md.digest(); StringBuilder sb = new StringBuilder(); @@ -45,6 +59,17 @@ public class XF implements EncryptionMethod { return hexString.toString(); } + @Override + public String generateSalt() { + // TODO #369: Find out what kind of salt format XF uses + return ""; + } + + @Override + public boolean hasSeparateSalt() { + return true; + } + private String regmatch(String pattern, String line) { List allMatches = new ArrayList<>(); Matcher m = Pattern.compile(pattern).matcher(line); diff --git a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java index 6e812886..a169f622 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java @@ -1,9 +1,7 @@ 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; @@ -11,6 +9,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * Test for implementations of {@link EncryptionMethod}. @@ -98,15 +97,13 @@ public abstract class AbstractEncryptionMethodTest { } @Test - public void testPasswordEquality() throws NoSuchAlgorithmException { - // TODO #358: Remove "throws NoSuchAlgorithmException" on method declaration + public void testPasswordEquality() { // TODO #358: Remove instanceof and use this code always if (method instanceof NewEncrMethod) { NewEncrMethod method1 = (NewEncrMethod) method; for (String password : INTERNAL_PASSWORDS) { - HashResult result = method1.computeHash(password, USERNAME); - final String hash = result.getHash(); - final String salt = result.getSalt(); + final String salt = method1.generateSalt(); + final String hash = method1.computeHash(password, salt, USERNAME); // Check that the computeHash(password, salt, name) method has the same output for the returned salt assertThat(hash, equalTo(method1.computeHash(password, salt, USERNAME))); @@ -125,23 +122,7 @@ public abstract class AbstractEncryptionMethodTest { return; } - for (String password : INTERNAL_PASSWORDS) { - try { - String hash = method.computeHash(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); - } - } + fail("No longer supporting old EncryptionMethod implementations"); } private boolean doesGivenHashMatch(String password, EncryptionMethod method) { @@ -154,11 +135,8 @@ public abstract class AbstractEncryptionMethodTest { } - try { - return method.comparePassword(hashes.get(password), password, USERNAME); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("EncryptionMethod '" + method + "' threw exception", e); - } + // TODO #358: Remove line below + return method.comparePassword(hashes.get(password), password, USERNAME); } // @org.junit.Test public void a() { AbstractEncryptionMethodTest.generateTest(); } @@ -170,45 +148,29 @@ public abstract class AbstractEncryptionMethodTest { System.out.println("\n\tpublic " + className + "Test() {"); System.out.println("\t\tsuper(new " + className + "(),"); + NewEncrMethod method1 = null; + if (method instanceof NewEncrMethod) { + method1 = (NewEncrMethod) method; + if (!method1.hasSeparateSalt()) method1 = null; + } + + String delim = ", "; for (String password : GIVEN_PASSWORDS) { if (password.equals(GIVEN_PASSWORDS[GIVEN_PASSWORDS.length - 1])) { delim = "); "; } - try { - System.out.println("\t\t\"" + method.computeHash(password, getSalt(method), USERNAME) + if (method1 != null) { + HashResult hashResult = method1.computeHash(password, USERNAME); + System.out.println(String.format("\t\tnew HashResult(\"%s\", \"%s\")%s// %s", + hashResult.getHash(), hashResult.getSalt(), delim, password)); + } else { + System.out.println("\t\t\"" + method.computeHash(password, null, USERNAME) + "\"" + delim + "// " + password); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("Could not generate hash", e); } } System.out.println("\t}"); System.out.println("\n}"); } - // TODO #358: Remove this method and use the new salt method on the interface - private static String getSalt(EncryptionMethod method) { - if (method instanceof BCRYPT) { - return BCRYPT.gensalt(); - } else if (method instanceof MD5 || method instanceof WORDPRESS || method instanceof SMF - || method instanceof SHA512 || method instanceof SHA1 || method instanceof ROYALAUTH - || method instanceof DOUBLEMD5 || method instanceof CRAZYCRYPT1) { - return ""; - } else if (method instanceof JOOMLA || method instanceof SALTEDSHA512) { - return PasswordSecurity.createSalt(32); - } else if (method instanceof SHA256 || method instanceof PHPBB || method instanceof WHIRLPOOL - || method instanceof MD5VB || method instanceof BCRYPT2Y) { - return PasswordSecurity.createSalt(16); - } else if (method instanceof WBB3) { - return PasswordSecurity.createSalt(40); - } else if (method instanceof XAUTH || method instanceof CryptPBKDF2Django - || method instanceof CryptPBKDF2) { - return PasswordSecurity.createSalt(12); - } else if (method instanceof WBB4) { - return BCRYPT.gensalt(8); - } - System.out.println("Note: Cannot generate salt for unknown encryption method '" + method + "'"); - return ""; - } - } diff --git a/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java b/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java index 2d133d40..65ea3b68 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java @@ -1,10 +1,20 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.util.WrapperMock; +import org.junit.Before; + /** * Test for {@link BCRYPT}. */ public class BcryptTest extends AbstractEncryptionMethodTest { + @Before + public void setUpSettings() { + WrapperMock.createInstance(); + Settings.bCryptLog2Rounds = 8; + } + public BcryptTest() { super(new BCRYPT(), "$2a$10$6iATmYgwJVc3YONhVcZFve3Cfb5GnwvKhJ20r.hMjmcNkIT9.Uh9K", // password diff --git a/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java b/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java new file mode 100644 index 00000000..53d3e4e1 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java @@ -0,0 +1,16 @@ +package fr.xephi.authme.security.crypts; + +/** + * Test for {@link IPB3}. + */ +public class IPB3Test extends AbstractEncryptionMethodTest { + + public IPB3Test() { + super(new IPB3(), + new HashResult("f8ecea1ce42b5babef369ff7692dbe3f", "1715b"), //password + new HashResult("40a93731a931352e0619cdf09b975040", "ba91c"), //PassWord1 + new HashResult("a77ca982373946d5800430bd2947ba11", "a7725"), //&^%te$t?Pw@_ + new HashResult("383d7b9e2b707d6e894ec7b30e3032c3", "fa9fd")); //âË_3(íù* + } + +} diff --git a/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java b/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java new file mode 100644 index 00000000..2d2e965a --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java @@ -0,0 +1,16 @@ +package fr.xephi.authme.security.crypts; + +/** + * Test for {@link MYBB}. + */ +public class MYBBTest extends AbstractEncryptionMethodTest { + + public MYBBTest() { + super(new MYBB(), + new HashResult("57c7a16d860833db5030738f5a465d2b", "acdc14e6"), //password + new HashResult("08fbdf721f2c42d9780b7d66df0ba830", "792fd7fb"), //PassWord1 + new HashResult("d602f38fb59ad9e185d5604f5d4ddb36", "4b5534a4"), //&^%te$t?Pw@_ + new HashResult("b3c39410d0ab8ae2a65c257820797fad", "e5a6cb14")); //âË_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 index 761fe7bb..0c9d67cf 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java @@ -7,11 +7,10 @@ public class Md5Test extends AbstractEncryptionMethodTest { public Md5Test() { super(new MD5(), - "5f4dcc3b5aa765d61d8327deb882cf99", // password - "f2126d405f46ed603ff5b2950f062c96", // PassWord1 - "0833dcd2bc741f90c46bbac5498fd08f", // &^%te$t?Pw@_ - "d1accd961cb7b688c87278191c1dfed3" // âË_3(íù* - ); + "5f4dcc3b5aa765d61d8327deb882cf99", // password + "f2126d405f46ed603ff5b2950f062c96", // PassWord1 + "0833dcd2bc741f90c46bbac5498fd08f", // &^%te$t?Pw@_ + "d1accd961cb7b688c87278191c1dfed3"); // âË_3(íù* } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java b/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java new file mode 100644 index 00000000..828f6d48 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java @@ -0,0 +1,20 @@ +package fr.xephi.authme.security.crypts; + +import org.junit.Ignore; + +/** + * Test for {@link PHPFUSION}. + */ +@Ignore +// TODO #364: Need to skip lowercase/uppercase password test for the non-ASCII one +public class PHPFUSIONTest extends AbstractEncryptionMethodTest { + + public PHPFUSIONTest() { + super(new PHPFUSION(), + new HashResult("f7a606c4eb3fcfbc382906476e05b06f21234a77d1a4eacc0f93f503deb69e70", "6cd1c97c55cb"), // password + new HashResult("8a9b7bb706a3347e5f684a7cb905bfb26b9a0d099358064139ab3ed1a66aeb2b", "d6012370b73f"), // PassWord1 + new HashResult("43f2f23f44c8f89e2dbf06050bc8c77dbcdf71a7b5d28c87ec657d474e63d62d", "f75400a209a4"), // &^%te$t?Pw@_ + new HashResult("4e7f4eb7e3653d7460f1cf590def4153c6fcdf8b8e16fb95538fdf9e54a95245", "d552e0f5b23a")); // âË_3(íù* + } + +} diff --git a/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java b/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java index 851d8b85..f88984e9 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java @@ -1,20 +1,16 @@ package fr.xephi.authme.security.crypts; -import org.junit.Ignore; - /** * Test for {@link SALTEDSHA512}. */ -@Ignore -// TODO ljacqu 20151220: Currently cannot test because of closely coupled database call inside of class public class SALTEDSHA512Test extends AbstractEncryptionMethodTest { public SALTEDSHA512Test() { super(new SALTEDSHA512(), - "c8efe95e1ab02d9a0e7c7d11d4ac3cc068a8405b5810aac3a1b8b01927ab059563438131dc995156739daf74db40ffdc79b78f6aec9b2a468fe106b88c66c204", // password - "74c61af1bcbb3293cdc0959c7323d50be28c167eddc7a1b7eb029e38263c2cfb6eb090f41370a65249752aa316fa851091c2bd8420302e87d383529beea735b4", // PassWord1 - "08eefcca4a17876441ebe61a02e8bc62cab7502dd87f8ec3b7f82edb2adace791b8dad31e74c5513cf99be502b732f5c5efffb239f4590d5c600d066a7037908", // &^%te$t?Pw@_ - "a122490c4c7c18ad665b5ac9617c948741468a787a2ba42c6fd2530ea1d7874681b8575ee9a8907c42ff65dac69e4ada2852789759c17d51865ca915b259a65a"); // âË_3(íù* + new HashResult("dea7a37cecf5384ae8e347fd1411efb51364b6ba1b328695de3b354612c1d7010807e8b7051c40f740e498490e1f133e2c2408327d13fbdd68e1b1f6d548e624", "29f8a3c52147f987fee7ba3e0fb311bd"), // password + new HashResult("7c06225aac574d2dc7c81a2ed306637adf025715f52083e05bdab014faaa234e24a97d0e69ea0108dfa77cc9228e58be319ee677e679b5d1ad168d40e50a42f6", "8ea37b85d020b98f60c0fe9b8ec9296c"), // PassWord1 + new HashResult("55711adbe03c9616f3505f0d57077fdd528c32243eb6f9840c1a6ff9e553940d6b89790750ebd52ebda63ca793fbe9980d54057af40836820c648750fe22d49c", "9f58079631ef21d32b4710694f1f461b"), // &^%te$t?Pw@_ + new HashResult("29dc5be8702975ea4563ed3de5b145e2d2f1c37ae661bbe0d3e94d964402cf09d539d65f3b90ff6921ea3d40727f76fb38fb34d1e5c2d62238c4e0203efc372f", "048bb76168265d906f1fd1f81d0616a9")); // âË_3(íù* } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java b/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java index 99474f76..3851cb4e 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java @@ -1,20 +1,16 @@ package fr.xephi.authme.security.crypts; -import org.junit.Ignore; - /** * Test for {@link WBB3}. */ -@Ignore -// TODO #364 ljacqu 20151220: Unignore test after fixing closely coupled DB dependency public class WBB3Test extends AbstractEncryptionMethodTest { public WBB3Test() { super(new WBB3(), - "ca426c4d20a82cd24c7bb07d94d69f3757e3d07d", // password - "72d59d27674a3cace2600ff152ba8b46274e27e9", // PassWord1 - "23daf26602e52591156968a14c2a6592b5be4743", // &^%te$t?Pw@_ - "d3908efe4a15314066391dd8572883c70b16fd8a"); // âË_3(íù* + new HashResult("8df818ef7d56075ab2744f74b98ad68a375ccac4", "b7415b355492ea60314f259a35733a3092c03e3f"), // password + new HashResult("106da5cf5df92cb845e12cf62cbdb5235b6dc693", "6110f19b2b52910dccf592a19c59126873f42e69"), // PassWord1 + new HashResult("940a9fb7acec0178c6691e8b3c14bd7d789078b1", "f9dd501ff3d1bf74904f9e89649e378429af56e7"), // &^%te$t?Pw@_ + new HashResult("0fa12e8d96c9e95f73aa91f3b76f8cdc815ec8a5", "736be8669f6159ddb2d5b47a3e6428cdb8b324de")); // âË_3(íù* } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java b/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java index cf762412..07165934 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java @@ -1,8 +1,12 @@ package fr.xephi.authme.security.crypts; +import org.junit.Ignore; + /** * Test for {@link WORDPRESS}. */ +@Ignore +// TODO #364: Need to skip an assertion due to the "internal salt" of Wordpress public class WORDPRESSTest extends AbstractEncryptionMethodTest { public WORDPRESSTest() { diff --git a/src/test/java/fr/xephi/authme/security/crypts/XFTest.java b/src/test/java/fr/xephi/authme/security/crypts/XFTest.java new file mode 100644 index 00000000..e5231069 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/XFTest.java @@ -0,0 +1,17 @@ +package fr.xephi.authme.security.crypts; + +import org.junit.Ignore; +import org.junit.Test; + +/** + * Test for {@link XF}. + */ +@Ignore +// TODO #369: XF needs to generate a salt it is expecting +public class XFTest { + + @Test + public void shouldComputeHash() { + System.out.println(new XF().computeHash("Test", "name")); + } +} From 73bc6e286ab3809abfdc0f09dd5d25b1852d7117 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 28 Dec 2015 21:03:33 +0100 Subject: [PATCH 07/18] #369 Fix bcrypt 2y implementation - Change salt length to 22: it was once changed on accident during some other commit --- .../authme/security/crypts/BCRYPT2Y.java | 11 +++++++--- .../crypts/AbstractEncryptionMethodTest.java | 12 +++++++---- .../authme/security/crypts/BCRYPT2YTest.java | 21 +++++++------------ 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java index 90901cf9..1b372c4c 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java @@ -4,19 +4,24 @@ import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; @Recommendation(Usage.DOES_NOT_WORK) -public class BCRYPT2Y implements EncryptionMethod { +public class BCRYPT2Y extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { if (salt.length() == 22) salt = "$2y$10$" + salt; - return (BCRYPT.hashpw(password, salt)); + return BCRYPT.hashpw(password, salt); } @Override - public boolean comparePassword(String hash, String password, String playerName) { + public boolean comparePassword(String hash, String password, String salt, String playerName) { String ok = hash.substring(0, 29); return ok.length() == 29 && hash.equals(computeHash(password, ok, playerName)); } + @Override + public int getSaltLength() { + return 22; + } + } diff --git a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java index a169f622..774cb4d9 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java @@ -151,7 +151,6 @@ public abstract class AbstractEncryptionMethodTest { NewEncrMethod method1 = null; if (method instanceof NewEncrMethod) { method1 = (NewEncrMethod) method; - if (!method1.hasSeparateSalt()) method1 = null; } @@ -161,9 +160,14 @@ public abstract class AbstractEncryptionMethodTest { delim = "); "; } if (method1 != null) { - HashResult hashResult = method1.computeHash(password, USERNAME); - System.out.println(String.format("\t\tnew HashResult(\"%s\", \"%s\")%s// %s", - hashResult.getHash(), hashResult.getSalt(), delim, password)); + if (method1.hasSeparateSalt()) { + HashResult hashResult = method1.computeHash(password, USERNAME); + System.out.println(String.format("\t\tnew HashResult(\"%s\", \"%s\")%s// %s", + hashResult.getHash(), hashResult.getSalt(), delim, password)); + } else { + System.out.println("\t\t\"" + method1.computeHash(password, USERNAME).getHash() + + "\"" + delim + "// " + password); + } } else { System.out.println("\t\t\"" + method.computeHash(password, null, USERNAME) + "\"" + delim + "// " + password); diff --git a/src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java b/src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java index d49c1d3c..ac34dea0 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java @@ -1,23 +1,16 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.security.PasswordSecurity; -import org.junit.Ignore; -import org.junit.Test; - -import java.security.NoSuchAlgorithmException; - /** * Test for {@link BCRYPT2Y}. */ -@Ignore -// TODO #369: Fix hash & add standard test -public class BCRYPT2YTest { +public class BCRYPT2YTest extends AbstractEncryptionMethodTest { - @Test - public void shouldCreateHash() throws NoSuchAlgorithmException { - String salt = PasswordSecurity.createSalt(16); // As defined in PasswordSecurity - EncryptionMethod method = new BCRYPT2Y(); - System.out.println(method.computeHash("password", salt, "testPlayer")); + public BCRYPT2YTest() { + super(new BCRYPT2Y(), + "$2y$10$da641e404b982edf1c7c0uTU9BcKzfA2vWKV05q6r.dCvm/93wqVK", // password + "$2y$10$e52c48a76f5b86f5da899uiK/HYocyPsfQXESNbP278rIz08LKEP2", // PassWord1 + "$2y$10$be6f11548dc5fb4088410ONdC0dXnJ04y1RHcJh5fVF3XK5d.qgqK", // &^%te$t?Pw@_ + "$2y$10$a8097db1fa4423b93f1b2eF6rMAGFkSX178fpROf/OvCFtrDebp6K"); // âË_3(íù* } } From 9b73475b9ae98e4cfd8ea3e38d26690cfe44b3ad Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 28 Dec 2015 21:27:47 +0100 Subject: [PATCH 08/18] Minor - clean up bcrypt 2y implementation - Update Recommendation annotation - Add proper length check to hash - Remove check that is always true --- .../fr/xephi/authme/security/crypts/BCRYPT2Y.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java index 1b372c4c..664b3c93 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java @@ -3,20 +3,25 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; -@Recommendation(Usage.DOES_NOT_WORK) +@Recommendation(Usage.RECOMMENDED) public class BCRYPT2Y extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { - if (salt.length() == 22) + if (salt.length() == 22) { salt = "$2y$10$" + salt; + } return BCRYPT.hashpw(password, salt); } @Override - public boolean comparePassword(String hash, String password, String salt, String playerName) { - String ok = hash.substring(0, 29); - return ok.length() == 29 && hash.equals(computeHash(password, ok, playerName)); + public boolean comparePassword(String hash, String password, String unusedSalt, String unusedName) { + if (hash.length() != 60) { + return false; + } + // The salt is the first 29 characters of the hash + String salt = hash.substring(0, 29); + return hash.equals(computeHash(password, salt, null)); } @Override From 47f4275225fee78c2c8eb81e2753a7108207f1b7 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 28 Dec 2015 22:00:43 +0100 Subject: [PATCH 09/18] #358 Update EncryptionMethod to new interface - Add new methods to the EncryptionMethod interface - Delete temporary interface (NewEncrMethod) - Remove temporary checks and casts to NewEncrMethod --- .../authme/security/PasswordSecurity.java | 51 +++++++------ .../xephi/authme/security/crypts/BCRYPT.java | 9 +-- .../authme/security/crypts/CryptPBKDF2.java | 10 ++- .../security/crypts/EncryptionMethod.java | 46 +++++++++--- .../authme/security/crypts/HashResult.java | 24 ++++++- .../security/crypts/HexSaltedMethod.java | 8 +-- .../fr/xephi/authme/security/crypts/IPB3.java | 14 +--- .../fr/xephi/authme/security/crypts/MYBB.java | 14 +--- .../authme/security/crypts/NewEncrMethod.java | 17 ----- .../authme/security/crypts/PHPFUSION.java | 12 +--- .../security/crypts/SeparateSaltMethod.java | 7 +- .../security/crypts/UnsaltedMethod.java | 8 +-- .../security/crypts/UsernameSaltMethod.java | 8 +-- .../fr/xephi/authme/security/crypts/WBB4.java | 25 +++++-- .../fr/xephi/authme/security/crypts/XF.java | 10 +-- .../crypts/AbstractEncryptionMethodTest.java | 72 ++++++------------- 16 files changed, 147 insertions(+), 188 deletions(-) delete mode 100644 src/main/java/fr/xephi/authme/security/crypts/NewEncrMethod.java diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index 8fd8365b..b733d55a 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -7,7 +7,6 @@ import fr.xephi.authme.events.PasswordEncryptionEvent; import fr.xephi.authme.security.crypts.BCRYPT; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.HashResult; -import fr.xephi.authme.security.crypts.NewEncrMethod; import fr.xephi.authme.settings.Settings; import org.bukkit.Bukkit; @@ -145,7 +144,18 @@ public class PasswordSecurity { if (method == null) throw new NoSuchAlgorithmException("Unknown hash algorithm"); - if (method.comparePassword(hash, password, playerName)) + String salt = null; + if (method.hasSeparateSalt()) { + PlayerAuth auth = AuthMe.getInstance().getDataSource().getAuth(playerName); + if (auth == null) { + // User is not in data source, so the result will invariably be wrong because an encryption + // method with hasSeparateSalt() == true NEEDS the salt to evaluate the password + return false; + } + salt = auth.getSalt(); + } + + if (method.comparePassword(hash, password, salt, playerName)) return true; if (Settings.supportOldPassword) { @@ -159,32 +169,21 @@ public class PasswordSecurity { } public HashResult computeHash(HashAlgorithm algorithm, String password, String playerName) { - EncryptionMethod method1 = initializeEncryptionMethod(algorithm, playerName); - // TODO #358: Remove this check: - NewEncrMethod method; - if (method1 instanceof NewEncrMethod) { - method = (NewEncrMethod) method1; - } else { - throw new RuntimeException("TODO #358: Class not yet extended with NewEncrMethod methods"); - } - + EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName); return method.computeHash(password, playerName); } public boolean comparePassword(HashAlgorithm algorithm, String hash, String password, String playerName) { - EncryptionMethod method1 = initializeEncryptionMethod(algorithm, playerName); - // TODO #358: Remove this check: - NewEncrMethod method; - if (method1 instanceof NewEncrMethod) { - method = (NewEncrMethod) method1; - } else { - throw new RuntimeException("TODO #358: Class not yet extended with NewEncrMethod methods"); - } - + EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName); String salt = null; if (method.hasSeparateSalt()) { PlayerAuth auth = dataSource.getAuth(playerName); - salt = (auth != null) ? auth.getSalt() : null; + if (auth == null) { + // User is not in data source, so the result will invariably be wrong because an encryption + // method with hasSeparateSalt() == true NEEDS the salt to evaluate the password + return false; + } + salt = auth.getSalt(); } return method.comparePassword(hash, password, salt, playerName); // TODO #358: Add logic for Settings.supportOldPassword @@ -209,11 +208,19 @@ public class PasswordSecurity { @Deprecated private static boolean compareWithAllEncryptionMethod(String password, String hash, String playerName) { + String salt; + PlayerAuth auth = AuthMe.getInstance().getDataSource().getAuth(playerName); + if (auth == null) { + salt = null; + } else { + salt = auth.getSalt(); + } + for (HashAlgorithm algo : HashAlgorithm.values()) { if (algo != HashAlgorithm.CUSTOM) { try { EncryptionMethod method = algo.getClazz().newInstance(); - if (method.comparePassword(hash, password, playerName)) { + if (method.comparePassword(hash, password, salt, playerName)) { PlayerAuth nAuth = AuthMe.getInstance().database.getAuth(playerName); if (nAuth != null) { nAuth.setHash(getHash(Settings.getPasswordHash, password, playerName)); 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 29ed5c11..b85a83df 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java @@ -66,7 +66,7 @@ import java.security.SecureRandom; */ @Recommendation(Usage.RECOMMENDED) // provided the salt length is >= 8 @HasSalt(value = SaltType.TEXT) // length depends on Settings.bCryptLog2Rounds -public class BCRYPT implements NewEncrMethod { +public class BCRYPT implements EncryptionMethod { // BCrypt parameters private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10; @@ -525,14 +525,9 @@ public class BCRYPT implements NewEncrMethod { return new HashResult(hashpw(password, salt), salt); } - @Override - public boolean comparePassword(String hash, String password, String playerName) { - return checkpw(password, hash); - } - @Override public boolean comparePassword(String hash, String password, String salt, String name) { - return comparePassword(hash, password, name); + return checkpw(password, hash); } @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java index 9e1e6a0f..2742d0b2 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java @@ -5,11 +5,10 @@ import fr.xephi.authme.security.crypts.description.Usage; import fr.xephi.authme.security.pbkdf2.PBKDF2Engine; import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters; -import java.security.NoSuchAlgorithmException; import java.util.Arrays; @Recommendation(Usage.DOES_NOT_WORK) -public class CryptPBKDF2 implements EncryptionMethod { +public class CryptPBKDF2 extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { @@ -21,7 +20,7 @@ public class CryptPBKDF2 implements EncryptionMethod { } @Override - public boolean comparePassword(String hash, String password, String playerName) { + public boolean comparePassword(String hash, String password, String unusedSalt, String unusedName) { String[] line = hash.split("\\$"); String salt = line[2]; String derivedKey = line[3]; @@ -30,4 +29,9 @@ public class CryptPBKDF2 implements EncryptionMethod { return engine.verifyKey(password); } + @Override + public int getSaltLength() { + return 12; + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java index 5e0d26da..efc98b66 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java @@ -5,28 +5,56 @@ package fr.xephi.authme.security.crypts; */ public interface EncryptionMethod { + /** + * Hash the given password for the given player name. + * + * @param password The password to hash + * @param name The name of the player (sometimes required to generate a salt with) + * + * @return The hash result for the password. + * @see HashResult + */ + HashResult computeHash(String password, String name); + /** * Hash the given password with the given salt for the given player. * - * @param password The clear-text password to hash + * @param password The password to hash * @param salt The salt to add to the hash - * @param name The player's name (sometimes required for storing the salt separately in the database) + * @param name The player's name (sometimes required to generate a salt with) * * @return The hashed password + * @see #hasSeparateSalt() */ String computeHash(String password, String salt, String name); /** - * Check whether a given hash matches the clear-text password. + * Check whether the given hash matches the clear-text password. * - * @param hash The hash to verify - * @param password The clear-text password to verify the hash against - * @param playerName The player name to do the check for (sometimes required for retrieving - * the salt from the database) + * @param hash The hash to verify + * @param password The clear-text password to verify the hash against + * @param salt The salt if it is stored separately (null otherwise) + * @param name The player name to do the check for (sometimes required for generating the salt) * * @return True if the password matches, false otherwise */ - @Deprecated - boolean comparePassword(String hash, String password, String playerName); + boolean comparePassword(String hash, String password, String salt, String name); + + /** + * Generate a new salt to hash a password with. + * + * @return The generated salt, null if the method does not use a random text-based salt + */ + String generateSalt(); + + /** + * Return whether the encryption method requires the salt to be stored separately and + * passed again to {@link #comparePassword(String, String, String, String)}. Note that + * an encryption method returning {@code false} does not imply that it uses no salt; it + * may be embedded into the hash or it may use the username as salt. + * + * @return True if the salt has to be stored and retrieved separately, false otherwise + */ + boolean hasSeparateSalt(); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/HashResult.java b/src/main/java/fr/xephi/authme/security/crypts/HashResult.java index 48cd7c56..767a3a0c 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/HashResult.java +++ b/src/main/java/fr/xephi/authme/security/crypts/HashResult.java @@ -1,20 +1,38 @@ package fr.xephi.authme.security.crypts; /** - * The result of a hash computation. + * The result of a hash computation. See {@link #salt} for details. */ public class HashResult { /** The generated hash. */ private final String hash; - /** The generated salt; may be null if no salt is used or if the salt is included in the hash output. */ + /** + * The generated salt; may be null if no salt is used or if the salt is included + * in the hash output. The salt is only not null if {@link EncryptionMethod#hasSeparateSalt()} + * returns true for the associated encryption method. + *

+ * When the field is not null, it must be stored into the salt column of the data source + * and retrieved again to compare a password with the hash. + */ private final String salt; - + + /** + * Constructor. + * + * @param hash The computed hash + * @param salt The generated salt + */ public HashResult(String hash, String salt) { this.hash = hash; this.salt = salt; } + /** + * Constructor for a hash with no separate salt. + * + * @param hash The computed hash + */ public HashResult(String hash) { this(hash, null); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java b/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java index 0c8fa6be..25443420 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java @@ -12,7 +12,7 @@ import fr.xephi.authme.security.crypts.description.Usage; */ @Recommendation(Usage.ACCEPTABLE) @HasSalt(SaltType.TEXT) // See saltLength() for length -public abstract class HexSaltedMethod implements NewEncrMethod { +public abstract class HexSaltedMethod implements EncryptionMethod { public abstract int getSaltLength(); @@ -28,12 +28,6 @@ public abstract class HexSaltedMethod implements NewEncrMethod { @Override public abstract boolean comparePassword(String hash, String password, String salt, String name); - @Override - @Deprecated - public boolean comparePassword(String hash, String password, String playerName) { - return false; - } - @Override public String generateSalt() { return RandomString.generateHex(getSaltLength()); diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java index cf401943..b89c8f32 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java @@ -1,22 +1,16 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.security.HashUtils; import fr.xephi.authme.security.RandomString; import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - import static fr.xephi.authme.security.HashUtils.md5; @Recommendation(Usage.DO_NOT_USE) @HasSalt(value = SaltType.TEXT, length = 5) -public class IPB3 implements NewEncrMethod { +public class IPB3 implements EncryptionMethod { @Override public String computeHash(String password, String salt, String name) { @@ -29,12 +23,6 @@ public class IPB3 implements NewEncrMethod { return new HashResult(computeHash(password, salt, name), salt); } - @Override - public boolean comparePassword(String hash, String password, String playerName) { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(computeHash(password, salt, playerName)); - } - @Override public boolean comparePassword(String hash, String password, String salt, String name) { return hash.equals(computeHash(password, salt, name)); diff --git a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java index 2ad60c3a..fe4b287e 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java @@ -1,16 +1,10 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.security.HashUtils; import fr.xephi.authme.security.RandomString; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - import static fr.xephi.authme.security.HashUtils.md5; -public class MYBB implements NewEncrMethod { +public class MYBB implements EncryptionMethod { @Override public String computeHash(String password, String salt, String name) { @@ -23,12 +17,6 @@ public class MYBB implements NewEncrMethod { return new HashResult(computeHash(password, salt, name), salt); } - @Override - public boolean comparePassword(String hash, String password, String playerName) { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(computeHash(password, salt, playerName)); - } - @Override public boolean comparePassword(String hash, String password, String salt, String name) { return hash.equals(computeHash(password, salt, name)); diff --git a/src/main/java/fr/xephi/authme/security/crypts/NewEncrMethod.java b/src/main/java/fr/xephi/authme/security/crypts/NewEncrMethod.java deleted file mode 100644 index 1a0bde0e..00000000 --- a/src/main/java/fr/xephi/authme/security/crypts/NewEncrMethod.java +++ /dev/null @@ -1,17 +0,0 @@ -package fr.xephi.authme.security.crypts; - -/** - * Temporary interface for additional methods that will be added to {@link EncryptionMethod}. - * TODO #358: Move methods to EncryptionMethod interface and delete this. - */ -public interface NewEncrMethod extends EncryptionMethod { - - HashResult computeHash(String password, String name); - - String generateSalt(); - - boolean hasSeparateSalt(); - - boolean comparePassword(String hash, String password, String salt, String name); - -} diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java index c6135a9d..596f994e 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java @@ -1,6 +1,5 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.security.HashUtils; import fr.xephi.authme.security.RandomString; import fr.xephi.authme.security.crypts.description.Recommendation; @@ -9,13 +8,11 @@ import fr.xephi.authme.security.crypts.description.Usage; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingException; -import java.math.BigInteger; import java.security.InvalidKeyException; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @Recommendation(Usage.DO_NOT_USE) -public class PHPFUSION implements NewEncrMethod { +public class PHPFUSION implements EncryptionMethod { @Override public String computeHash(String password, String salt, String name) { @@ -46,13 +43,6 @@ public class PHPFUSION implements NewEncrMethod { return new HashResult(computeHash(password, salt, name), salt); } - @Override - public boolean comparePassword(String hash, String password, - String playerName) { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(computeHash(password, salt, "")); - } - @Override public boolean comparePassword(String hash, String password, String salt, String name) { return hash.equals(computeHash(password, salt, name)); diff --git a/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java index ad2db489..c0b2f070 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java @@ -3,7 +3,7 @@ package fr.xephi.authme.security.crypts; /** * Common supertype for encryption methods which store their salt separately from the hash. */ -public abstract class SeparateSaltMethod implements NewEncrMethod { +public abstract class SeparateSaltMethod implements EncryptionMethod { @Override public abstract String computeHash(String password, String salt, String name); @@ -27,9 +27,4 @@ public abstract class SeparateSaltMethod implements NewEncrMethod { return true; } - @Override - @Deprecated - public boolean comparePassword(String hash, String password, String playerName) { - return false; - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java b/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java index 85321529..b6949a72 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java @@ -10,7 +10,7 @@ import fr.xephi.authme.security.crypts.description.Usage; */ @Recommendation(Usage.DO_NOT_USE) @HasSalt(SaltType.NONE) -public abstract class UnsaltedMethod implements NewEncrMethod { +public abstract class UnsaltedMethod implements EncryptionMethod { public abstract String computeHash(String password); @@ -24,12 +24,6 @@ public abstract class UnsaltedMethod implements NewEncrMethod { return computeHash(password); } - @Override - @Deprecated - public boolean comparePassword(String hash, String password, String playerName) { - return false; - } - @Override public boolean comparePassword(String hash, String password, String salt, String name) { return hash.equals(computeHash(password)); diff --git a/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java index 2ce8983f..1d294713 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java @@ -11,7 +11,7 @@ import fr.xephi.authme.security.crypts.description.Usage; */ @Recommendation(Usage.DO_NOT_USE) @HasSalt(SaltType.USERNAME) -public abstract class UsernameSaltMethod implements NewEncrMethod { +public abstract class UsernameSaltMethod implements EncryptionMethod { @Override public abstract HashResult computeHash(String password, String name); @@ -21,12 +21,6 @@ public abstract class UsernameSaltMethod implements NewEncrMethod { return hash.equals(computeHash(password, name).getHash()); } - @Override - @Deprecated - public boolean comparePassword(String hash, String password, String playerName) { - return false; - } - @Override public String computeHash(String password, String salt, String name) { return computeHash(password, name).getHash(); diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java index f4d1c2d3..5cfd954b 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java @@ -1,10 +1,10 @@ package fr.xephi.authme.security.crypts; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; -/** - */ -public class WBB4 implements EncryptionMethod { +@Recommendation(Usage.DOES_NOT_WORK) +public class WBB4 extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { @@ -12,8 +12,23 @@ public class WBB4 implements EncryptionMethod { } @Override - public boolean comparePassword(String hash, String password, String playerName) { + public boolean comparePassword(String hash, String password, String salt, String playerName) { return BCRYPT.checkpw(password, hash, 2); } + @Override + public String generateSalt() { + return BCRYPT.gensalt(8); + } + + /** + * Note that {@link #generateSalt()} is overridden for this class. + * + * @return The salt length + */ + @Override + public int getSaltLength() { + return 8; + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/XF.java b/src/main/java/fr/xephi/authme/security/crypts/XF.java index 7c9d8a70..42e026f2 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XF.java @@ -1,7 +1,5 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.AuthMe; - import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -11,7 +9,7 @@ import java.util.regex.Pattern; /** */ -public class XF implements NewEncrMethod { +public class XF implements EncryptionMethod { @Override public String computeHash(String password, String salt, String name) { @@ -24,12 +22,6 @@ public class XF implements NewEncrMethod { return new HashResult(computeHash(password, salt, null), salt); } - @Override - public boolean comparePassword(String hash, String password, String playerName) { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(regmatch("\"hash\";.:..:\"(.*)\";.:.:\"salt\"", salt)); - } - public boolean comparePassword(String hash, String password, String salt, String name) { return hash.equals(regmatch("\"hash\";.:..:\"(.*)\";.:.:\"salt\"", salt)); } diff --git a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java index 774cb4d9..a6d8163d 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java @@ -9,7 +9,6 @@ import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; /** * Test for implementations of {@link EncryptionMethod}. @@ -98,45 +97,30 @@ public abstract class AbstractEncryptionMethodTest { @Test public void testPasswordEquality() { - // TODO #358: Remove instanceof and use this code always - if (method instanceof NewEncrMethod) { - NewEncrMethod method1 = (NewEncrMethod) method; - for (String password : INTERNAL_PASSWORDS) { - final String salt = method1.generateSalt(); - final String hash = method1.computeHash(password, salt, USERNAME); + for (String password : INTERNAL_PASSWORDS) { + final String salt = method.generateSalt(); + final String hash = method.computeHash(password, salt, USERNAME); - // Check that the computeHash(password, salt, name) method has the same output for the returned salt - assertThat(hash, equalTo(method1.computeHash(password, salt, USERNAME))); + // Check that the computeHash(password, salt, name) method has the same output for the returned salt + assertThat(hash, equalTo(method.computeHash(password, salt, USERNAME))); - assertTrue("Generated hash for '" + password + "' should match password (hash = '" + hash + "')", - method1.comparePassword(hash, password, salt, USERNAME)); - if (!password.equals(password.toLowerCase())) { - assertFalse("Lower-case of '" + password + "' should not match generated hash '" + hash + "'", - method1.comparePassword(hash, password.toLowerCase(), salt, USERNAME)); - } - if (!password.equals(password.toUpperCase())) { - assertFalse("Upper-case of '" + password + "' should not match generated hash '" + hash + "'", - method1.comparePassword(hash, password.toUpperCase(), salt, USERNAME)); - } + assertTrue("Generated hash for '" + password + "' should match password (hash = '" + hash + "')", + method.comparePassword(hash, password, salt, USERNAME)); + if (!password.equals(password.toLowerCase())) { + assertFalse("Lower-case of '" + password + "' should not match generated hash '" + hash + "'", + method.comparePassword(hash, password.toLowerCase(), salt, USERNAME)); + } + if (!password.equals(password.toUpperCase())) { + assertFalse("Upper-case of '" + password + "' should not match generated hash '" + hash + "'", + method.comparePassword(hash, password.toUpperCase(), salt, USERNAME)); } - return; } - - fail("No longer supporting old EncryptionMethod implementations"); } private boolean doesGivenHashMatch(String password, EncryptionMethod method) { - // TODO #358: Remove casting checks and remove old code below - if (method instanceof NewEncrMethod) { - NewEncrMethod method1 = (NewEncrMethod) method; - String hash = hashes.get(password); - String salt = salts.get(password); - return method1.comparePassword(hash, password, salt, USERNAME); - } - - - // TODO #358: Remove line below - return method.comparePassword(hashes.get(password), password, USERNAME); + String hash = hashes.get(password); + String salt = salts.get(password); + return method.comparePassword(hash, password, salt, USERNAME); } // @org.junit.Test public void a() { AbstractEncryptionMethodTest.generateTest(); } @@ -148,28 +132,18 @@ public abstract class AbstractEncryptionMethodTest { System.out.println("\n\tpublic " + className + "Test() {"); System.out.println("\t\tsuper(new " + className + "(),"); - NewEncrMethod method1 = null; - if (method instanceof NewEncrMethod) { - method1 = (NewEncrMethod) method; - } - - String delim = ", "; for (String password : GIVEN_PASSWORDS) { if (password.equals(GIVEN_PASSWORDS[GIVEN_PASSWORDS.length - 1])) { delim = "); "; } - if (method1 != null) { - if (method1.hasSeparateSalt()) { - HashResult hashResult = method1.computeHash(password, USERNAME); - System.out.println(String.format("\t\tnew HashResult(\"%s\", \"%s\")%s// %s", - hashResult.getHash(), hashResult.getSalt(), delim, password)); - } else { - System.out.println("\t\t\"" + method1.computeHash(password, USERNAME).getHash() - + "\"" + delim + "// " + password); - } + + if (method.hasSeparateSalt()) { + HashResult hashResult = method.computeHash(password, USERNAME); + System.out.println(String.format("\t\tnew HashResult(\"%s\", \"%s\")%s// %s", + hashResult.getHash(), hashResult.getSalt(), delim, password)); } else { - System.out.println("\t\t\"" + method.computeHash(password, null, USERNAME) + System.out.println("\t\t\"" + method.computeHash(password, USERNAME).getHash() + "\"" + delim + "// " + password); } } From 121d3232219f5129af234509130a5b71b12351eb Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 28 Dec 2015 22:32:24 +0100 Subject: [PATCH 10/18] #369 Delete Xenforo (XF) encryption algorithm Rationale: - Seems not to have been working since it was added to the codebase - Seems not to correspond to the actual Xenforo password hashing (class does some sort of JSON extraction?) - It would be easier to rewrite it from scratch if someone requests it later --- README.md | 8 +- .../xephi/authme/security/HashAlgorithm.java | 1 - .../fr/xephi/authme/security/crypts/XF.java | 73 ------------------- src/main/resources/config.yml | 2 +- .../xephi/authme/security/crypts/XFTest.java | 17 ----- 5 files changed, 4 insertions(+), 97 deletions(-) delete mode 100644 src/main/java/fr/xephi/authme/security/crypts/XF.java delete mode 100644 src/test/java/fr/xephi/authme/security/crypts/XFTest.java diff --git a/README.md b/README.md index fb23fd75..f98ce8bb 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,6 @@ typing commands or using the inventory. It can also kick players with uncommonly

  • MyBB: MYBB
  • IPB3: IPB3
  • PhpFusion: PHPFUSION
  • -
  • Xenforo SHA1: XFSHA1 (Deprecated)
  • -
  • Xenforo SHA256: XFSHA256 (Deprecated)
  • Joomla: JOOMLA
  • WBB3: WBB3*
  • SHA512: SHA512
  • @@ -92,7 +90,7 @@ typing commands or using the inventory. It can also kick players with uncommonly
  • Custom MySQL tables/columns names (useful with forums databases)
  • Cached database queries!
  • -
  • Full compatible with Citizens2, CombatTag, CombatTagPlus and ChestShop!
  • +
  • Fully compatible with Citizens2, CombatTag, CombatTagPlus and ChestShop!
  • Compatible with Minecraft mods like BuildCraft or RedstoneCraft
  • Restricted users (associate a Username with an IP)
  • Protect player's inventory until a correct Authentication
  • @@ -120,7 +118,7 @@ typing commands or using the inventory. It can also kick players with uncommonly
  • Website Integration
  • Click here for an example of the Config file
  • How to convert from Rakamak -
  • Convert from FlatFile (auths.db but not the sqlite one ) to MySQL: /converter +
  • Convert from FlatFile (auths.db but not the sqlite one) to MySQL: /converter

  • @@ -139,5 +137,5 @@ GameHosting.it is leader in Italy as Game Server Provider. With its own DataCent #####Credits

    Team members: look at the team.txt file -

    Credit for old version of the plugin to: d4rkwarriors, fabe1337 , Whoami2 and pomo4ka

    +

    Credit for old version of the plugin to: d4rkwarriors, fabe1337, Whoami2 and pomo4ka

    Thanks also to: AS1LV3RN1NJA, Hoeze and eprimex

    diff --git a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java index ad212767..607df6b2 100644 --- a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java +++ b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java @@ -22,7 +22,6 @@ public enum HashAlgorithm { IPB3(fr.xephi.authme.security.crypts.IPB3.class), PHPFUSION(fr.xephi.authme.security.crypts.PHPFUSION.class), SMF(fr.xephi.authme.security.crypts.SMF.class), - XENFORO(fr.xephi.authme.security.crypts.XF.class), SALTED2MD5(fr.xephi.authme.security.crypts.SALTED2MD5.class), JOOMLA(fr.xephi.authme.security.crypts.JOOMLA.class), BCRYPT(fr.xephi.authme.security.crypts.BCRYPT.class), diff --git a/src/main/java/fr/xephi/authme/security/crypts/XF.java b/src/main/java/fr/xephi/authme/security/crypts/XF.java deleted file mode 100644 index 42e026f2..00000000 --- a/src/main/java/fr/xephi/authme/security/crypts/XF.java +++ /dev/null @@ -1,73 +0,0 @@ -package fr.xephi.authme.security.crypts; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - */ -public class XF implements EncryptionMethod { - - @Override - public String computeHash(String password, String salt, String name) { - return getSha256(getSha256(password) + regmatch("\"salt\";.:..:\"(.*)\";.:.:\"hashFunc\"", salt)); - } - - @Override - public HashResult computeHash(String password, String name) { - String salt = generateSalt(); - return new HashResult(computeHash(password, salt, null), salt); - } - - public boolean comparePassword(String hash, String password, String salt, String name) { - return hash.equals(regmatch("\"hash\";.:..:\"(.*)\";.:.:\"salt\"", salt)); - } - - private String getSha256(String password) { - MessageDigest md = null; - try { - md = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - // TODO #358: Handle exception properly - throw new RuntimeException(e); - } - md.update(password.getBytes()); - byte byteData[] = md.digest(); - StringBuilder sb = new StringBuilder(); - for (byte element : byteData) { - sb.append(Integer.toString((element & 0xff) + 0x100, 16).substring(1)); - } - StringBuilder hexString = new StringBuilder(); - for (byte element : byteData) { - String hex = Integer.toHexString(0xff & element); - if (hex.length() == 1) { - hexString.append('0'); - } - hexString.append(hex); - } - return hexString.toString(); - } - - @Override - public String generateSalt() { - // TODO #369: Find out what kind of salt format XF uses - return ""; - } - - @Override - public boolean hasSeparateSalt() { - return true; - } - - private String regmatch(String pattern, String line) { - List allMatches = new ArrayList<>(); - Matcher m = Pattern.compile(pattern).matcher(line); - while (m.find()) { - allMatches.add(m.group(1)); - } - return allMatches.get(0); - } -} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 7f151c49..a98d9739 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -181,7 +181,7 @@ settings: # Example unLoggedinGroup: NotLogged unLoggedinGroup: unLoggedinGroup # possible values: MD5, SHA1, SHA256, WHIRLPOOL, XAUTH, MD5VB, PHPBB, - # MYBB, IPB3, PHPFUSION, SMF, XENFORO, SALTED2MD5, JOOMLA, BCRYPT, WBB3, SHA512, + # MYBB, IPB3, PHPFUSION, SMF, SALTED2MD5, JOOMLA, BCRYPT, WBB3, SHA512, # DOUBLEMD5, PBKDF2, PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM(for developpers only) passwordHash: SHA256 # salt length for the SALTED2MD5 MD5(MD5(password)+salt) diff --git a/src/test/java/fr/xephi/authme/security/crypts/XFTest.java b/src/test/java/fr/xephi/authme/security/crypts/XFTest.java deleted file mode 100644 index e5231069..00000000 --- a/src/test/java/fr/xephi/authme/security/crypts/XFTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package fr.xephi.authme.security.crypts; - -import org.junit.Ignore; -import org.junit.Test; - -/** - * Test for {@link XF}. - */ -@Ignore -// TODO #369: XF needs to generate a salt it is expecting -public class XFTest { - - @Test - public void shouldComputeHash() { - System.out.println(new XF().computeHash("Test", "name")); - } -} From 1c12278c4b0a7f30edc3eb06f8b06133c7646f93 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 28 Dec 2015 22:39:15 +0100 Subject: [PATCH 11/18] #369 Remove XENFORO enum entry --- .../fr/xephi/authme/datasource/MySQL.java | 98 +------------------ .../authme/security/PasswordSecurity.java | 1 - 2 files changed, 1 insertion(+), 98 deletions(-) diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index e779afff..7135d6ad 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -303,16 +303,6 @@ public class MySQL implements DataSource { .build(); rs.close(); pst.close(); - if (Settings.getPasswordHash == HashAlgorithm.XENFORO) { - pst = con.prepareStatement("SELECT data FROM xf_user_authenticate WHERE " + columnID + "=?;"); - pst.setInt(1, id); - rs = pst.executeQuery(); - if (rs.next()) { - Blob blob = rs.getBlob("data"); - byte[] bytes = blob.getBytes(1, (int) blob.length()); - pAuth.setHash(new String(bytes)); - } - } } catch (SQLException ex) { ConsoleLogger.showError(ex.getMessage()); ConsoleLogger.writeStackTrace(ex); @@ -502,25 +492,6 @@ public class MySQL implements DataSource { } rs.close(); pst.close(); - } else if (Settings.getPasswordHash == HashAlgorithm.XENFORO) { - pst = con.prepareStatement("SELECT " + columnID + " FROM " + tableName + " WHERE " + columnName + "=?;"); - pst.setString(1, auth.getNickname()); - rs = pst.executeQuery(); - if (rs.next()) { - int id = rs.getInt(columnID); - // Insert password in the correct table - pst2 = con.prepareStatement("INSERT INTO xf_user_authenticate (user_id, scheme_class, data) VALUES (?,?,?);"); - pst2.setInt(1, id); - pst2.setString(2, "XenForo_Authentication_Core12"); - byte[] bytes = auth.getHash().getBytes(); - Blob blob = con.createBlob(); - blob.setBytes(1, bytes); - pst2.setBlob(3, blob); - pst2.executeUpdate(); - pst2.close(); - } - rs.close(); - pst.close(); } return true; } catch (SQLException ex) { @@ -548,34 +519,6 @@ public class MySQL implements DataSource { pst.setString(2, auth.getNickname()); pst.executeUpdate(); pst.close(); - if (Settings.getPasswordHash == HashAlgorithm.XENFORO) { - sql = "SELECT " + columnID + " FROM " + tableName + " WHERE " + columnName + "=?;"; - pst = con.prepareStatement(sql); - pst.setString(1, auth.getNickname()); - ResultSet rs = pst.executeQuery(); - if (rs.next()) { - int id = rs.getInt(columnID); - // Insert password in the correct table - sql = "UPDATE xf_user_authenticate SET data=? WHERE " + columnID + "=?;"; - PreparedStatement pst2 = con.prepareStatement(sql); - byte[] bytes = auth.getHash().getBytes(); - Blob blob = con.createBlob(); - blob.setBytes(1, bytes); - pst2.setBlob(1, blob); - pst2.setInt(2, id); - pst2.executeUpdate(); - pst2.close(); - // ... - sql = "UPDATE xf_user_authenticate SET scheme_class=? WHERE " + columnID + "=?;"; - pst2 = con.prepareStatement(sql); - pst2.setString(1, "XenForo_Authentication_Core12"); - pst2.setInt(2, id); - pst2.executeUpdate(); - pst2.close(); - } - rs.close(); - pst.close(); - } return true; } catch (SQLException ex) { ConsoleLogger.showError(ex.getMessage()); @@ -679,24 +622,7 @@ public class MySQL implements DataSource { public synchronized boolean removeAuth(String user) { user = user.toLowerCase(); try (Connection con = getConnection()) { - String sql; - PreparedStatement pst; - if (Settings.getPasswordHash == HashAlgorithm.XENFORO) { - sql = "SELECT " + columnID + " FROM " + tableName + " WHERE " + columnName + "=?;"; - pst = con.prepareStatement(sql); - pst.setString(1, user); - ResultSet rs = pst.executeQuery(); - if (rs.next()) { - int id = rs.getInt(columnID); - sql = "DELETE FROM xf_user_authenticate WHERE " + columnID + "=" + id; - Statement st = con.createStatement(); - st.executeUpdate(sql); - st.close(); - } - rs.close(); - pst.close(); - } - pst = con.prepareStatement("DELETE FROM " + tableName + " WHERE " + columnName + "=?;"); + PreparedStatement pst = con.prepareStatement("DELETE FROM " + tableName + " WHERE " + columnName + "=?;"); pst.setString(1, user); pst.executeUpdate(); return true; @@ -1136,17 +1062,6 @@ public class MySQL implements DataSource { .groupId(group) .build(); - if (Settings.getPasswordHash == HashAlgorithm.XENFORO) { - int id = rs.getInt(columnID); - pst.setInt(1, id); - ResultSet rs2 = pst.executeQuery(); - if (rs2.next()) { - Blob blob = rs2.getBlob("data"); - byte[] bytes = blob.getBytes(1, (int) blob.length()); - pAuth.setHash(new String(bytes)); - } - rs2.close(); - } auths.add(pAuth); } pst.close(); @@ -1191,17 +1106,6 @@ public class MySQL implements DataSource { .groupId(group) .build(); - if (Settings.getPasswordHash == HashAlgorithm.XENFORO) { - int id = rs.getInt(columnID); - pst.setInt(1, id); - ResultSet rs2 = pst.executeQuery(); - if (rs2.next()) { - Blob blob = rs2.getBlob("data"); - byte[] bytes = blob.getBytes(1, (int) blob.length()); - pAuth.setHash(new String(bytes)); - } - rs2.close(); - } auths.add(pAuth); } } catch (Exception ex) { diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index b733d55a..d84b6870 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -106,7 +106,6 @@ public class PasswordSecurity { case SHA1: case WHIRLPOOL: case PLAINTEXT: - case XENFORO: case SHA512: case ROYALAUTH: case CRAZYCRYPT1: From b3b751920a7a4ca756cd79f084a5780020d8072a Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 29 Dec 2015 00:13:20 +0100 Subject: [PATCH 12/18] #358 Replace usage of static PasswordSecurity methods - Replace static methods to instance methods - Use PlayerAuth builder instead of constructor --- src/main/java/fr/xephi/authme/AuthMe.java | 55 +++++---- src/main/java/fr/xephi/authme/api/NewAPI.java | 97 ++++++++-------- .../xephi/authme/command/CommandService.java | 15 ++- .../authme/ChangePasswordAdminCommand.java | 24 ++-- .../authme/RegisterAdminCommand.java | 50 ++++----- .../executable/email/RecoverEmailCommand.java | 57 +++++----- .../authme/converter/RakamakConverter.java | 43 ++++--- .../process/login/AsynchronousLogin.java | 21 ++-- .../process/register/AsyncRegister.java | 23 ++-- .../unregister/AsynchronousUnregister.java | 85 +++++++------- .../authme/security/PasswordSecurity.java | 106 +++--------------- .../xephi/authme/task/ChangePasswordTask.java | 83 ++++++-------- .../authme/command/CommandServiceTest.java | 26 ++++- 13 files changed, 314 insertions(+), 371 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index efe629a2..ad488d69 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -42,6 +42,7 @@ import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.process.Management; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.OtherAccounts; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Spawn; @@ -100,6 +101,7 @@ public class AuthMe extends JavaPlugin { private Messages messages; private JsonCache playerBackup; private ModuleManager moduleManager; + private PasswordSecurity passwordSecurity; // Public Instances public NewAPI api; @@ -206,12 +208,24 @@ public class AuthMe extends JavaPlugin { return; } - // Set up messages + // Set up messages & password security messages = Messages.getInstance(); + passwordSecurity = new PasswordSecurity(getDataSource(), Settings.getPasswordHash, Settings.supportOldPassword); + + // Connect to the database and setup tables + try { + setupDatabase(); + } catch (Exception e) { + ConsoleLogger.writeStackTrace(e); + ConsoleLogger.showError(e.getMessage()); + ConsoleLogger.showError("Fatal error occurred during database connection! Authme initialization ABORTED!"); + stopOrUnload(); + return; + } // Set up the permissions manager and command handler permsMan = initializePermissionsManager(); - commandHandler = initializeCommandHandler(permsMan, messages); + commandHandler = initializeCommandHandler(permsMan, messages, passwordSecurity); // Set up the module manager setupModuleManager(); @@ -253,16 +267,7 @@ public class AuthMe extends JavaPlugin { // Do a backup on start new PerformBackup(plugin).doBackup(PerformBackup.BackupCause.START); - // Connect to the database and setup tables - try { - setupDatabase(); - } catch (Exception e) { - ConsoleLogger.writeStackTrace(e); - ConsoleLogger.showError(e.getMessage()); - ConsoleLogger.showError("Fatal error occurred during database connection! Authme initialization ABORTED!"); - stopOrUnload(); - return; - } + // Setup the inventory backup playerBackup = new JsonCache(); @@ -409,11 +414,12 @@ public class AuthMe extends JavaPlugin { } } - private CommandHandler initializeCommandHandler(PermissionsManager permissionsManager, Messages messages) { + private CommandHandler initializeCommandHandler(PermissionsManager permissionsManager, Messages messages, + PasswordSecurity passwordSecurity) { HelpProvider helpProvider = new HelpProvider(permissionsManager); Set baseCommands = CommandInitializer.buildCommands(); CommandMapper mapper = new CommandMapper(baseCommands, messages, permissionsManager, helpProvider); - CommandService commandService = new CommandService(this, mapper, helpProvider, messages); + CommandService commandService = new CommandService(this, mapper, helpProvider, messages, passwordSecurity); return new CommandHandler(commandService); } @@ -563,14 +569,15 @@ public class AuthMe extends JavaPlugin { int accounts = database.getAccountsRegistered(); if (accounts >= 4000) { ConsoleLogger.showError("YOU'RE USING THE SQLITE DATABASE WITH " - + accounts + "+ ACCOUNTS, FOR BETTER PERFORMANCES, PLEASE UPGRADE TO MYSQL!!"); + + accounts + "+ ACCOUNTS; FOR BETTER PERFORMANCE, PLEASE UPGRADE TO MYSQL!!"); } } }); } if (Settings.getDataSource == DataSource.DataSourceType.FILE) { - ConsoleLogger.showError("FlatFile backend has been detected and is now deprecated, it will be changed to SQLite... Connection will be impossible until conversion is done!"); + ConsoleLogger.showError("FlatFile backend has been detected and is now deprecated, it will be changed " + + "to SQLite... Connection will be impossible until conversion is done!"); ForceFlatToSqlite converter = new ForceFlatToSqlite(database); DataSource source = converter.run(); if (source != null) { @@ -579,12 +586,12 @@ public class AuthMe extends JavaPlugin { } // TODO: Move this to another place maybe ? - if (Settings.getPasswordHash == HashAlgorithm.PLAINTEXT) - { - ConsoleLogger.showError("Your HashAlgorithm has been detected has plaintext and is now deprecrated, it will be changed and hashed now to AuthMe default hashing method"); - for (PlayerAuth auth : database.getAllAuths()) - { - auth.setHash(PasswordSecurity.getHash(HashAlgorithm.SHA256, auth.getHash(), auth.getNickname())); + if (Settings.getPasswordHash == HashAlgorithm.PLAINTEXT) { + ConsoleLogger.showError("Your HashAlgorithm has been detected as plaintext and is now deprecated; " + + "it will be changed and hashed now to the AuthMe default hashing method"); + for (PlayerAuth auth : database.getAllAuths()) { + HashResult hashResult = passwordSecurity.computeHash(HashAlgorithm.SHA256, auth.getHash(), auth.getNickname()); + auth.setHash(hashResult.getHash()); database.updatePassword(auth); } Settings.setValue("settings.security.passwordHash", "SHA256"); @@ -973,4 +980,8 @@ public class AuthMe extends JavaPlugin { return database; } + public PasswordSecurity getPasswordSecurity() { + return passwordSecurity; + } + } diff --git a/src/main/java/fr/xephi/authme/api/NewAPI.java b/src/main/java/fr/xephi/authme/api/NewAPI.java index 908f75df..09d47b0c 100644 --- a/src/main/java/fr/xephi/authme/api/NewAPI.java +++ b/src/main/java/fr/xephi/authme/api/NewAPI.java @@ -3,8 +3,7 @@ package fr.xephi.authme.api; import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; -import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.util.Utils; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -12,9 +11,8 @@ import org.bukkit.Server; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import java.security.NoSuchAlgorithmException; - /** + * The current API of AuthMe. */ public class NewAPI { @@ -42,33 +40,36 @@ public class NewAPI { /** * Hook into AuthMe * - * @return AuthMe plugin + * @return The API object */ public static NewAPI getInstance() { - if (singleton != null) + if (singleton != null) { return singleton; + } Plugin p = Bukkit.getServer().getPluginManager().getPlugin("AuthMe"); if (p == null || !(p instanceof AuthMe)) { return null; } AuthMe authme = (AuthMe) p; - singleton = (new NewAPI(authme)); + singleton = new NewAPI(authme); return singleton; } /** - * Method getPlugin. + * Return the plugin instance. * - * @return AuthMe + * @return The AuthMe instance */ public AuthMe getPlugin() { return plugin; } /** - * @param player + * Return whether the given player is authenticated. * - * @return true if player is authenticate + * @param player The player to verify + * + * @return true if the player is authenticated */ public boolean isAuthenticated(Player player) { return PlayerCache.getInstance().isAuthenticated(player.getName()); @@ -93,15 +94,15 @@ public class NewAPI { } /** - * Method getLastLocation. + * Get the last location of a player. * - * @param player Player + * @param player Player The player to process * - * @return Location + * @return Location The location of the player */ public Location getLastLocation(Player player) { try { - PlayerAuth auth = PlayerCache.getInstance().getAuth(player.getName().toLowerCase()); + PlayerAuth auth = PlayerCache.getInstance().getAuth(player.getName()); if (auth != null) { return new Location(Bukkit.getWorld(auth.getWorld()), auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ()); @@ -121,81 +122,81 @@ public class NewAPI { */ public boolean isRegistered(String playerName) { String player = playerName.toLowerCase(); - return plugin.database.isAuthAvailable(player); + return plugin.getDataSource().isAuthAvailable(player); } /** - * @param playerName String - * @param passwordToCheck String + * Check the password for the given player. * - * @return true if the password is correct , false else + * @param playerName The player to check the password for + * @param passwordToCheck The password to check + * + * @return true if the password is correct, false otherwise */ public boolean checkPassword(String playerName, String passwordToCheck) { - if (!isRegistered(playerName)) - return false; - String player = playerName.toLowerCase(); - PlayerAuth auth = plugin.database.getAuth(player); - try { - return PasswordSecurity.comparePasswordWithHash(passwordToCheck, auth.getHash(), playerName); - } catch (NoSuchAlgorithmException e) { + if (!isRegistered(playerName)) { return false; } + String player = playerName.toLowerCase(); + PlayerAuth auth = plugin.getDataSource().getAuth(player); + return plugin.getPasswordSecurity().comparePassword(passwordToCheck, auth.getHash(), playerName); } /** - * Register a player + * Register a player. * - * @param playerName String - * @param password String + * @param playerName The player to register + * @param password The password to register the player with * - * @return true if the player is register correctly + * @return true if the player was registered successfully */ public boolean registerPlayer(String playerName, String password) { - try { - String name = playerName.toLowerCase(); - String hash = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); - if (isRegistered(name)) { - return false; - } - PlayerAuth auth = new PlayerAuth(name, hash, "192.168.0.1", 0, "your@email.com", playerName); - return plugin.database.saveAuth(auth); - } catch (NoSuchAlgorithmException ex) { + String name = playerName.toLowerCase(); + HashResult result = plugin.getPasswordSecurity().computeHash(password, name); + if (isRegistered(name)) { return false; } + PlayerAuth auth = PlayerAuth.builder() + .name(name) + .hash(result.getHash()) + .salt(result.getSalt()) + .realName(playerName) + .build(); + return plugin.getDataSource().saveAuth(auth); } /** - * Force a player to login + * Force a player to login. * - * @param player * player + * @param player The player to log in */ public void forceLogin(Player player) { plugin.getManagement().performLogin(player, "dontneed", true); } /** - * Force a player to logout + * Force a player to logout. * - * @param player * player + * @param player The player to log out */ public void forceLogout(Player player) { plugin.getManagement().performLogout(player); } /** - * Force a player to register + * Force a player to register. * - * @param player * player - * @param password String + * @param player The player to register + * @param password The password to use */ public void forceRegister(Player player, String password) { plugin.getManagement().performRegister(player, password, null); } /** - * Force a player to unregister + * Force a player to unregister. * - * @param player * player + * @param player The player to unregister */ public void forceUnregister(Player player) { plugin.getManagement().performUnregister(player, "", true); diff --git a/src/main/java/fr/xephi/authme/command/CommandService.java b/src/main/java/fr/xephi/authme/command/CommandService.java index 4e70a1fc..b53fc2e8 100644 --- a/src/main/java/fr/xephi/authme/command/CommandService.java +++ b/src/main/java/fr/xephi/authme/command/CommandService.java @@ -7,6 +7,7 @@ import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.process.Management; +import fr.xephi.authme.security.PasswordSecurity; import org.bukkit.command.CommandSender; import java.util.List; @@ -21,6 +22,7 @@ public class CommandService { private final Messages messages; private final HelpProvider helpProvider; private final CommandMapper commandMapper; + private final PasswordSecurity passwordSecurity; /** * Constructor. @@ -30,11 +32,13 @@ public class CommandService { * @param helpProvider Help provider * @param messages Messages instance */ - public CommandService(AuthMe authMe, CommandMapper commandMapper, HelpProvider helpProvider, Messages messages) { + public CommandService(AuthMe authMe, CommandMapper commandMapper, HelpProvider helpProvider, Messages messages, + PasswordSecurity passwordSecurity) { this.authMe = authMe; this.messages = messages; this.helpProvider = helpProvider; this.commandMapper = commandMapper; + this.passwordSecurity = passwordSecurity; } /** @@ -91,6 +95,15 @@ public class CommandService { return authMe.getDataSource(); } + /** + * Return the PasswordSecurity instance. + * + * @return The password security instance + */ + public PasswordSecurity getPasswordSecurity() { + return passwordSecurity; + } + /** * Output the help for a given command. * diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java index 1905d838..44d8967e 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java @@ -1,6 +1,5 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; @@ -8,13 +7,11 @@ import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.output.Messages; import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; -import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; -import java.security.NoSuchAlgorithmException; import java.util.List; /** @@ -60,13 +57,6 @@ public class ChangePasswordAdminCommand implements ExecutableCommand { @Override public void run() { DataSource dataSource = commandService.getDataSource(); - String hash; - try { - hash = PasswordSecurity.getHash(Settings.getPasswordHash, playerPass, playerNameLowerCase); - } catch (NoSuchAlgorithmException e) { - commandService.send(sender, MessageKey.ERROR); - return; - } PlayerAuth auth = null; if (PlayerCache.getInstance().isAuthenticated(playerNameLowerCase)) { auth = PlayerCache.getInstance().getAuth(playerNameLowerCase); @@ -77,17 +67,21 @@ public class ChangePasswordAdminCommand implements ExecutableCommand { commandService.send(sender, MessageKey.UNKNOWN_USER); return; } - auth.setHash(hash); + + // TODO #358: Do we always pass lowercase name?? In that case we need to do that in PasswordSecurity! + HashResult hashResult = commandService.getPasswordSecurity().computeHash(playerPass, playerNameLowerCase); + auth.setHash(hashResult.getHash()); + auth.setSalt(hashResult.getSalt()); if (PasswordSecurity.userSalt.containsKey(playerNameLowerCase)) { auth.setSalt(PasswordSecurity.userSalt.get(playerNameLowerCase)); commandService.getDataSource().updateSalt(auth); } if (!dataSource.updatePassword(auth)) { commandService.send(sender, MessageKey.ERROR); - return; + } else { + commandService.send(sender, MessageKey.PASSWORD_CHANGED_SUCCESS); + ConsoleLogger.info(playerNameLowerCase + "'s password changed"); } - commandService.send(sender, MessageKey.PASSWORD_CHANGED_SUCCESS); - ConsoleLogger.info(playerNameLowerCase + "'s password changed"); } }); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java index 0a752c44..44c0be4a 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java @@ -5,12 +5,11 @@ import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; -import java.security.NoSuchAlgorithmException; import java.util.List; /** @@ -41,7 +40,8 @@ public class RegisterAdminCommand implements ExecutableCommand { commandService.send(sender, MessageKey.PASSWORD_IS_USERNAME_ERROR); return; } - if (playerPassLowerCase.length() < Settings.getPasswordMinLen || playerPassLowerCase.length() > Settings.passwordMaxLength) { + if (playerPassLowerCase.length() < Settings.getPasswordMinLen + || playerPassLowerCase.length() > Settings.passwordMaxLength) { commandService.send(sender, MessageKey.INVALID_PASSWORD_LENGTH); return; } @@ -53,30 +53,30 @@ public class RegisterAdminCommand implements ExecutableCommand { @Override public void run() { - try { - if (commandService.getDataSource().isAuthAvailable(playerNameLowerCase)) { - commandService.send(sender, MessageKey.NAME_ALREADY_REGISTERED); - return; - } - String hash = PasswordSecurity.getHash(Settings.getPasswordHash, playerPass, playerNameLowerCase); - PlayerAuth auth = new PlayerAuth(playerNameLowerCase, hash, "192.168.0.1", 0L, "your@email.com", playerName); - if (PasswordSecurity.userSalt.containsKey(playerNameLowerCase) && PasswordSecurity.userSalt.get(playerNameLowerCase) != null) - auth.setSalt(PasswordSecurity.userSalt.get(playerNameLowerCase)); - else auth.setSalt(""); - if (!commandService.getDataSource().saveAuth(auth)) { - commandService.send(sender, MessageKey.ERROR); - return; - } - commandService.getDataSource().setUnlogged(playerNameLowerCase); - if (Bukkit.getPlayerExact(playerName) != null) - Bukkit.getPlayerExact(playerName).kickPlayer("An admin just registered you, please log again"); - commandService.send(sender, MessageKey.REGISTER_SUCCESS); - ConsoleLogger.info(playerNameLowerCase + " registered"); - } catch (NoSuchAlgorithmException ex) { - ConsoleLogger.showError(ex.getMessage()); - commandService.send(sender, MessageKey.ERROR); + if (commandService.getDataSource().isAuthAvailable(playerNameLowerCase)) { + commandService.send(sender, MessageKey.NAME_ALREADY_REGISTERED); + return; } + HashResult hashResult = commandService.getPasswordSecurity() + .computeHash(playerPass, playerNameLowerCase); + PlayerAuth auth = PlayerAuth.builder() + .name(playerNameLowerCase) + .realName(playerName) + .hash(hashResult.getHash()) + .salt(hashResult.getSalt()) + .build(); + if (!commandService.getDataSource().saveAuth(auth)) { + commandService.send(sender, MessageKey.ERROR); + return; + } + commandService.getDataSource().setUnlogged(playerNameLowerCase); + if (Bukkit.getPlayerExact(playerName) != null) { + Bukkit.getPlayerExact(playerName).kickPlayer("An admin just registered you, please log again"); + } else { + commandService.send(sender, MessageKey.REGISTER_SUCCESS); + ConsoleLogger.info(playerName + " registered"); + } } }); } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java index 07f05bfc..045bf00a 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java @@ -1,20 +1,18 @@ package fr.xephi.authme.command.executable.email; import fr.xephi.authme.AuthMe; -import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.RandomString; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.StringUtils; import org.bukkit.entity.Player; -import java.security.NoSuchAlgorithmException; import java.util.List; public class RecoverEmailCommand extends PlayerCommand { @@ -37,36 +35,33 @@ public class RecoverEmailCommand extends PlayerCommand { commandService.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); return; } - try { - String thePass = RandomString.generate(Settings.getRecoveryPassLength); - String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, thePass, playerName); - PlayerAuth auth; - if (PlayerCache.getInstance().isAuthenticated(playerName)) { - auth = PlayerCache.getInstance().getAuth(playerName); - } else if (dataSource.isAuthAvailable(playerName)) { - auth = dataSource.getAuth(playerName); - } else { - commandService.send(player, MessageKey.UNKNOWN_USER); - return; - } - if (Settings.getmailAccount.equals("") || Settings.getmailAccount.isEmpty()) { - commandService.send(player, MessageKey.ERROR); - return; - } - if (!playerMail.equalsIgnoreCase(auth.getEmail()) || playerMail.equalsIgnoreCase("your@email.com") - || auth.getEmail().equalsIgnoreCase("your@email.com")) { - commandService.send(player, MessageKey.INVALID_EMAIL); - return; - } - auth.setHash(hashNew); - dataSource.updatePassword(auth); - plugin.mail.main(auth, thePass); - commandService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); - } catch (NoSuchAlgorithmException | NoClassDefFoundError ex) { - ConsoleLogger.showError(StringUtils.formatException(ex)); - commandService.send(player, MessageKey.ERROR); + String thePass = RandomString.generate(Settings.getRecoveryPassLength); + HashResult hashNew = commandService.getPasswordSecurity().computeHash(thePass, playerName); + PlayerAuth auth; + if (PlayerCache.getInstance().isAuthenticated(playerName)) { + auth = PlayerCache.getInstance().getAuth(playerName); + } else if (dataSource.isAuthAvailable(playerName)) { + auth = dataSource.getAuth(playerName); + } else { + commandService.send(player, MessageKey.UNKNOWN_USER); + return; } + if (StringUtils.isEmpty(Settings.getmailAccount)) { + commandService.send(player, MessageKey.ERROR); + return; + } + + if (!playerMail.equalsIgnoreCase(auth.getEmail()) || playerMail.equalsIgnoreCase("your@email.com") + || auth.getEmail().equalsIgnoreCase("your@email.com")) { + commandService.send(player, MessageKey.INVALID_EMAIL); + return; + } + auth.setHash(hashNew.getHash()); + auth.setSalt(hashNew.getSalt()); + dataSource.updatePassword(auth); + plugin.mail.main(auth, thePass); + commandService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); } else { commandService.send(player, MessageKey.REGISTER_EMAIL_MESSAGE); } diff --git a/src/main/java/fr/xephi/authme/converter/RakamakConverter.java b/src/main/java/fr/xephi/authme/converter/RakamakConverter.java index ab8b754f..15e26efc 100644 --- a/src/main/java/fr/xephi/authme/converter/RakamakConverter.java +++ b/src/main/java/fr/xephi/authme/converter/RakamakConverter.java @@ -6,6 +6,7 @@ import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; import org.bukkit.command.CommandSender; @@ -13,7 +14,6 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; -import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map.Entry; @@ -28,15 +28,12 @@ public class RakamakConverter implements Converter { public RakamakConverter(AuthMe instance, CommandSender sender) { this.instance = instance; - this.database = instance.database; + this.database = instance.getDataSource(); this.sender = sender; } - public RakamakConverter getInstance() { - return this; - } - @Override + // TODO ljacqu 20151229: Restructure this into smaller portions public void run() { HashAlgorithm hash = Settings.getPasswordHash; boolean useIP = Settings.rakamakUseIp; @@ -45,7 +42,7 @@ public class RakamakConverter implements Converter { File source = new File(Settings.PLUGIN_FOLDER, fileName); File ipfiles = new File(Settings.PLUGIN_FOLDER, ipFileName); HashMap playerIP = new HashMap<>(); - HashMap playerPSW = new HashMap<>(); + HashMap playerPSW = new HashMap<>(); try { BufferedReader users; BufferedReader ipFile; @@ -61,30 +58,30 @@ public class RakamakConverter implements Converter { } } ipFile.close(); + users = new BufferedReader(new FileReader(source)); + PasswordSecurity passwordSecurity = instance.getPasswordSecurity(); while ((line = users.readLine()) != null) { if (line.contains("=")) { String[] arguments = line.split("="); - try { - playerPSW.put(arguments[0], PasswordSecurity.getHash(hash, arguments[1], arguments[0])); - } catch (NoSuchAlgorithmException e) { - ConsoleLogger.showError(e.getMessage()); - } + HashResult hashResult = passwordSecurity.computeHash(hash, arguments[1], arguments[0]); + playerPSW.put(arguments[0], hashResult); + } } users.close(); - for (Entry m : playerPSW.entrySet()) { + for (Entry m : playerPSW.entrySet()) { String playerName = m.getKey(); - String psw = playerPSW.get(playerName); - String ip; - if (useIP) { - ip = playerIP.get(playerName); - } else { - ip = "127.0.0.1"; - } - PlayerAuth auth = new PlayerAuth(playerName, psw, ip, System.currentTimeMillis(), playerName); - if (PasswordSecurity.userSalt.containsKey(playerName)) - auth.setSalt(PasswordSecurity.userSalt.get(playerName)); + HashResult psw = playerPSW.get(playerName); + String ip = useIP ? playerIP.get(playerName) : "127.0.0.1"; + PlayerAuth auth = PlayerAuth.builder() + .name(playerName) + .realName(playerName) + .ip(ip) + .hash(psw.getHash()) + .salt(psw.getSalt()) + .lastLogin(System.currentTimeMillis()) + .build(); database.saveAuth(auth); } ConsoleLogger.info("Rakamak database has been imported correctly"); diff --git a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java index c2bbbd64..17138cc6 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -138,17 +138,18 @@ public class AsynchronousLogin { String hash = pAuth.getHash(); String email = pAuth.getEmail(); - boolean passwordVerified = true; - if (!forceLogin) - try { - passwordVerified = PasswordSecurity.comparePasswordWithHash(password, hash, realName); - } catch (Exception ex) { - ConsoleLogger.showError(ex.getMessage()); - m.send(player, MessageKey.ERROR); - return; - } + boolean passwordVerified = forceLogin || plugin.getPasswordSecurity().comparePassword(password, hash, realName); + if (passwordVerified && player.isOnline()) { - PlayerAuth auth = new PlayerAuth(name, hash, getIP(), new Date().getTime(), email, realName); + PlayerAuth auth = PlayerAuth.builder() + .name(name) + .realName(realName) + .ip(getIP()) + .lastLogin(new Date().getTime()) + .email(email) + .hash(hash) + .salt(pAuth.getSalt()) + .build(); database.updateSession(auth); if (Settings.useCaptcha) { diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java index fb102ec9..9796dbb0 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -8,7 +8,7 @@ import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.PlayerPermission; -import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; import org.bukkit.entity.Player; @@ -44,7 +44,10 @@ public class AsyncRegister { } else if (!Settings.isRegistrationEnabled) { m.send(player, MessageKey.REGISTRATION_DISABLED); return false; - } else if (passLow.contains("delete") || passLow.contains("where") || passLow.contains("insert") || passLow.contains("modify") || passLow.contains("from") || passLow.contains("select") || passLow.contains(";") || passLow.contains("null") || !passLow.matches(Settings.getPassRegex)) { + } else if (passLow.contains("delete") || passLow.contains("where") || passLow.contains("insert") + || passLow.contains("modify") || passLow.contains("from") || passLow.contains("select") + || passLow.contains(";") || passLow.contains("null") || !passLow.matches(Settings.getPassRegex)) { + // TODO #308: Remove check for SQL keywords m.send(player, MessageKey.PASSWORD_MATCH_ERROR); return false; } else if (passLow.equalsIgnoreCase(player.getName())) { @@ -87,26 +90,25 @@ public class AsyncRegister { } } - private void emailRegister() throws Exception { + private void emailRegister() { if (Settings.getmaxRegPerEmail > 0 && !plugin.getPermissionsManager().hasPermission(player, PlayerPermission.ALLOW_MULTIPLE_ACCOUNTS) && database.getAllAuthsByEmail(email).size() >= Settings.getmaxRegPerEmail) { m.send(player, MessageKey.MAX_REGISTER_EXCEEDED); return; } - final String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); - final String salt = PasswordSecurity.userSalt.get(name); + final HashResult hashResult = plugin.getPasswordSecurity().computeHash(password, name); PlayerAuth auth = PlayerAuth.builder() .name(name) .realName(player.getName()) - .hash(hashNew) + .hash(hashResult.getHash()) + .salt(hashResult.getSalt()) .ip(ip) .locWorld(player.getLocation().getWorld().getName()) .locX(player.getLocation().getX()) .locY(player.getLocation().getY()) .locZ(player.getLocation().getZ()) .email(email) - .salt(salt != null ? salt : "") .build(); if (!database.saveAuth(auth)) { @@ -122,18 +124,17 @@ public class AsyncRegister { } private void passwordRegister() throws Exception { - final String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); - final String salt = PasswordSecurity.userSalt.get(name); + final HashResult hashResult = plugin.getPasswordSecurity().computeHash(password, name); PlayerAuth auth = PlayerAuth.builder() .name(name) .realName(player.getName()) - .hash(hashNew) + .hash(hashResult.getHash()) + .salt(hashResult.getSalt()) .ip(ip) .locWorld(player.getLocation().getWorld().getName()) .locX(player.getLocation().getX()) .locY(player.getLocation().getY()) .locZ(player.getLocation().getZ()) - .salt(salt != null ? salt : "") .build(); if (!database.saveAuth(auth)) { diff --git a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java index 53c54b3e..83b8cf7e 100644 --- a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java +++ b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java @@ -62,55 +62,52 @@ public class AsynchronousUnregister { } public void process() { - try { - if (force || PasswordSecurity.comparePasswordWithHash(password, PlayerCache.getInstance().getAuth(name).getHash(), player.getName())) { - if (!plugin.database.removeAuth(name)) { - m.send(player, MessageKey.ERROR); - return; + if (force || plugin.getPasswordSecurity().comparePassword(password, PlayerCache.getInstance().getAuth(name).getHash(), player.getName())) { + if (!plugin.database.removeAuth(name)) { + m.send(player, MessageKey.ERROR); + return; + } + int timeOut = Settings.getRegistrationTimeout * 20; + if (Settings.isForcedRegistrationEnabled) { + Utils.teleportToSpawn(player); + player.saveData(); + PlayerCache.getInstance().removePlayer(player.getName().toLowerCase()); + if (!Settings.getRegisteredGroup.isEmpty()) { + Utils.setGroup(player, GroupType.UNREGISTERED); } - int timeOut = Settings.getRegistrationTimeout * 20; - if (Settings.isForcedRegistrationEnabled) { - Utils.teleportToSpawn(player); - player.saveData(); - PlayerCache.getInstance().removePlayer(player.getName().toLowerCase()); - if (!Settings.getRegisteredGroup.isEmpty()) { - Utils.setGroup(player, GroupType.UNREGISTERED); - } - LimboCache.getInstance().addLimboPlayer(player); - LimboPlayer limboPlayer = LimboCache.getInstance().getLimboPlayer(name); - int interval = Settings.getWarnMessageInterval; - BukkitScheduler scheduler = plugin.getServer().getScheduler(); - if (timeOut != 0) { - BukkitTask id = scheduler.runTaskLaterAsynchronously(plugin, - new TimeoutTask(plugin, name, player), timeOut); - limboPlayer.setTimeoutTaskId(id); - } - limboPlayer.setMessageTaskId(scheduler.runTaskAsynchronously(plugin, - new MessageTask(plugin, name, m.retrieve(MessageKey.REGISTER_MESSAGE), interval))); - m.send(player, MessageKey.UNREGISTERED_SUCCESS); - ConsoleLogger.info(player.getDisplayName() + " unregistered himself"); - return; - } - if (!Settings.unRegisteredGroup.isEmpty()) { - Utils.setGroup(player, Utils.GroupType.UNREGISTERED); - } - PlayerCache.getInstance().removePlayer(name); - // check if Player cache File Exist and delete it, preventing - // duplication of items - if (playerCache.doesCacheExist(player)) { - playerCache.removeCache(player); - } - // Apply blind effect - if (Settings.applyBlindEffect) { - player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeOut, 2)); + LimboCache.getInstance().addLimboPlayer(player); + LimboPlayer limboPlayer = LimboCache.getInstance().getLimboPlayer(name); + int interval = Settings.getWarnMessageInterval; + BukkitScheduler scheduler = plugin.getServer().getScheduler(); + if (timeOut != 0) { + BukkitTask id = scheduler.runTaskLaterAsynchronously(plugin, + new TimeoutTask(plugin, name, player), timeOut); + limboPlayer.setTimeoutTaskId(id); } + limboPlayer.setMessageTaskId(scheduler.runTaskAsynchronously(plugin, + new MessageTask(plugin, name, m.retrieve(MessageKey.REGISTER_MESSAGE), interval))); m.send(player, MessageKey.UNREGISTERED_SUCCESS); ConsoleLogger.info(player.getDisplayName() + " unregistered himself"); - Utils.teleportToSpawn(player); - } else { - m.send(player, MessageKey.WRONG_PASSWORD); + return; } - } catch (NoSuchAlgorithmException ignored) { + if (!Settings.unRegisteredGroup.isEmpty()) { + Utils.setGroup(player, Utils.GroupType.UNREGISTERED); + } + PlayerCache.getInstance().removePlayer(name); + // check if Player cache File Exist and delete it, preventing + // duplication of items + if (playerCache.doesCacheExist(player)) { + playerCache.removeCache(player); + } + // Apply blind effect + if (Settings.applyBlindEffect) { + player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeOut, 2)); + } + m.send(player, MessageKey.UNREGISTERED_SUCCESS); + ConsoleLogger.info(player.getDisplayName() + " unregistered himself"); + Utils.teleportToSpawn(player); + } else { + m.send(player, MessageKey.WRONG_PASSWORD); } } } diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index d84b6870..125e92b9 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -4,7 +4,6 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.PasswordEncryptionEvent; -import fr.xephi.authme.security.crypts.BCRYPT; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; @@ -20,9 +19,13 @@ public class PasswordSecurity { @Deprecated public static final HashMap userSalt = new HashMap<>(); private final DataSource dataSource; + private final HashAlgorithm algorithm; + private final boolean supportOldAlgorithm; - public PasswordSecurity(DataSource dataSource) { + public PasswordSecurity(DataSource dataSource, HashAlgorithm algorithm, boolean supportOldAlgorithm) { this.dataSource = dataSource; + this.algorithm = algorithm; + this.supportOldAlgorithm = supportOldAlgorithm; } @Deprecated @@ -32,96 +35,7 @@ public class PasswordSecurity { @Deprecated public static String getHash(HashAlgorithm alg, String password, String playerName) throws NoSuchAlgorithmException { - EncryptionMethod method; - try { - if (alg != HashAlgorithm.CUSTOM) - method = alg.getClazz().newInstance(); - else method = null; - } catch (InstantiationException | IllegalAccessException e) { - throw new NoSuchAlgorithmException("Problem with hash algorithm '" + alg + "'", e); - } - String salt = ""; - switch (alg) { - case SHA256: - salt = createSalt(16); - break; - case MD5VB: - salt = createSalt(16); - break; - case XAUTH: - salt = createSalt(12); - break; - case MYBB: - salt = createSalt(8); - userSalt.put(playerName, salt); - break; - case IPB3: - salt = createSalt(5); - userSalt.put(playerName, salt); - break; - case PHPFUSION: - salt = createSalt(12); - userSalt.put(playerName, salt); - break; - case SALTED2MD5: - salt = createSalt(Settings.saltLength); - userSalt.put(playerName, salt); - break; - case JOOMLA: - salt = createSalt(32); - userSalt.put(playerName, salt); - break; - case BCRYPT: - salt = BCRYPT.gensalt(Settings.bCryptLog2Rounds); - userSalt.put(playerName, salt); - break; - case WBB3: - salt = createSalt(40); - userSalt.put(playerName, salt); - break; - case WBB4: - salt = BCRYPT.gensalt(8); - userSalt.put(playerName, salt); - break; - case PBKDF2DJANGO: - case PBKDF2: - salt = createSalt(12); - userSalt.put(playerName, salt); - break; - case SMF: - return method.computeHash(password, null, playerName); - case PHPBB: - salt = createSalt(16); - userSalt.put(playerName, salt); - break; - case BCRYPT2Y: - salt = createSalt(16); - userSalt.put(playerName, salt); - break; - case SALTEDSHA512: - salt = createSalt(32); - userSalt.put(playerName, salt); - break; - case MD5: - case SHA1: - case WHIRLPOOL: - case PLAINTEXT: - case SHA512: - case ROYALAUTH: - case CRAZYCRYPT1: - case DOUBLEMD5: - case WORDPRESS: - case CUSTOM: - break; - default: - throw new NoSuchAlgorithmException("Unknown hash algorithm"); - } - PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName); - Bukkit.getPluginManager().callEvent(event); - method = event.getMethod(); - if (method == null) - throw new NoSuchAlgorithmException("Unknown hash algorithm"); - return method.computeHash(password, salt, playerName); + return ""; } @Deprecated @@ -167,11 +81,19 @@ public class PasswordSecurity { return false; } + public HashResult computeHash(String password, String playerName) { + return computeHash(algorithm, password, playerName); + } + public HashResult computeHash(HashAlgorithm algorithm, String password, String playerName) { EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName); return method.computeHash(password, playerName); } + public boolean comparePassword(String hash, String password, String playerName) { + return comparePassword(algorithm, hash, password, playerName); + } + public boolean comparePassword(HashAlgorithm algorithm, String hash, String password, String playerName) { EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName); String salt = null; diff --git a/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java b/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java index c5c7df3b..6e332ebd 100644 --- a/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java +++ b/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java @@ -1,20 +1,18 @@ package fr.xephi.authme.task; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; -import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; +import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; import org.bukkit.entity.Player; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; - -import java.security.NoSuchAlgorithmException; - public class ChangePasswordTask implements Runnable { private final AuthMe plugin; @@ -32,48 +30,41 @@ public class ChangePasswordTask implements Runnable { @Override public void run() { Messages m = plugin.getMessages(); - try { - final String name = player.getName().toLowerCase(); - String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, newPassword, name); - PlayerAuth auth = PlayerCache.getInstance().getAuth(name); - if (PasswordSecurity.comparePasswordWithHash(oldPassword, auth.getHash(), player.getName())) { - auth.setHash(hashNew); - if (PasswordSecurity.userSalt.containsKey(name) && PasswordSecurity.userSalt.get(name) != null) { - auth.setSalt(PasswordSecurity.userSalt.get(name)); - } else { - auth.setSalt(""); - } - if (!plugin.database.updatePassword(auth)) { - m.send(player, MessageKey.ERROR); - return; - } - plugin.database.updateSalt(auth); - PlayerCache.getInstance().updatePlayer(auth); - m.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS); - ConsoleLogger.info(player.getName() + " changed his password"); - if (Settings.bungee) - { - final String hash = auth.getHash(); - final String salt = auth.getSalt(); - plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable(){ + PasswordSecurity passwordSecurity = plugin.getPasswordSecurity(); - @Override - public void run() { - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - out.writeUTF("Forward"); - out.writeUTF("ALL"); - out.writeUTF("AuthMe"); - out.writeUTF("changepassword;" + name + ";" + hash + ";" + salt); - player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); - } - }); - } - } else { - m.send(player, MessageKey.WRONG_PASSWORD); + final String name = player.getName().toLowerCase(); + PlayerAuth auth = PlayerCache.getInstance().getAuth(name); + if (passwordSecurity.comparePassword(oldPassword, auth.getHash(), player.getName())) { + HashResult hashResult = passwordSecurity.computeHash(newPassword, name); + auth.setHash(hashResult.getHash()); + auth.setSalt(hashResult.getSalt()); + + if (!plugin.getDataSource().updatePassword(auth)) { + m.send(player, MessageKey.ERROR); + return; } - } catch (NoSuchAlgorithmException ex) { - ConsoleLogger.showError(ex.getMessage()); - m.send(player, MessageKey.ERROR); + plugin.getDataSource().updateSalt(auth); + PlayerCache.getInstance().updatePlayer(auth); + m.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS); + ConsoleLogger.info(player.getName() + " changed his password"); + if (Settings.bungee) { + final String hash = auth.getHash(); + final String salt = auth.getSalt(); + plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable(){ + + @Override + public void run() { + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("Forward"); + out.writeUTF("ALL"); + out.writeUTF("AuthMe"); + out.writeUTF("changepassword;" + name + ";" + hash + ";" + salt); + player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); + } + }); + } + } else { + m.send(player, MessageKey.WRONG_PASSWORD); } } } diff --git a/src/test/java/fr/xephi/authme/command/CommandServiceTest.java b/src/test/java/fr/xephi/authme/command/CommandServiceTest.java index 7780db7a..3b457291 100644 --- a/src/test/java/fr/xephi/authme/command/CommandServiceTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandServiceTest.java @@ -2,10 +2,12 @@ package fr.xephi.authme.command; import fr.xephi.authme.AuthMe; import fr.xephi.authme.command.help.HelpProvider; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.process.Management; +import fr.xephi.authme.security.PasswordSecurity; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.junit.Before; @@ -32,6 +34,7 @@ public class CommandServiceTest { private CommandMapper commandMapper; private HelpProvider helpProvider; private Messages messages; + private PasswordSecurity passwordSecurity; private CommandService commandService; @Before @@ -40,7 +43,8 @@ public class CommandServiceTest { commandMapper = mock(CommandMapper.class); helpProvider = mock(HelpProvider.class); messages = mock(Messages.class); - commandService = new CommandService(authMe, commandMapper, helpProvider, messages); + passwordSecurity = mock(PasswordSecurity.class); + commandService = new CommandService(authMe, commandMapper, helpProvider, messages, passwordSecurity); } @Test @@ -110,9 +114,25 @@ public class CommandServiceTest { } @Test - @Ignore public void shouldGetDataSource() { - // TODO ljacqu 20151226: Cannot mock calls to fields + // given + DataSource dataSource = mock(DataSource.class); + given(authMe.getDataSource()).willReturn(dataSource); + + // when + DataSource result = commandService.getDataSource(); + + // then + assertThat(result, equalTo(dataSource)); + } + + @Test + public void shouldGetPasswordSecurity() { + // given/when + PasswordSecurity passwordSecurity = commandService.getPasswordSecurity(); + + // then + assertThat(passwordSecurity, equalTo(this.passwordSecurity)); } @Test From 531327dd9be6d982c899fb4dfc3e415e9aac023a Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 29 Dec 2015 11:55:57 +0100 Subject: [PATCH 13/18] Minor - make AuthMe.database private - In favor of AuthMe.getDataSource() --- src/main/java/fr/xephi/authme/AuthMe.java | 2 +- src/main/java/fr/xephi/authme/api/API.java | 108 +++++++----------- .../authme/ChangePasswordAdminCommand.java | 7 +- .../authme/PurgeBannedPlayersCommand.java | 2 +- .../authme/converter/CrazyLoginConverter.java | 2 +- .../authme/converter/RoyalAuthConverter.java | 2 +- .../xephi/authme/converter/SqliteToSql.java | 8 +- .../authme/converter/vAuthFileReader.java | 2 +- .../xephi/authme/converter/xAuthToFlat.java | 2 +- .../xephi/authme/hooks/BungeeCordMessage.java | 24 ++-- .../authme/listener/AuthMePlayerListener.java | 24 ++-- .../fr/xephi/authme/process/Management.java | 10 +- .../process/email/AsyncChangeEmail.java | 6 +- .../unregister/AsynchronousUnregister.java | 20 +--- .../authme/security/PasswordSecurity.java | 18 +-- .../security/crypts/description/SaltType.java | 2 +- src/main/java/fr/xephi/authme/util/Utils.java | 17 +-- 17 files changed, 98 insertions(+), 158 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index ad488d69..7c5316aa 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -102,12 +102,12 @@ public class AuthMe extends JavaPlugin { private JsonCache playerBackup; private ModuleManager moduleManager; private PasswordSecurity passwordSecurity; + private DataSource database; // Public Instances public NewAPI api; public SendMailSSL mail; public DataManager dataManager; - public DataSource database; public OtherAccounts otherAccounts; public Location essentialsSpawn; diff --git a/src/main/java/fr/xephi/authme/api/API.java b/src/main/java/fr/xephi/authme/api/API.java index 1f1ac69d..09e5a423 100644 --- a/src/main/java/fr/xephi/authme/api/API.java +++ b/src/main/java/fr/xephi/authme/api/API.java @@ -4,6 +4,7 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.Utils; import org.bukkit.Bukkit; @@ -12,34 +13,37 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; -import java.security.NoSuchAlgorithmException; - /** + * Deprecated API of AuthMe. Please use {@link NewAPI} instead. */ +@Deprecated public class API { public static final String newline = System.getProperty("line.separator"); public static AuthMe instance; + private static PasswordSecurity passwordSecurity; /** - * Constructor for API. + * Constructor for the deprecated API. * * @param instance AuthMe */ @Deprecated public API(AuthMe instance) { API.instance = instance; + passwordSecurity = instance.getPasswordSecurity(); } /** - * Hook into AuthMe + * Hook into AuthMe. * * @return AuthMe instance */ @Deprecated public static AuthMe hookAuthMe() { - if (instance != null) + if (instance != null) { return instance; + } Plugin plugin = Bukkit.getServer().getPluginManager().getPlugin("AuthMe"); if (plugin == null || !(plugin instanceof AuthMe)) { return null; @@ -49,9 +53,10 @@ public class API { } /** - * @param player + * Return whether the player is authenticated. * - * @return true if player is authenticate + * @param player The player to verify + * @return true if the player is authenticated */ @Deprecated public static boolean isAuthenticated(Player player) { @@ -59,8 +64,9 @@ public class API { } /** - * @param player + * Return whether the player is unrestricted. * + * @param player The player to verify * @return true if the player is unrestricted */ @Deprecated @@ -68,13 +74,6 @@ public class API { return Utils.isUnrestricted(player); } - /** - * Method getLastLocation. - * - * @param player Player - * - * @return Location - */ @Deprecated public static Location getLastLocation(Player player) { try { @@ -92,13 +91,6 @@ public class API { } } - /** - * Method setPlayerInventory. - * - * @param player Player - * @param content ItemStack[] - * @param armor ItemStack[] - */ @Deprecated public static void setPlayerInventory(Player player, ItemStack[] content, ItemStack[] armor) { @@ -110,21 +102,23 @@ public class API { } /** - * @param playerName + * Check whether the given player name is registered. * + * @param playerName The player name to verify * @return true if player is registered */ @Deprecated public static boolean isRegistered(String playerName) { String player = playerName.toLowerCase(); - return instance.database.isAuthAvailable(player); + return instance.getDataSource().isAuthAvailable(player); } /** - * @param playerName String - * @param passwordToCheck String + * Check the password for the given player. * - * @return true if the password is correct , false else + * @param playerName The name of the player + * @param passwordToCheck The password to check + * @return true if the password is correct, false otherwise */ @Deprecated public static boolean checkPassword(String playerName, @@ -132,71 +126,53 @@ public class API { if (!isRegistered(playerName)) return false; String player = playerName.toLowerCase(); - PlayerAuth auth = instance.database.getAuth(player); - try { - return PasswordSecurity.comparePasswordWithHash(passwordToCheck, auth.getHash(), playerName); - } catch (NoSuchAlgorithmException e) { - return false; - } + PlayerAuth auth = instance.getDataSource().getAuth(player); + return passwordSecurity.comparePassword(passwordToCheck, auth.getHash(), playerName); } /** - * Register a player + * Register a player. * - * @param playerName String - * @param password String - * - * @return true if the player is register correctly + * @param playerName The name of the player + * @param password The password + * @return true if the player was registered correctly */ @Deprecated public static boolean registerPlayer(String playerName, String password) { - try { - String name = playerName.toLowerCase(); - String hash = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); - if (isRegistered(name)) { - return false; - } - PlayerAuth auth = new PlayerAuth(name, hash, "198.18.0.1", 0, "your@email.com", playerName); - return instance.database.saveAuth(auth); - } catch (NoSuchAlgorithmException ex) { + String name = playerName.toLowerCase(); + HashResult hashResult = passwordSecurity.computeHash(Settings.getPasswordHash, password, name); + if (isRegistered(name)) { return false; } + PlayerAuth auth = PlayerAuth.builder() + .name(name) + .hash(hashResult.getHash()) + .lastLogin(0) + .realName(playerName) + .build(); + return instance.getDataSource().saveAuth(auth); } /** - * Force a player to login + * Force a player to log in. * - * @param player * player + * @param player The player to log in */ @Deprecated public static void forceLogin(Player player) { instance.getManagement().performLogin(player, "dontneed", true); } - /** - * Method getPlugin. - * - * @return AuthMe - */ @Deprecated public AuthMe getPlugin() { return instance; } /** - * @param player + * Check whether the player is an NPC. * - * @return true if player is a npc - */ - @Deprecated - public boolean isaNPC(Player player) { - return Utils.isNPC(player); - } - - /** - * @param player - * - * @return true if player is a npc + * @param player The player to verify + * @return true if player is an npc */ @Deprecated public boolean isNPC(Player player) { diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java index 44d8967e..74954521 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java @@ -7,7 +7,6 @@ import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; import org.bukkit.command.CommandSender; @@ -72,10 +71,8 @@ public class ChangePasswordAdminCommand implements ExecutableCommand { HashResult hashResult = commandService.getPasswordSecurity().computeHash(playerPass, playerNameLowerCase); auth.setHash(hashResult.getHash()); auth.setSalt(hashResult.getSalt()); - if (PasswordSecurity.userSalt.containsKey(playerNameLowerCase)) { - auth.setSalt(PasswordSecurity.userSalt.get(playerNameLowerCase)); - commandService.getDataSource().updateSalt(auth); - } + + // TODO #358: updatePassword(auth) needs to update the salt, too. if (!dataSource.updatePassword(auth)) { commandService.send(sender, MessageKey.ERROR); } else { diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/PurgeBannedPlayersCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/PurgeBannedPlayersCommand.java index 01d6b38b..b94ce260 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/PurgeBannedPlayersCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/PurgeBannedPlayersCommand.java @@ -24,7 +24,7 @@ public class PurgeBannedPlayersCommand implements ExecutableCommand { } // Purge the banned players - plugin.database.purgeBanned(bannedPlayers); + plugin.getDataSource().purgeBanned(bannedPlayers); if (Settings.purgeEssentialsFile && plugin.ess != null) plugin.dataManager.purgeEssentials(bannedPlayers); if (Settings.purgePlayerDat) diff --git a/src/main/java/fr/xephi/authme/converter/CrazyLoginConverter.java b/src/main/java/fr/xephi/authme/converter/CrazyLoginConverter.java index dd592a1a..8b4ee564 100644 --- a/src/main/java/fr/xephi/authme/converter/CrazyLoginConverter.java +++ b/src/main/java/fr/xephi/authme/converter/CrazyLoginConverter.java @@ -27,7 +27,7 @@ public class CrazyLoginConverter implements Converter { * @param sender CommandSender */ public CrazyLoginConverter(AuthMe instance, CommandSender sender) { - this.database = instance.database; + this.database = instance.getDataSource(); this.sender = sender; } diff --git a/src/main/java/fr/xephi/authme/converter/RoyalAuthConverter.java b/src/main/java/fr/xephi/authme/converter/RoyalAuthConverter.java index 14d289fa..5e85ce6e 100644 --- a/src/main/java/fr/xephi/authme/converter/RoyalAuthConverter.java +++ b/src/main/java/fr/xephi/authme/converter/RoyalAuthConverter.java @@ -15,7 +15,7 @@ public class RoyalAuthConverter implements Converter { public RoyalAuthConverter(AuthMe plugin) { this.plugin = plugin; - this.data = plugin.database; + this.data = plugin.getDataSource(); } @Override diff --git a/src/main/java/fr/xephi/authme/converter/SqliteToSql.java b/src/main/java/fr/xephi/authme/converter/SqliteToSql.java index cb36f66e..37df161a 100644 --- a/src/main/java/fr/xephi/authme/converter/SqliteToSql.java +++ b/src/main/java/fr/xephi/authme/converter/SqliteToSql.java @@ -21,16 +21,14 @@ public class SqliteToSql implements Converter { @Override public void run() { - if (plugin.database.getType() != DataSourceType.MYSQL) - { + if (plugin.getDataSource().getType() != DataSourceType.MYSQL) { sender.sendMessage("Please config your mySQL connection and re-run this command"); return; } try { SQLite data = new SQLite(); - for (PlayerAuth auth : data.getAllAuths()) - { - plugin.database.saveAuth(auth); + for (PlayerAuth auth : data.getAllAuths()) { + plugin.getDataSource().saveAuth(auth); } } catch (Exception e) { sender.sendMessage(plugin.getMessages().retrieve(MessageKey.ERROR)); diff --git a/src/main/java/fr/xephi/authme/converter/vAuthFileReader.java b/src/main/java/fr/xephi/authme/converter/vAuthFileReader.java index a72c149d..16b89e5a 100644 --- a/src/main/java/fr/xephi/authme/converter/vAuthFileReader.java +++ b/src/main/java/fr/xephi/authme/converter/vAuthFileReader.java @@ -23,7 +23,7 @@ class vAuthFileReader { */ public vAuthFileReader(AuthMe plugin) { this.plugin = plugin; - this.database = plugin.database; + this.database = plugin.getDataSource(); } public void convert() { diff --git a/src/main/java/fr/xephi/authme/converter/xAuthToFlat.java b/src/main/java/fr/xephi/authme/converter/xAuthToFlat.java index 630d2c6f..52ee4999 100644 --- a/src/main/java/fr/xephi/authme/converter/xAuthToFlat.java +++ b/src/main/java/fr/xephi/authme/converter/xAuthToFlat.java @@ -30,7 +30,7 @@ class xAuthToFlat { */ public xAuthToFlat(AuthMe instance, CommandSender sender) { this.instance = instance; - this.database = instance.database; + this.database = instance.getDataSource(); this.sender = sender; } diff --git a/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java b/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java index f5d3e0ae..3202f45d 100644 --- a/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java +++ b/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java @@ -6,6 +6,7 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.datasource.DataSource; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; @@ -24,15 +25,6 @@ public class BungeeCordMessage implements PluginMessageListener { this.plugin = plugin; } - /** - * Method onPluginMessageReceived. - * - * @param channel String - * @param player Player - * @param message byte[] - * - * @see org.bukkit.plugin.messaging.PluginMessageListener#onPluginMessageReceived(String, Player, byte[]) - */ @Override public void onPluginMessageReceived(String channel, Player player, byte[] message) { if (!channel.equals("BungeeCord")) { @@ -50,21 +42,22 @@ public class BungeeCordMessage implements PluginMessageListener { final String[] args = str.split(";"); final String act = args[0]; final String name = args[1]; + final DataSource dataSource = plugin.getDataSource(); plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { @Override public void run() { - PlayerAuth auth = plugin.database.getAuth(name); + PlayerAuth auth = dataSource.getAuth(name); if (auth == null) { return; } if ("login".equals(act)) { PlayerCache.getInstance().updatePlayer(auth); - plugin.database.setLogged(name); + dataSource.setLogged(name); ConsoleLogger.info("Player " + auth.getNickname() + " has logged in from one of your server!"); } else if ("logout".equals(act)) { PlayerCache.getInstance().removePlayer(name); - plugin.database.setUnlogged(name); + dataSource.setUnlogged(name); ConsoleLogger.info("Player " + auth.getNickname() + " has logged out from one of your server!"); } else if ("register".equals(act)) { @@ -73,10 +66,11 @@ public class BungeeCordMessage implements PluginMessageListener { } else if ("changepassword".equals(act)) { final String password = args[2]; auth.setHash(password); - if (args.length == 4) - auth.setSalt(args[3]); + if (args.length == 4) { + auth.setSalt(args[3]); + } PlayerCache.getInstance().updatePlayer(auth); - plugin.database.updatePassword(auth); + dataSource.updatePassword(auth); } } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index 701308ee..a5a6931f 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -89,7 +89,7 @@ public class AuthMePlayerListener implements Listener { plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { @Override public void run() { - if (plugin.database.isAuthAvailable(player.getName().toLowerCase())) { + if (plugin.getDataSource().isAuthAvailable(player.getName().toLowerCase())) { m.send(player, MessageKey.LOGIN_MESSAGE); } else { if (Settings.emailRegistration) { @@ -221,8 +221,9 @@ public class AuthMePlayerListener implements Listener { @EventHandler(priority = EventPriority.HIGHEST) public void onPreLogin(AsyncPlayerPreLoginEvent event) { - PlayerAuth auth = plugin.database.getAuth(event.getName()); - if (auth != null && auth.getRealName() != null && !auth.getRealName().isEmpty() && !auth.getRealName().equals("Player") && !auth.getRealName().equals(event.getName())) { + PlayerAuth auth = plugin.getDataSource().getAuth(event.getName()); + if (auth != null && auth.getRealName() != null && !auth.getRealName().isEmpty() && + !auth.getRealName().equals("Player") && !auth.getRealName().equals(event.getName())) { event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); event.setKickMessage("You should join using username: " + ChatColor.AQUA + auth.getRealName() + ChatColor.RESET + "\nnot: " + ChatColor.RED + event.getName()); // TODO: write a better message @@ -231,7 +232,7 @@ public class AuthMePlayerListener implements Listener { if (auth != null && auth.getRealName().equals("Player")) { auth.setRealName(event.getName()); - plugin.database.saveAuth(auth); + plugin.getDataSource().saveAuth(auth); } if (auth == null && Settings.enableProtection) { @@ -302,7 +303,7 @@ public class AuthMePlayerListener implements Listener { } final String name = player.getName().toLowerCase(); - boolean isAuthAvailable = plugin.database.isAuthAvailable(name); + boolean isAuthAvailable = plugin.getDataSource().isAuthAvailable(name); if (Settings.isKickNonRegisteredEnabled && !isAuthAvailable) { if (Settings.antiBotInAction) { @@ -475,9 +476,16 @@ public class AuthMePlayerListener implements Listener { Player player = event.getPlayer(); String name = player.getName().toLowerCase(); Location spawn = plugin.getSpawnLocation(player); - if (Settings.isSaveQuitLocationEnabled && plugin.database.isAuthAvailable(name)) { - PlayerAuth auth = new PlayerAuth(name, spawn.getX(), spawn.getY(), spawn.getZ(), spawn.getWorld().getName(), player.getName()); - plugin.database.updateQuitLoc(auth); + if (Settings.isSaveQuitLocationEnabled && plugin.getDataSource().isAuthAvailable(name)) { + PlayerAuth auth = PlayerAuth.builder() + .name(name) + .realName(player.getName()) + .locX(spawn.getX()) + .locY(spawn.getY()) + .locZ(spawn.getZ()) + .locWorld(spawn.getWorld().getName()) + .build(); + plugin.getDataSource().updateQuitLoc(auth); } if (spawn != null && spawn.getWorld() != null) { event.setRespawnLocation(spawn); diff --git a/src/main/java/fr/xephi/authme/process/Management.java b/src/main/java/fr/xephi/authme/process/Management.java index 58ce248f..6d262308 100644 --- a/src/main/java/fr/xephi/authme/process/Management.java +++ b/src/main/java/fr/xephi/authme/process/Management.java @@ -33,7 +33,7 @@ public class Management { @Override public void run() { - new AsynchronousLogin(player, password, forceLogin, plugin, plugin.database).process(); + new AsynchronousLogin(player, password, forceLogin, plugin, plugin.getDataSource()).process(); } }); } @@ -43,7 +43,7 @@ public class Management { @Override public void run() { - new AsynchronousLogout(player, plugin, plugin.database).process(); + new AsynchronousLogout(player, plugin, plugin.getDataSource()).process(); } }); } @@ -53,7 +53,7 @@ public class Management { @Override public void run() { - new AsyncRegister(player, password, email, plugin, plugin.database).process(); + new AsyncRegister(player, password, email, plugin, plugin.getDataSource()).process(); } }); } @@ -73,7 +73,7 @@ public class Management { @Override public void run() { - new AsynchronousJoin(player, plugin, plugin.database).process(); + new AsynchronousJoin(player, plugin, plugin.getDataSource()).process(); } }); @@ -84,7 +84,7 @@ public class Management { @Override public void run() { - new AsynchronousQuit(player, plugin, plugin.database, isKick).process(); + new AsynchronousQuit(player, plugin, plugin.getDataSource(), isKick).process(); } }); diff --git a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java index 214b8215..7208f60a 100644 --- a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java +++ b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java @@ -40,7 +40,7 @@ public class AsyncChangeEmail { if (Settings.getmaxRegPerEmail > 0) { if (!plugin.getPermissionsManager().hasPermission(player, PlayerPermission.ALLOW_MULTIPLE_ACCOUNTS) - && plugin.database.getAllAuthsByEmail(newEmail).size() >= Settings.getmaxRegPerEmail) { + && plugin.getDataSource().getAllAuthsByEmail(newEmail).size() >= Settings.getmaxRegPerEmail) { m.send(player, MessageKey.MAX_REGISTER_EXCEEDED); return; } @@ -68,7 +68,7 @@ public class AsyncChangeEmail { } String old = auth.getEmail(); auth.setEmail(newEmail); - if (!plugin.database.updateEmail(auth)) { + if (!plugin.getDataSource().updateEmail(auth)) { m.send(player, MessageKey.ERROR); auth.setEmail(old); return; @@ -81,7 +81,7 @@ public class AsyncChangeEmail { } m.send(player, MessageKey.EMAIL_CHANGED_SUCCESS); } else { - if (plugin.database.isAuthAvailable(playerName)) { + if (plugin.getDataSource().isAuthAvailable(playerName)) { m.send(player, MessageKey.LOGIN_MESSAGE); } else { if (Settings.emailRegistration) { diff --git a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java index 83b8cf7e..ed9ad468 100644 --- a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java +++ b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java @@ -6,7 +6,6 @@ import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.backup.JsonCache; import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.cache.limbo.LimboPlayer; -import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.settings.Settings; @@ -20,16 +19,12 @@ import org.bukkit.potion.PotionEffectType; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitTask; -import java.security.NoSuchAlgorithmException; - -/** - */ public class AsynchronousUnregister { - protected final Player player; - protected final String name; - protected final String password; - protected final boolean force; + private final Player player; + private final String name; + private final String password; + private final boolean force; private final AuthMe plugin; private final Messages m; private final JsonCache playerCache; @@ -52,18 +47,13 @@ public class AsynchronousUnregister { this.playerCache = new JsonCache(); } - /** - * Method getIp. - * - * @return String - */ protected String getIp() { return plugin.getIP(player); } public void process() { if (force || plugin.getPasswordSecurity().comparePassword(password, PlayerCache.getInstance().getAuth(name).getHash(), player.getName())) { - if (!plugin.database.removeAuth(name)) { + if (!plugin.getDataSource().removeAuth(name)) { m.send(player, MessageKey.ERROR); return; } diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index 125e92b9..68a59015 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -28,16 +28,6 @@ public class PasswordSecurity { this.supportOldAlgorithm = supportOldAlgorithm; } - @Deprecated - public static String createSalt(int length) { - return RandomString.generateHex(length); - } - - @Deprecated - public static String getHash(HashAlgorithm alg, String password, String playerName) throws NoSuchAlgorithmException { - return ""; - } - @Deprecated public static boolean comparePasswordWithHash(String password, String hash, String playerName) throws NoSuchAlgorithmException { @@ -142,12 +132,12 @@ public class PasswordSecurity { try { EncryptionMethod method = algo.getClazz().newInstance(); if (method.comparePassword(hash, password, salt, playerName)) { - PlayerAuth nAuth = AuthMe.getInstance().database.getAuth(playerName); + PlayerAuth nAuth = AuthMe.getInstance().getDataSource().getAuth(playerName); if (nAuth != null) { - nAuth.setHash(getHash(Settings.getPasswordHash, password, playerName)); + // nAuth.setHash(getHash(Settings.getPasswordHash, password, playerName)); nAuth.setSalt(userSalt.containsKey(playerName) ? userSalt.get(playerName) : ""); - AuthMe.getInstance().database.updatePassword(nAuth); - AuthMe.getInstance().database.updateSalt(nAuth); + AuthMe.getInstance().getDataSource().updatePassword(nAuth); + AuthMe.getInstance().getDataSource().updateSalt(nAuth); } return true; } diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/SaltType.java b/src/main/java/fr/xephi/authme/security/crypts/description/SaltType.java index f91ca61c..7d6b225c 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/description/SaltType.java +++ b/src/main/java/fr/xephi/authme/security/crypts/description/SaltType.java @@ -1,7 +1,7 @@ package fr.xephi.authme.security.crypts.description; /** - * The type of salt used by an encryption algorithms. + * The type of salt used by an encryption algorithm. */ public enum SaltType { diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 1388917d..bab7c7ee 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -143,13 +143,9 @@ public final class Utils { return true; } - if (!Settings.isForcedRegistrationEnabled) { - // TODO ljacqu 20151123: Use a getter to retrieve things from AuthMe - if (!plugin.database.isAuthAvailable(player.getName())) { - return true; - } + if (!Settings.isForcedRegistrationEnabled && !plugin.getDataSource().isAuthAvailable(player.getName())) { + return true; } - return false; } @@ -159,15 +155,6 @@ public final class Utils { && (Settings.getUnrestrictedName.contains(player.getName().toLowerCase())); } - /** - * Method packCoords. - * - * @param x double - * @param y double - * @param z double - * @param w String - * @param pl Player - */ public static void packCoords(double x, double y, double z, String w, final Player pl) { World theWorld; if (w.equals("unavailableworld")) { From 922082f3126a2b34ae468263fc87125464c52eb2 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 29 Dec 2015 13:29:26 +0100 Subject: [PATCH 14/18] #364 Add HashAlgorithm integration test, fix failing tests - Create integration test for the HashAlgorithm enum - Create AsciiRestricted annotation and make test aware of it - Add option to skip "same hash for same salt" test (for wordpress) - Change some EncryptionMethods to extend from a common superclass --- .../xephi/authme/security/crypts/BCRYPT.java | 2 +- .../security/crypts/CryptPBKDF2Django.java | 2 + .../fr/xephi/authme/security/crypts/IPB3.java | 17 +----- .../fr/xephi/authme/security/crypts/MYBB.java | 17 +----- .../xephi/authme/security/crypts/PHPBB.java | 13 ++-- .../authme/security/crypts/PHPFUSION.java | 19 +----- .../authme/security/crypts/SALTED2MD5.java | 6 -- .../crypts/description/AsciiRestricted.java | 15 +++++ .../HashAlgorithmIntegrationTest.java | 61 +++++++++++++++++++ .../crypts/AbstractEncryptionMethodTest.java | 29 ++++++++- .../crypts/CryptPBKDF2DjangoTest.java | 4 -- .../authme/security/crypts/PHPFUSIONTest.java | 4 -- .../authme/security/crypts/WORDPRESSTest.java | 10 +-- 13 files changed, 123 insertions(+), 76 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/security/crypts/description/AsciiRestricted.java create mode 100644 src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java 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 b85a83df..3c0bc785 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java @@ -522,7 +522,7 @@ public class BCRYPT implements EncryptionMethod { @Override public HashResult computeHash(String password, String name) { String salt = generateSalt(); - return new HashResult(hashpw(password, salt), salt); + return new HashResult(hashpw(password, salt), null); } @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java index dd20c1de..3c9b5d60 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java @@ -1,10 +1,12 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.security.crypts.description.AsciiRestricted; import fr.xephi.authme.security.pbkdf2.PBKDF2Engine; import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters; import javax.xml.bind.DatatypeConverter; +@AsciiRestricted public class CryptPBKDF2Django extends HexSaltedMethod { @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java index b89c8f32..85789b88 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java @@ -10,31 +10,16 @@ import static fr.xephi.authme.security.HashUtils.md5; @Recommendation(Usage.DO_NOT_USE) @HasSalt(value = SaltType.TEXT, length = 5) -public class IPB3 implements EncryptionMethod { +public class IPB3 extends SeparateSaltMethod { @Override public String computeHash(String password, String salt, String name) { return md5(md5(salt) + md5(password)); } - @Override - public HashResult computeHash(String password, String name) { - String salt = generateSalt(); - return new HashResult(computeHash(password, salt, name), salt); - } - - @Override - public boolean comparePassword(String hash, String password, String salt, String name) { - return hash.equals(computeHash(password, salt, name)); - } - @Override public String generateSalt() { return RandomString.generateHex(5); } - @Override - public boolean hasSeparateSalt() { - return true; - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java index fe4b287e..97418640 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java @@ -4,31 +4,16 @@ import fr.xephi.authme.security.RandomString; import static fr.xephi.authme.security.HashUtils.md5; -public class MYBB implements EncryptionMethod { +public class MYBB extends SeparateSaltMethod { @Override public String computeHash(String password, String salt, String name) { return md5(md5(salt) + md5(password)); } - @Override - public HashResult computeHash(String password, String name) { - String salt = generateSalt(); - return new HashResult(computeHash(password, salt, name), salt); - } - - @Override - public boolean comparePassword(String hash, String password, String salt, String name) { - return hash.equals(computeHash(password, salt, name)); - } - @Override public String generateSalt() { return RandomString.generateHex(8); } - @Override - public boolean hasSeparateSalt() { - return true; - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java index cd039615..753f419a 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java @@ -4,10 +4,10 @@ */ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.security.RandomString; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.MessageDigestAlgorithm; import java.io.UnsupportedEncodingException; -import java.security.GeneralSecurityException; import java.security.MessageDigest; /** @@ -20,10 +20,10 @@ public class PHPBB extends HexSaltedMethod { private static String md5(String data) { try { byte[] bytes = data.getBytes("ISO-8859-1"); - MessageDigest md5er = MessageDigest.getInstance("MD5"); + MessageDigest md5er = HashUtils.getDigest(MessageDigestAlgorithm.MD5); byte[] hash = md5er.digest(bytes); return bytes2hex(hash); - } catch (GeneralSecurityException | UnsupportedEncodingException e) { + } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } @@ -132,9 +132,10 @@ public class PHPBB extends HexSaltedMethod { } private boolean phpbb_check_hash(String password, String hash) { - if (hash.length() == 34) + if (hash.length() == 34) { return _hash_crypt_private(password, hash).equals(hash); - else return md5(password).equals(hash); + } + return md5(password).equals(hash); } @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java index 596f994e..2950fe67 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java @@ -2,6 +2,7 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; import fr.xephi.authme.security.RandomString; +import fr.xephi.authme.security.crypts.description.AsciiRestricted; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; @@ -12,7 +13,8 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @Recommendation(Usage.DO_NOT_USE) -public class PHPFUSION implements EncryptionMethod { +@AsciiRestricted +public class PHPFUSION extends SeparateSaltMethod { @Override public String computeHash(String password, String salt, String name) { @@ -37,25 +39,10 @@ public class PHPFUSION implements EncryptionMethod { } } - @Override - public HashResult computeHash(String password, String name) { - String salt = generateSalt(); - return new HashResult(computeHash(password, salt, name), salt); - } - - @Override - public boolean comparePassword(String hash, String password, String salt, String name) { - return hash.equals(computeHash(password, salt, name)); - } - @Override public String generateSalt() { return RandomString.generateHex(12); } - @Override - public boolean hasSeparateSalt() { - return true; - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java index 331c33fa..613bcd3f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java @@ -1,7 +1,5 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.security.HashUtils; import fr.xephi.authme.security.RandomString; import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; @@ -9,10 +7,6 @@ import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; import fr.xephi.authme.settings.Settings; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - import static fr.xephi.authme.security.HashUtils.md5; @Recommendation(Usage.ACCEPTABLE) // presuming that length is something sensible (>= 8) diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/AsciiRestricted.java b/src/main/java/fr/xephi/authme/security/crypts/description/AsciiRestricted.java new file mode 100644 index 00000000..f515717e --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/description/AsciiRestricted.java @@ -0,0 +1,15 @@ +package fr.xephi.authme.security.crypts.description; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Denotes an encryption algorithm that is restricted to the ASCII charset. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface AsciiRestricted { + +} diff --git a/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java b/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java new file mode 100644 index 00000000..35e3b577 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java @@ -0,0 +1,61 @@ +package fr.xephi.authme.security; + +import fr.xephi.authme.security.crypts.EncryptionMethod; +import fr.xephi.authme.security.crypts.HashResult; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.util.StringUtils; +import fr.xephi.authme.util.WrapperMock; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * Integration test for {@link HashAlgorithm}. + */ +public class HashAlgorithmIntegrationTest { + + @BeforeClass + public static void setUpWrapper() { + WrapperMock.createInstance(); + Settings.bCryptLog2Rounds = 8; + Settings.saltLength = 16; + } + + @Test + public void shouldHaveUniqueClassForEntries() { + // given + Set> classes = new HashSet<>(); + + // when / then + for (HashAlgorithm algorithm : HashAlgorithm.values()) { + if (!HashAlgorithm.CUSTOM.equals(algorithm)) { + if (classes.contains(algorithm.getClazz())) { + fail("Found class '" + algorithm.getClazz() + "' twice!"); + } + classes.add(algorithm.getClazz()); + } + } + } + + @Test + public void shouldBeAbleToInstantiateEncryptionAlgorithms() throws InstantiationException, IllegalAccessException { + // given / when / then + for (HashAlgorithm algorithm : HashAlgorithm.values()) { + if (!HashAlgorithm.CUSTOM.equals(algorithm)) { + EncryptionMethod method = algorithm.getClazz().newInstance(); + HashResult hashResult = method.computeHash("pwd", "name"); + assertThat("Salt should not be null if method.hasSeparateSalt(), and vice versa. Method: '" + + method + "'", StringUtils.isEmpty(hashResult.getSalt()), equalTo(!method.hasSeparateSalt())); + assertThat("Hash should not be empty for method '" + method + "'", + StringUtils.isEmpty(hashResult.getHash()), equalTo(false)); + } + } + } + +} diff --git a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java index a6d8163d..ad3d183f 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java @@ -1,8 +1,11 @@ package fr.xephi.authme.security.crypts; +import com.google.common.collect.ImmutableList; +import fr.xephi.authme.security.crypts.description.AsciiRestricted; import org.junit.Test; import java.util.HashMap; +import java.util.List; import java.util.Map; import static org.hamcrest.Matchers.equalTo; @@ -27,7 +30,8 @@ public abstract class AbstractEncryptionMethodTest { * List of passwords that are hashed at runtime and then tested against; this verifies that hashes that are * generated are valid. */ - private static final String[] INTERNAL_PASSWORDS = {"test1234", "Ab_C73", "(!#&$~`_-Aa0", "Ûïé1&?+A"}; + private static final List INTERNAL_PASSWORDS = + ImmutableList.of("test1234", "Ab_C73", "(!#&$~`_-Aa0", "Ûïé1&?+A"); /** The encryption method to test. */ private EncryptionMethod method; @@ -97,12 +101,19 @@ public abstract class AbstractEncryptionMethodTest { @Test public void testPasswordEquality() { - for (String password : INTERNAL_PASSWORDS) { + List internalPasswords = method.getClass().isAnnotationPresent(AsciiRestricted.class) + ? INTERNAL_PASSWORDS.subList(0, INTERNAL_PASSWORDS.size() - 1) + : INTERNAL_PASSWORDS; + + for (String password : internalPasswords) { final String salt = method.generateSalt(); final String hash = method.computeHash(password, salt, USERNAME); // Check that the computeHash(password, salt, name) method has the same output for the returned salt - assertThat(hash, equalTo(method.computeHash(password, salt, USERNAME))); + if (testHashEqualityForSameSalt()) { + assertThat("Computing a hash with the same salt will generate the same hash", + hash, equalTo(method.computeHash(password, salt, USERNAME))); + } assertTrue("Generated hash for '" + password + "' should match password (hash = '" + hash + "')", method.comparePassword(hash, password, salt, USERNAME)); @@ -151,4 +162,16 @@ public abstract class AbstractEncryptionMethodTest { System.out.println("\n}"); } + /** + * Return whether an encryption algorithm should be tested that it generates the same + * hash for the same salt. If {@code true}, we call {@link EncryptionMethod#computeHash(String, String)} + * and verify that {@link EncryptionMethod#computeHash(String, String, String)} generates + * the same hash for the salt returned in the first call. + * + * @return Whether or not to test that the hash is the same for the same salt + */ + protected boolean testHashEqualityForSameSalt() { + return true; + } + } diff --git a/src/test/java/fr/xephi/authme/security/crypts/CryptPBKDF2DjangoTest.java b/src/test/java/fr/xephi/authme/security/crypts/CryptPBKDF2DjangoTest.java index f2f22edf..afff6bf8 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/CryptPBKDF2DjangoTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/CryptPBKDF2DjangoTest.java @@ -1,12 +1,8 @@ package fr.xephi.authme.security.crypts; -import org.junit.Ignore; - /** * Test for {@link CryptPBKDF2Django}. */ -@Ignore -// TODO ljacqu 20151220: testPasswordEquality fails - password matches hash for uppercase password...? public class CryptPBKDF2DjangoTest extends AbstractEncryptionMethodTest { public CryptPBKDF2DjangoTest() { diff --git a/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java b/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java index 828f6d48..fe2259d2 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java @@ -1,12 +1,8 @@ package fr.xephi.authme.security.crypts; -import org.junit.Ignore; - /** * Test for {@link PHPFUSION}. */ -@Ignore -// TODO #364: Need to skip lowercase/uppercase password test for the non-ASCII one public class PHPFUSIONTest extends AbstractEncryptionMethodTest { public PHPFUSIONTest() { diff --git a/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java b/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java index 07165934..49a16d65 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java @@ -1,12 +1,8 @@ package fr.xephi.authme.security.crypts; -import org.junit.Ignore; - /** * Test for {@link WORDPRESS}. */ -@Ignore -// TODO #364: Need to skip an assertion due to the "internal salt" of Wordpress public class WORDPRESSTest extends AbstractEncryptionMethodTest { public WORDPRESSTest() { @@ -16,4 +12,10 @@ public class WORDPRESSTest extends AbstractEncryptionMethodTest { "$P$BjzPjjzPjrAOyB1V0WFdpisgCTFx.N/", // &^%te$t?Pw@_ "$P$BjzPjxxyjp2QdKcab/oTW8l/W0AgE21"); // âË_3(íù* } + + @Override + protected boolean testHashEqualityForSameSalt() { + // We need to skip the test because Wordpress uses an "internal salt" that is not exposed to the outside + return false; + } } From 9c4a578bec1de662d692567ea6b490197c5b9f93 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Wed, 30 Dec 2015 15:43:25 +0100 Subject: [PATCH 15/18] #358 Remove old methods on PasswordSecurity, unify hash + salt - For encryption methods with a separate salt, the hash is useless without the salt, so hash and salt should always be persisted and retrieved together --- src/main/java/fr/xephi/authme/api/API.java | 9 +- src/main/java/fr/xephi/authme/api/NewAPI.java | 13 +- .../process/login/AsynchronousLogin.java | 6 +- .../unregister/AsynchronousUnregister.java | 5 +- .../authme/security/PasswordSecurity.java | 182 ++++++++---------- .../authme/security/crypts/WORDPRESS.java | 3 +- .../xephi/authme/task/ChangePasswordTask.java | 2 +- 7 files changed, 95 insertions(+), 125 deletions(-) diff --git a/src/main/java/fr/xephi/authme/api/API.java b/src/main/java/fr/xephi/authme/api/API.java index 09e5a423..af96bf96 100644 --- a/src/main/java/fr/xephi/authme/api/API.java +++ b/src/main/java/fr/xephi/authme/api/API.java @@ -121,13 +121,8 @@ public class API { * @return true if the password is correct, false otherwise */ @Deprecated - public static boolean checkPassword(String playerName, - String passwordToCheck) { - if (!isRegistered(playerName)) - return false; - String player = playerName.toLowerCase(); - PlayerAuth auth = instance.getDataSource().getAuth(player); - return passwordSecurity.comparePassword(passwordToCheck, auth.getHash(), playerName); + public static boolean checkPassword(String playerName, String passwordToCheck) { + return isRegistered(playerName) && passwordSecurity.comparePassword(passwordToCheck, playerName); } /** diff --git a/src/main/java/fr/xephi/authme/api/NewAPI.java b/src/main/java/fr/xephi/authme/api/NewAPI.java index 09d47b0c..ee48d5d6 100644 --- a/src/main/java/fr/xephi/authme/api/NewAPI.java +++ b/src/main/java/fr/xephi/authme/api/NewAPI.java @@ -116,9 +116,11 @@ public class NewAPI { } /** - * @param playerName + * Return whether the player is registered. * - * @return true if player is registered + * @param playerName The player name to check + * + * @return true if player is registered, false otherwise */ public boolean isRegistered(String playerName) { String player = playerName.toLowerCase(); @@ -134,12 +136,7 @@ public class NewAPI { * @return true if the password is correct, false otherwise */ public boolean checkPassword(String playerName, String passwordToCheck) { - if (!isRegistered(playerName)) { - return false; - } - String player = playerName.toLowerCase(); - PlayerAuth auth = plugin.getDataSource().getAuth(player); - return plugin.getPasswordSecurity().comparePassword(passwordToCheck, auth.getHash(), playerName); + return isRegistered(playerName) && plugin.getPasswordSecurity().comparePassword(passwordToCheck, playerName); } /** diff --git a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java index 17138cc6..0d99d04c 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -136,9 +136,9 @@ public class AsynchronousLogin { if (pAuth == null || needsCaptcha()) return; - String hash = pAuth.getHash(); String email = pAuth.getEmail(); - boolean passwordVerified = forceLogin || plugin.getPasswordSecurity().comparePassword(password, hash, realName); + boolean passwordVerified = forceLogin || plugin.getPasswordSecurity() + .comparePassword(password, pAuth.getHash(), pAuth.getSalt(), realName); if (passwordVerified && player.isOnline()) { PlayerAuth auth = PlayerAuth.builder() @@ -147,7 +147,7 @@ public class AsynchronousLogin { .ip(getIP()) .lastLogin(new Date().getTime()) .email(email) - .hash(hash) + .hash(pAuth.getHash()) .salt(pAuth.getSalt()) .build(); database.updateSession(auth); diff --git a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java index ed9ad468..1545552e 100644 --- a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java +++ b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java @@ -2,6 +2,7 @@ package fr.xephi.authme.process.unregister; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.backup.JsonCache; import fr.xephi.authme.cache.limbo.LimboCache; @@ -52,7 +53,9 @@ public class AsynchronousUnregister { } public void process() { - if (force || plugin.getPasswordSecurity().comparePassword(password, PlayerCache.getInstance().getAuth(name).getHash(), player.getName())) { + PlayerAuth cachedAuth = PlayerCache.getInstance().getAuth(name); + if (force || plugin.getPasswordSecurity().comparePassword( + password, cachedAuth.getHash(), cachedAuth.getSalt(), player.getName())) { if (!plugin.getDataSource().removeAuth(name)) { m.send(player, MessageKey.ERROR); return; diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index 68a59015..678396b5 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -1,23 +1,19 @@ package fr.xephi.authme.security; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.PasswordEncryptionEvent; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.HashResult; -import fr.xephi.authme.settings.Settings; import org.bukkit.Bukkit; -import java.security.NoSuchAlgorithmException; import java.util.HashMap; /** + * Manager class for password-related operations. */ public class PasswordSecurity { - @Deprecated - public static final HashMap userSalt = new HashMap<>(); private final DataSource dataSource; private final HashAlgorithm algorithm; private final boolean supportOldAlgorithm; @@ -28,49 +24,6 @@ public class PasswordSecurity { this.supportOldAlgorithm = supportOldAlgorithm; } - @Deprecated - public static boolean comparePasswordWithHash(String password, String hash, - String playerName) throws NoSuchAlgorithmException { - HashAlgorithm algorithm = Settings.getPasswordHash; - EncryptionMethod method; - try { - if (algorithm != HashAlgorithm.CUSTOM) { - method = algorithm.getClazz().newInstance(); - } else { - method = null; - } - - PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName); - Bukkit.getPluginManager().callEvent(event); - method = event.getMethod(); - - if (method == null) - throw new NoSuchAlgorithmException("Unknown hash algorithm"); - - String salt = null; - if (method.hasSeparateSalt()) { - PlayerAuth auth = AuthMe.getInstance().getDataSource().getAuth(playerName); - if (auth == null) { - // User is not in data source, so the result will invariably be wrong because an encryption - // method with hasSeparateSalt() == true NEEDS the salt to evaluate the password - return false; - } - salt = auth.getSalt(); - } - - if (method.comparePassword(hash, password, salt, playerName)) - return true; - - if (Settings.supportOldPassword) { - if (compareWithAllEncryptionMethod(password, hash, playerName)) - return true; - } - } catch (InstantiationException | IllegalAccessException e) { - throw new NoSuchAlgorithmException("Problem with this hash algorithm"); - } - return false; - } - public HashResult computeHash(String password, String playerName) { return computeHash(algorithm, password, playerName); } @@ -80,71 +33,92 @@ public class PasswordSecurity { return method.computeHash(password, playerName); } - public boolean comparePassword(String hash, String password, String playerName) { - return comparePassword(algorithm, hash, password, playerName); + public boolean comparePassword(String password, String playerName) { + PlayerAuth auth = dataSource.getAuth(playerName); + if (auth != null) { + return comparePassword(auth.getHash(), auth.getSalt(), password, playerName); + } + return false; } - public boolean comparePassword(HashAlgorithm algorithm, String hash, String password, String playerName) { + public boolean comparePassword(String hash, String salt, String password, String playerName) { EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName); - String salt = null; - if (method.hasSeparateSalt()) { - PlayerAuth auth = dataSource.getAuth(playerName); - if (auth == null) { - // User is not in data source, so the result will invariably be wrong because an encryption - // method with hasSeparateSalt() == true NEEDS the salt to evaluate the password - return false; - } - salt = auth.getSalt(); + // User is not in data source, so the result will invariably be wrong because an encryption + // method with hasSeparateSalt() == true NEEDS the salt to evaluate the password + if (method.hasSeparateSalt() && salt == null) { + return false; } - return method.comparePassword(hash, password, salt, playerName); - // TODO #358: Add logic for Settings.supportOldPassword + return method.comparePassword(hash, password, salt, playerName) + || supportOldAlgorithm && compareWithAllEncryptionMethods(password, hash, salt, playerName); } - private EncryptionMethod initializeEncryptionMethod(HashAlgorithm algorithm, String playerName) { - EncryptionMethod method; - try { - method = HashAlgorithm.CUSTOM.equals(algorithm) - ? null - : algorithm.getClazz().newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - throw new IllegalStateException("Constructor for '" + algorithm.getClazz() - + "' could not be invoked. (Is there no default constructor?)", e); - } - - PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName); - Bukkit.getPluginManager().callEvent(event); - return event.getMethod(); - } - - @Deprecated - private static boolean compareWithAllEncryptionMethod(String password, - String hash, String playerName) { - String salt; - PlayerAuth auth = AuthMe.getInstance().getDataSource().getAuth(playerName); - if (auth == null) { - salt = null; - } else { - salt = auth.getSalt(); - } - - for (HashAlgorithm algo : HashAlgorithm.values()) { - if (algo != HashAlgorithm.CUSTOM) { - try { - EncryptionMethod method = algo.getClazz().newInstance(); - if (method.comparePassword(hash, password, salt, playerName)) { - PlayerAuth nAuth = AuthMe.getInstance().getDataSource().getAuth(playerName); - if (nAuth != null) { - // nAuth.setHash(getHash(Settings.getPasswordHash, password, playerName)); - nAuth.setSalt(userSalt.containsKey(playerName) ? userSalt.get(playerName) : ""); - AuthMe.getInstance().getDataSource().updatePassword(nAuth); - AuthMe.getInstance().getDataSource().updateSalt(nAuth); - } - return true; - } - } catch (Exception ignored) { + /** + * Compare the given hash with all available encryption methods to support the migration to a new encryption method. + * + * @param password The clear-text password to check + * @param hash The hash to text the password against + * @param salt The salt (or null if none available) + * @param playerName The name of the player + * @return True if the + */ + private boolean compareWithAllEncryptionMethods(String password, String hash, String salt, String playerName) { + for (HashAlgorithm algorithm : HashAlgorithm.values()) { + if (!HashAlgorithm.CUSTOM.equals(algorithm)) { + EncryptionMethod method = initializeEncryptionMethodWithoutEvent(algorithm); + if (method != null && method.comparePassword(hash, password, salt, playerName)) { + hashPasswordForNewAlgorithm(password, playerName); + return true; } } } return false; } + + /** + * Get the encryption method from the given {@link HashAlgorithm} value and emits a + * {@link PasswordEncryptionEvent}. The encryption method from the event is returned, + * which may have been changed by an external listener. + * + * @param algorithm The algorithm to retrieve the encryption method for + * @param playerName The name of the player a password will be hashed for + * @return The encryption method + */ + private static EncryptionMethod initializeEncryptionMethod(HashAlgorithm algorithm, String playerName) { + EncryptionMethod method = initializeEncryptionMethodWithoutEvent(algorithm); + PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName); + Bukkit.getPluginManager().callEvent(event); + return event.getMethod(); + } + + /** + * Initialize the encryption method corresponding to the given hash algorithm. + * + * @param algorithm The algorithm to retrieve the encryption method for + * @return The associated encryption method + */ + private static EncryptionMethod initializeEncryptionMethodWithoutEvent(HashAlgorithm algorithm) { + try { + return HashAlgorithm.CUSTOM.equals(algorithm) + ? null + : algorithm.getClazz().newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new UnsupportedOperationException("Constructor for '" + algorithm.getClazz().getSimpleName() + + "' could not be invoked. (Is there no default constructor?)", e); + } + } + + private void hashPasswordForNewAlgorithm(String password, String playerName) { + PlayerAuth auth = dataSource.getAuth(playerName); + if (auth != null) { + HashResult hashResult = initializeEncryptionMethod(algorithm, playerName) + .computeHash(password, playerName); + + // TODO #358: updatePassword() should just take the HashResult..., or at least hash & salt. Idem for setHash + auth.setSalt(hashResult.getSalt()); + auth.setHash(hashResult.getHash()); + dataSource.updatePassword(auth); + dataSource.updateSalt(auth); + } + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java index 7d06aac6..d1e6a9dc 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java @@ -11,7 +11,8 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; -@Recommendation(Usage.ACCEPTABLE) +// TODO #391: Wordpress algorithm fails sometimes. Fix it and change the Recommendation to "ACCEPTABLE" if appropriate +@Recommendation(Usage.DO_NOT_USE) @HasSalt(value = SaltType.TEXT, length = 9) // Note ljacqu 20151228: Wordpress is actually a salted algorithm but salt generation is handled internally // and isn't exposed to the outside, so we treat it as an unsalted implementation diff --git a/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java b/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java index 6e332ebd..1b2f75ff 100644 --- a/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java +++ b/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java @@ -34,7 +34,7 @@ public class ChangePasswordTask implements Runnable { final String name = player.getName().toLowerCase(); PlayerAuth auth = PlayerCache.getInstance().getAuth(name); - if (passwordSecurity.comparePassword(oldPassword, auth.getHash(), player.getName())) { + if (passwordSecurity.comparePassword(oldPassword, auth.getHash(), auth.getSalt(), player.getName())) { HashResult hashResult = passwordSecurity.computeHash(newPassword, name); auth.setHash(hashResult.getHash()); auth.setSalt(hashResult.getSalt()); From a3402d573f58a324fbd9adc9c26480e3376375c8 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Wed, 30 Dec 2015 17:56:22 +0100 Subject: [PATCH 16/18] #358 Handle hash + salt as one "unit" - Rename HashResult to EncryptedPassword to reflect its broader use - Use EncryptedPassword in methods that require the hash and the salt, instead of passing them as strings separately - Store EncryptedPassword as field in PlayerAuth; updatePassword() thus processes the entire data in the EncryptedPassword object --- src/main/java/fr/xephi/authme/AuthMe.java | 7 +- src/main/java/fr/xephi/authme/api/API.java | 7 +- src/main/java/fr/xephi/authme/api/NewAPI.java | 7 +- .../xephi/authme/cache/auth/PlayerAuth.java | 258 ++++-------------- .../authme/ChangePasswordAdminCommand.java | 8 +- .../authme/RegisterAdminCommand.java | 7 +- .../executable/email/RecoverEmailCommand.java | 7 +- .../authme/converter/RakamakConverter.java | 15 +- .../authme/datasource/CacheDataSource.java | 18 -- .../xephi/authme/datasource/DataSource.java | 9 - .../fr/xephi/authme/datasource/FlatFile.java | 25 +- .../fr/xephi/authme/datasource/MySQL.java | 79 +++--- .../fr/xephi/authme/datasource/SQLite.java | 130 ++++----- .../xephi/authme/hooks/BungeeCordMessage.java | 7 +- .../process/login/AsynchronousLogin.java | 6 +- .../process/register/AsyncRegister.java | 12 +- .../unregister/AsynchronousUnregister.java | 2 +- .../authme/security/PasswordSecurity.java | 47 ++-- .../xephi/authme/security/crypts/BCRYPT.java | 8 +- .../authme/security/crypts/BCRYPT2Y.java | 4 +- .../authme/security/crypts/CRAZYCRYPT1.java | 4 +- .../authme/security/crypts/CryptPBKDF2.java | 4 +- .../security/crypts/CryptPBKDF2Django.java | 4 +- ...HashResult.java => EncryptedPassword.java} | 6 +- .../security/crypts/EncryptionMethod.java | 15 +- .../security/crypts/HexSaltedMethod.java | 6 +- .../xephi/authme/security/crypts/JOOMLA.java | 3 +- .../xephi/authme/security/crypts/MD5VB.java | 3 +- .../xephi/authme/security/crypts/PHPBB.java | 4 +- .../xephi/authme/security/crypts/SHA256.java | 3 +- .../fr/xephi/authme/security/crypts/SMF.java | 4 +- .../security/crypts/SeparateSaltMethod.java | 8 +- .../security/crypts/UnsaltedMethod.java | 8 +- .../security/crypts/UsernameSaltMethod.java | 8 +- .../fr/xephi/authme/security/crypts/WBB4.java | 4 +- .../authme/security/crypts/WORDPRESS.java | 3 +- .../xephi/authme/security/crypts/XAUTH.java | 3 +- .../xephi/authme/task/ChangePasswordTask.java | 15 +- .../HashAlgorithmIntegrationTest.java | 8 +- .../crypts/AbstractEncryptionMethodTest.java | 68 +++-- .../authme/security/crypts/IPB3Test.java | 8 +- .../authme/security/crypts/MYBBTest.java | 8 +- .../authme/security/crypts/PHPFUSIONTest.java | 8 +- .../security/crypts/SALTED2MD5Test.java | 8 +- .../security/crypts/SALTEDSHA512Test.java | 8 +- .../authme/security/crypts/WBB3Test.java | 8 +- 46 files changed, 328 insertions(+), 564 deletions(-) rename src/main/java/fr/xephi/authme/security/crypts/{HashResult.java => EncryptedPassword.java} (89%) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 7681a536..64d293a5 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -43,7 +43,7 @@ import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.process.Management; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.security.crypts.HashResult; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.OtherAccounts; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Spawn; @@ -592,8 +592,9 @@ public class AuthMe extends JavaPlugin { ConsoleLogger.showError("Your HashAlgorithm has been detected as plaintext and is now deprecated; " + "it will be changed and hashed now to the AuthMe default hashing method"); for (PlayerAuth auth : database.getAllAuths()) { - HashResult hashResult = passwordSecurity.computeHash(HashAlgorithm.SHA256, auth.getHash(), auth.getNickname()); - auth.setHash(hashResult.getHash()); + EncryptedPassword encryptedPassword = passwordSecurity.computeHash( + HashAlgorithm.SHA256, auth.getPassword().getHash(), auth.getNickname()); + auth.setPassword(encryptedPassword); database.updatePassword(auth); } Settings.setValue("settings.security.passwordHash", "SHA256"); diff --git a/src/main/java/fr/xephi/authme/api/API.java b/src/main/java/fr/xephi/authme/api/API.java index af96bf96..2d799b8d 100644 --- a/src/main/java/fr/xephi/authme/api/API.java +++ b/src/main/java/fr/xephi/authme/api/API.java @@ -4,8 +4,7 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.security.crypts.HashResult; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.util.Utils; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -135,13 +134,13 @@ public class API { @Deprecated public static boolean registerPlayer(String playerName, String password) { String name = playerName.toLowerCase(); - HashResult hashResult = passwordSecurity.computeHash(Settings.getPasswordHash, password, name); + EncryptedPassword encryptedPassword = passwordSecurity.computeHash(password, name); if (isRegistered(name)) { return false; } PlayerAuth auth = PlayerAuth.builder() .name(name) - .hash(hashResult.getHash()) + .hash(encryptedPassword) .lastLogin(0) .realName(playerName) .build(); diff --git a/src/main/java/fr/xephi/authme/api/NewAPI.java b/src/main/java/fr/xephi/authme/api/NewAPI.java index ee48d5d6..2990f1df 100644 --- a/src/main/java/fr/xephi/authme/api/NewAPI.java +++ b/src/main/java/fr/xephi/authme/api/NewAPI.java @@ -3,7 +3,7 @@ package fr.xephi.authme.api; import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; -import fr.xephi.authme.security.crypts.HashResult; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.util.Utils; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -149,14 +149,13 @@ public class NewAPI { */ public boolean registerPlayer(String playerName, String password) { String name = playerName.toLowerCase(); - HashResult result = plugin.getPasswordSecurity().computeHash(password, name); + EncryptedPassword result = plugin.getPasswordSecurity().computeHash(password, name); if (isRegistered(name)) { return false; } PlayerAuth auth = PlayerAuth.builder() .name(name) - .hash(result.getHash()) - .salt(result.getSalt()) + .hash(result) .realName(playerName) .build(); return plugin.getDataSource().saveAuth(auth); diff --git a/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java b/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java index c501e8eb..64132afa 100644 --- a/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java +++ b/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java @@ -1,11 +1,9 @@ package fr.xephi.authme.cache.auth; -import fr.xephi.authme.security.HashAlgorithm; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.security.crypts.EncryptedPassword; import static com.google.common.base.Objects.firstNonNull; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Strings.nullToEmpty; /** @@ -13,14 +11,13 @@ import static com.google.common.base.Strings.nullToEmpty; public class PlayerAuth { private String nickname; - private String hash; + private EncryptedPassword password; private String ip; private long lastLogin; private double x; private double y; private double z; private String world; - private String salt; private int groupId; private String email; private String realName; @@ -41,7 +38,7 @@ public class PlayerAuth { * @param realName String */ public PlayerAuth(String nickname, String ip, long lastLogin, String realName) { - this(nickname, "", "", -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); + this(nickname, new EncryptedPassword(""), -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); } /** @@ -55,7 +52,8 @@ public class PlayerAuth { * @param realName String */ public PlayerAuth(String nickname, double x, double y, double z, String world, String realName) { - this(nickname, "", "", -1, "127.0.0.1", System.currentTimeMillis(), x, y, z, world, "your@email.com", realName); + this(nickname, new EncryptedPassword(""), -1, "127.0.0.1", System.currentTimeMillis(), x, y, z, world, + "your@email.com", realName); } /** @@ -68,7 +66,7 @@ public class PlayerAuth { * @param realName String */ public PlayerAuth(String nickname, String hash, String ip, long lastLogin, String realName) { - this(nickname, hash, "", -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); + this(nickname, new EncryptedPassword(hash), -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); } /** @@ -82,7 +80,7 @@ public class PlayerAuth { * @param realName String */ public PlayerAuth(String nickname, String hash, String ip, long lastLogin, String email, String realName) { - this(nickname, hash, "", -1, ip, lastLogin, 0, 0, 0, "world", email, realName); + this(nickname, new EncryptedPassword(hash), -1, ip, lastLogin, 0, 0, 0, "world", email, realName); } /** @@ -96,7 +94,7 @@ public class PlayerAuth { * @param realName String */ public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, String realName) { - this(nickname, hash, salt, -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); + this(nickname, new EncryptedPassword(hash, salt), -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); } /** @@ -113,8 +111,9 @@ public class PlayerAuth { * @param email String * @param realName String */ - public PlayerAuth(String nickname, String hash, String ip, long lastLogin, double x, double y, double z, String world, String email, String realName) { - this(nickname, hash, "", -1, ip, lastLogin, x, y, z, world, email, realName); + public PlayerAuth(String nickname, String hash, String ip, long lastLogin, double x, double y, double z, + String world, String email, String realName) { + this(nickname, new EncryptedPassword(hash), -1, ip, lastLogin, x, y, z, world, email, realName); } /** @@ -132,8 +131,10 @@ public class PlayerAuth { * @param email String * @param realName String */ - public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, double x, double y, double z, String world, String email, String realName) { - this(nickname, hash, salt, -1, ip, lastLogin, x, y, z, world, email, realName); + public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, double x, double y, + double z, String world, String email, String realName) { + this(nickname, new EncryptedPassword(hash, salt), -1, ip, lastLogin, + x, y, z, world, email, realName); } /** @@ -147,38 +148,37 @@ public class PlayerAuth { * @param lastLogin long * @param realName String */ - public PlayerAuth(String nickname, String hash, String salt, int groupId, String ip, long lastLogin, String realName) { - this(nickname, hash, salt, groupId, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); - } - - /** - * Constructor for PlayerAuth. - * - * @param nickname String - * @param hash String - * @param salt String - * @param groupId int - * @param ip String - * @param lastLogin long - * @param x double - * @param y double - * @param z double - * @param world String - * @param email String - * @param realName String - */ public PlayerAuth(String nickname, String hash, String salt, int groupId, String ip, - long lastLogin, double x, double y, double z, String world, String email, - String realName) { + long lastLogin, String realName) { + this(nickname, new EncryptedPassword(hash, salt), groupId, ip, lastLogin, + 0, 0, 0, "world", "your@email.com", realName); + } + + /** + * Constructor for PlayerAuth. + * + * @param nickname String + * @param password String + * @param groupId int + * @param ip String + * @param lastLogin long + * @param x double + * @param y double + * @param z double + * @param world String + * @param email String + * @param realName String + */ + public PlayerAuth(String nickname, EncryptedPassword password, int groupId, String ip, long lastLogin, + double x, double y, double z, String world, String email, String realName) { this.nickname = nickname.toLowerCase(); - this.hash = hash; + this.password = password; this.ip = ip; this.lastLogin = lastLogin; this.x = x; this.y = y; this.z = z; this.world = world; - this.salt = salt; this.groupId = groupId; this.email = email; this.realName = realName; @@ -191,237 +191,108 @@ public class PlayerAuth { */ public void set(PlayerAuth auth) { this.setEmail(auth.getEmail()); - this.setHash(auth.getHash()); + this.setPassword(auth.getPassword()); this.setIp(auth.getIp()); this.setLastLogin(auth.getLastLogin()); this.setNickname(auth.getNickname()); this.setQuitLocX(auth.getQuitLocX()); this.setQuitLocY(auth.getQuitLocY()); this.setQuitLocZ(auth.getQuitLocZ()); - this.setSalt(auth.getSalt()); this.setWorld(auth.getWorld()); this.setRealName(auth.getRealName()); } - /** - * Method setNickname. - * - * @param nickname String - */ + public void setNickname(String nickname) { this.nickname = nickname.toLowerCase(); } - /** - * Method getNickname. - * - * @return String - */ public String getNickname() { return nickname; } - /** - * Method getRealName. - * - * @return String - */ public String getRealName() { return realName; } - /** - * Method setRealName. - * - * @param realName String - */ public void setRealName(String realName) { this.realName = realName; } - /** - * Method getGroupId. - * - * @return int - */ public int getGroupId() { return groupId; } - /** - * Method getQuitLocX. - * - * @return double - */ public double getQuitLocX() { return x; } - /** - * Method setQuitLocX. - * - * @param d double - */ public void setQuitLocX(double d) { this.x = d; } - /** - * Method getQuitLocY. - * - * @return double - */ public double getQuitLocY() { return y; } - /** - * Method setQuitLocY. - * - * @param d double - */ public void setQuitLocY(double d) { this.y = d; } - /** - * Method getQuitLocZ. - * - * @return double - */ public double getQuitLocZ() { return z; } - /** - * Method setQuitLocZ. - * - * @param d double - */ public void setQuitLocZ(double d) { this.z = d; } - /** - * Method getWorld. - * - * @return String - */ public String getWorld() { return world; } - /** - * Method setWorld. - * - * @param world String - */ public void setWorld(String world) { this.world = world; } - /** - * Method getIp. - * - * @return String - */ public String getIp() { return ip; } - /** - * Method setIp. - * - * @param ip String - */ public void setIp(String ip) { this.ip = ip; } - /** - * Method getLastLogin. - * - * @return long - */ public long getLastLogin() { return lastLogin; } - /** - * Method setLastLogin. - * - * @param lastLogin long - */ public void setLastLogin(long lastLogin) { this.lastLogin = lastLogin; } - /** - * Method getEmail. - * - * @return String - */ public String getEmail() { return email; } - /** - * Method setEmail. - * - * @param email String - */ public void setEmail(String email) { this.email = email; } - /** - * Method getSalt. - * - * @return String - */ - public String getSalt() { - return this.salt; - } - - /** - * Method setSalt. - * - * @param salt String - */ - public void setSalt(String salt) { - this.salt = salt; - } - - /** - * Method getHash. - * - * @return String - */ - public String getHash() { - if (Settings.getPasswordHash == HashAlgorithm.MD5VB) { + public EncryptedPassword getPassword() { + // TODO #358: Check whether this check is really necessary. It's been here since the first commit. + /*if (Settings.getPasswordHash == HashAlgorithm.MD5VB) { if (salt != null && !salt.isEmpty() && Settings.getPasswordHash == HashAlgorithm.MD5VB) { return "$MD5vb$" + salt + "$" + hash; } - } - return hash; + }*/ + return password; } - /** - * Method setHash. - * - * @param hash String - */ - public void setHash(String hash) { - this.hash = hash; + public void setPassword(EncryptedPassword password) { + this.password = password; } - /** - * Method equals. - * - * @param obj Object - * - * @return boolean - */ @Override public boolean equals(Object obj) { if (!(obj instanceof PlayerAuth)) { @@ -431,11 +302,6 @@ public class PlayerAuth { return other.getIp().equals(this.ip) && other.getNickname().equals(this.nickname); } - /** - * Method hashCode. - * - * @return int - */ @Override public int hashCode() { int hashCode = 7; @@ -444,20 +310,14 @@ public class PlayerAuth { return hashCode; } - /** - * Method toString. - * - * @return String - */ @Override public String toString() { - return ("Player : " + nickname + " | " + realName + return "Player : " + nickname + " | " + realName + " ! IP : " + ip + " ! LastLogin : " + lastLogin + " ! LastPosition : " + x + "," + y + "," + z + "," + world + " ! Email : " + email - + " ! Hash : " + hash - + " ! Salt : " + salt); + + " ! Hash : {" + password.getHash() + ", " + password.getSalt() + "}"; } /** @@ -472,8 +332,8 @@ public class PlayerAuth { str.append(this.realName).append(d); str.append(this.ip).append(d); str.append(this.email).append(d); - str.append(this.hash).append(d); - str.append(this.salt).append(d); + str.append(this.password.getHash()).append(d); + str.append(this.password.getSalt()).append(d); str.append(this.groupId).append(d); str.append(this.lastLogin).append(d); str.append(this.world).append(d); @@ -492,8 +352,7 @@ public class PlayerAuth { this.realName = args[1]; this.ip = args[2]; this.email = args[3]; - this.hash = args[4]; - this.salt = args[5]; + this.password = new EncryptedPassword(args[4], args[5]); this.groupId = Integer.parseInt(args[6]); this.lastLogin = Long.parseLong(args[7]); this.world = args[8]; @@ -509,8 +368,7 @@ public class PlayerAuth { public static final class Builder { private String name; private String realName; - private String hash; - private String salt; + private EncryptedPassword hash; private String ip; private String world; private String email; @@ -523,8 +381,7 @@ public class PlayerAuth { public PlayerAuth build() { return new PlayerAuth( checkNotNull(name), - nullToEmpty(hash), - nullToEmpty(salt), + firstNonNull(hash, new EncryptedPassword("")), groupId, firstNonNull(ip, "127.0.0.1"), lastLogin, @@ -545,14 +402,13 @@ public class PlayerAuth { return this; } - public Builder hash(String hash) { + public Builder hash(EncryptedPassword hash) { this.hash = hash; return this; } - public Builder salt(String salt) { - this.salt = salt; - return this; + public Builder hash(String hash, String salt) { + return hash(new EncryptedPassword(hash, salt)); } public Builder ip(String ip) { diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java index 74954521..17f3b333 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java @@ -7,7 +7,7 @@ import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.security.crypts.HashResult; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.Settings; import org.bukkit.command.CommandSender; @@ -68,11 +68,9 @@ public class ChangePasswordAdminCommand implements ExecutableCommand { } // TODO #358: Do we always pass lowercase name?? In that case we need to do that in PasswordSecurity! - HashResult hashResult = commandService.getPasswordSecurity().computeHash(playerPass, playerNameLowerCase); - auth.setHash(hashResult.getHash()); - auth.setSalt(hashResult.getSalt()); + EncryptedPassword encryptedPassword = commandService.getPasswordSecurity().computeHash(playerPass, playerNameLowerCase); + auth.setPassword(encryptedPassword); - // TODO #358: updatePassword(auth) needs to update the salt, too. if (!dataSource.updatePassword(auth)) { commandService.send(sender, MessageKey.ERROR); } else { diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java index 44c0be4a..5131ac28 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java @@ -5,7 +5,7 @@ import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.security.crypts.HashResult; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.Settings; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; @@ -57,13 +57,12 @@ public class RegisterAdminCommand implements ExecutableCommand { commandService.send(sender, MessageKey.NAME_ALREADY_REGISTERED); return; } - HashResult hashResult = commandService.getPasswordSecurity() + EncryptedPassword encryptedPassword = commandService.getPasswordSecurity() .computeHash(playerPass, playerNameLowerCase); PlayerAuth auth = PlayerAuth.builder() .name(playerNameLowerCase) .realName(playerName) - .hash(hashResult.getHash()) - .salt(hashResult.getSalt()) + .hash(encryptedPassword) .build(); if (!commandService.getDataSource().saveAuth(auth)) { diff --git a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java index 045bf00a..5e2ed660 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java @@ -8,7 +8,7 @@ import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.security.RandomString; -import fr.xephi.authme.security.crypts.HashResult; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.StringUtils; import org.bukkit.entity.Player; @@ -37,7 +37,7 @@ public class RecoverEmailCommand extends PlayerCommand { } String thePass = RandomString.generate(Settings.getRecoveryPassLength); - HashResult hashNew = commandService.getPasswordSecurity().computeHash(thePass, playerName); + EncryptedPassword hashNew = commandService.getPasswordSecurity().computeHash(thePass, playerName); PlayerAuth auth; if (PlayerCache.getInstance().isAuthenticated(playerName)) { auth = PlayerCache.getInstance().getAuth(playerName); @@ -57,8 +57,7 @@ public class RecoverEmailCommand extends PlayerCommand { commandService.send(player, MessageKey.INVALID_EMAIL); return; } - auth.setHash(hashNew.getHash()); - auth.setSalt(hashNew.getSalt()); + auth.setPassword(hashNew); dataSource.updatePassword(auth); plugin.mail.main(auth, thePass); commandService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); diff --git a/src/main/java/fr/xephi/authme/converter/RakamakConverter.java b/src/main/java/fr/xephi/authme/converter/RakamakConverter.java index 15e26efc..a86b6826 100644 --- a/src/main/java/fr/xephi/authme/converter/RakamakConverter.java +++ b/src/main/java/fr/xephi/authme/converter/RakamakConverter.java @@ -6,7 +6,7 @@ import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.security.crypts.HashResult; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.Settings; import org.bukkit.command.CommandSender; @@ -42,7 +42,7 @@ public class RakamakConverter implements Converter { File source = new File(Settings.PLUGIN_FOLDER, fileName); File ipfiles = new File(Settings.PLUGIN_FOLDER, ipFileName); HashMap playerIP = new HashMap<>(); - HashMap playerPSW = new HashMap<>(); + HashMap playerPSW = new HashMap<>(); try { BufferedReader users; BufferedReader ipFile; @@ -64,22 +64,21 @@ public class RakamakConverter implements Converter { while ((line = users.readLine()) != null) { if (line.contains("=")) { String[] arguments = line.split("="); - HashResult hashResult = passwordSecurity.computeHash(hash, arguments[1], arguments[0]); - playerPSW.put(arguments[0], hashResult); + EncryptedPassword encryptedPassword = passwordSecurity.computeHash(hash, arguments[1], arguments[0]); + playerPSW.put(arguments[0], encryptedPassword); } } users.close(); - for (Entry m : playerPSW.entrySet()) { + for (Entry m : playerPSW.entrySet()) { String playerName = m.getKey(); - HashResult psw = playerPSW.get(playerName); + EncryptedPassword psw = playerPSW.get(playerName); String ip = useIP ? playerIP.get(playerName) : "127.0.0.1"; PlayerAuth auth = PlayerAuth.builder() .name(playerName) .realName(playerName) .ip(ip) - .hash(psw.getHash()) - .salt(psw.getSalt()) + .hash(psw) .lastLogin(System.currentTimeMillis()) .build(); database.saveAuth(auth); diff --git a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java index 8b26b08c..2351c6cb 100644 --- a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java +++ b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java @@ -270,24 +270,6 @@ public class CacheDataSource implements DataSource { return result; } - /** - * Method updateSalt. - * - * @param auth PlayerAuth - * - * @return boolean - * - * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) - */ - @Override - public synchronized boolean updateSalt(final PlayerAuth auth) { - boolean result = source.updateSalt(auth); - if (result) { - cachedAuths.refresh(auth.getNickname()); - } - return result; - } - /** * Method getAllAuthsByName. * diff --git a/src/main/java/fr/xephi/authme/datasource/DataSource.java b/src/main/java/fr/xephi/authme/datasource/DataSource.java index e43dffa8..10f74b92 100644 --- a/src/main/java/fr/xephi/authme/datasource/DataSource.java +++ b/src/main/java/fr/xephi/authme/datasource/DataSource.java @@ -134,15 +134,6 @@ public interface DataSource { */ boolean updateEmail(PlayerAuth auth); - /** - * Method updateSalt. - * - * @param auth PlayerAuth - * - * @return boolean - */ - boolean updateSalt(PlayerAuth auth); - void close(); void reload(); diff --git a/src/main/java/fr/xephi/authme/datasource/FlatFile.java b/src/main/java/fr/xephi/authme/datasource/FlatFile.java index b7fb5517..55646ddc 100644 --- a/src/main/java/fr/xephi/authme/datasource/FlatFile.java +++ b/src/main/java/fr/xephi/authme/datasource/FlatFile.java @@ -102,7 +102,7 @@ public class FlatFile implements DataSource { BufferedWriter bw = null; try { bw = new BufferedWriter(new FileWriter(source, true)); - bw.write(auth.getNickname() + ":" + auth.getHash() + ":" + auth.getIp() + ":" + auth.getLastLogin() + ":" + auth.getQuitLocX() + ":" + auth.getQuitLocY() + ":" + auth.getQuitLocZ() + ":" + auth.getWorld() + ":" + auth.getEmail() + "\n"); + bw.write(auth.getNickname() + ":" + auth.getPassword() + ":" + auth.getIp() + ":" + auth.getLastLogin() + ":" + auth.getQuitLocX() + ":" + auth.getQuitLocY() + ":" + auth.getQuitLocZ() + ":" + auth.getWorld() + ":" + auth.getEmail() + "\n"); } catch (IOException ex) { ConsoleLogger.showError(ex.getMessage()); return false; @@ -137,25 +137,26 @@ public class FlatFile implements DataSource { while ((line = br.readLine()) != null) { String[] args = line.split(":"); if (args[0].equals(auth.getNickname())) { + // Note ljacqu 20151230: This does not persist the salt; it is not supported in flat file. switch (args.length) { case 4: { - newAuth = new PlayerAuth(args[0], auth.getHash(), args[2], Long.parseLong(args[3]), 0, 0, 0, "world", "your@email.com", args[0]); + newAuth = new PlayerAuth(args[0], auth.getPassword().getHash(), args[2], Long.parseLong(args[3]), 0, 0, 0, "world", "your@email.com", args[0]); break; } case 7: { - newAuth = new PlayerAuth(args[0], auth.getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), "world", "your@email.com", args[0]); + newAuth = new PlayerAuth(args[0], auth.getPassword().getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), "world", "your@email.com", args[0]); break; } case 8: { - newAuth = new PlayerAuth(args[0], auth.getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), args[7], "your@email.com", args[0]); + newAuth = new PlayerAuth(args[0], auth.getPassword().getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), args[7], "your@email.com", args[0]); break; } case 9: { - newAuth = new PlayerAuth(args[0], auth.getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), args[7], args[8], args[0]); + newAuth = new PlayerAuth(args[0], auth.getPassword().getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), args[7], args[8], args[0]); break; } default: { - newAuth = new PlayerAuth(args[0], auth.getHash(), args[2], 0, 0, 0, 0, "world", "your@email.com", args[0]); + newAuth = new PlayerAuth(args[0], auth.getPassword().getHash(), args[2], 0, 0, 0, 0, "world", "your@email.com", args[0]); break; } } @@ -600,18 +601,6 @@ public class FlatFile implements DataSource { return true; } - /** - * Method updateSalt. - * - * @param auth PlayerAuth - * - * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) - */ - @Override - public boolean updateSalt(PlayerAuth auth) { - return false; - } - /** * Method getAllAuthsByName. * diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index 7135d6ad..245c6934 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -6,7 +6,9 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.security.HashAlgorithm; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.util.StringUtils; import java.sql.*; import java.util.ArrayList; @@ -284,13 +286,14 @@ public class MySQL implements DataSource { if (!rs.next()) { return null; } - String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : ""; - int group = !salt.isEmpty() && !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; + String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : null; + String hash = rs.getString(columnPassword); + int group = !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; int id = rs.getInt(columnID); pAuth = PlayerAuth.builder() .name(rs.getString(columnName)) .realName(rs.getString(columnRealName)) - .hash(rs.getString(columnPassword)) + .hash(new EncryptedPassword(hash, salt)) .lastLogin(rs.getLong(columnLastLogin)) .ip(rs.getString(columnIp)) .locWorld(rs.getString(lastlocWorld)) @@ -298,7 +301,6 @@ public class MySQL implements DataSource { .locY(rs.getDouble(lastlocY)) .locZ(rs.getDouble(lastlocZ)) .email(rs.getString(columnEmail)) - .salt(salt) .groupId(group) .build(); rs.close(); @@ -328,7 +330,7 @@ public class MySQL implements DataSource { ResultSet rs; String sql; - boolean useSalt = !columnSalt.isEmpty() || !auth.getSalt().isEmpty(); + boolean useSalt = !columnSalt.isEmpty() || !StringUtils.isEmpty(auth.getPassword().getSalt()); sql = "INSERT INTO " + tableName + "(" + columnName + "," + columnPassword + "," + columnIp + "," + columnLastLogin + "," + columnRealName @@ -336,12 +338,12 @@ public class MySQL implements DataSource { + ") VALUES (?,?,?,?,?" + (useSalt ? ",?" : "") + ");"; pst = con.prepareStatement(sql); pst.setString(1, auth.getNickname()); - pst.setString(2, auth.getHash()); + pst.setString(2, auth.getPassword().getHash()); pst.setString(3, auth.getIp()); pst.setLong(4, auth.getLastLogin()); pst.setString(5, auth.getRealName()); if (useSalt) { - pst.setString(6, auth.getSalt()); + pst.setString(6, auth.getPassword().getSalt()); } pst.executeUpdate(); pst.close(); @@ -513,10 +515,22 @@ public class MySQL implements DataSource { @Override public synchronized boolean updatePassword(PlayerAuth auth) { try (Connection con = getConnection()) { - String sql = "UPDATE " + tableName + " SET " + columnPassword + "=? WHERE " + columnName + "=?;"; - PreparedStatement pst = con.prepareStatement(sql); - pst.setString(1, auth.getHash()); - pst.setString(2, auth.getNickname()); + boolean useSalt = !columnSalt.isEmpty(); + PreparedStatement pst; + if (useSalt) { + String sql = String.format("UPDATE %s SET %s = ?, %s = ? WHERE %s = ?;", + tableName, columnPassword, columnSalt, columnName); + pst = con.prepareStatement(sql); + pst.setString(1, auth.getPassword().getHash()); + pst.setString(2, auth.getPassword().getSalt()); + pst.setString(3, auth.getNickname()); + } else { + String sql = String.format("UPDATE %s SET %s = ? WHERE %s = ?;", + tableName, columnPassword, columnName); + pst = con.prepareStatement(sql); + pst.setString(1, auth.getPassword().getHash()); + pst.setString(2, auth.getNickname()); + } pst.executeUpdate(); pst.close(); return true; @@ -719,35 +733,6 @@ public class MySQL implements DataSource { return false; } - /** - * Method updateSalt. - * - * @param auth PlayerAuth - * - * @return boolean - * - * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) - */ - @Override - public synchronized boolean updateSalt(PlayerAuth auth) { - if (columnSalt.isEmpty()) { - return false; - } - try (Connection con = getConnection()) { - String sql = "UPDATE " + tableName + " SET " + columnSalt + " =? WHERE " + columnName + "=?;"; - PreparedStatement pst = con.prepareStatement(sql); - pst.setString(1, auth.getSalt()); - pst.setString(2, auth.getNickname()); - pst.executeUpdate(); - pst.close(); - return true; - } catch (SQLException ex) { - ConsoleLogger.showError(ex.getMessage()); - ConsoleLogger.writeStackTrace(ex); - } - return false; - } - /** * Method reload. * @@ -1045,12 +1030,12 @@ public class MySQL implements DataSource { ResultSet rs = st.executeQuery("SELECT * FROM " + tableName); PreparedStatement pst = con.prepareStatement("SELECT data FROM xf_user_authenticate WHERE " + columnID + "=?;"); while (rs.next()) { - String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : ""; - int group = !salt.isEmpty() && !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; + String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : null; + int group = !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; PlayerAuth pAuth = PlayerAuth.builder() .name(rs.getString(columnName)) .realName(rs.getString(columnRealName)) - .hash(rs.getString(columnPassword)) + .hash(new EncryptedPassword(rs.getString(columnPassword), salt)) .lastLogin(rs.getLong(columnLastLogin)) .ip(rs.getString(columnIp)) .locWorld(rs.getString(lastlocWorld)) @@ -1058,7 +1043,6 @@ public class MySQL implements DataSource { .locY(rs.getDouble(lastlocY)) .locZ(rs.getDouble(lastlocZ)) .email(rs.getString(columnEmail)) - .salt(salt) .groupId(group) .build(); @@ -1089,12 +1073,12 @@ public class MySQL implements DataSource { ResultSet rs = st.executeQuery("SELECT * FROM " + tableName + " WHERE " + columnLogged + "=1;"); PreparedStatement pst = con.prepareStatement("SELECT data FROM xf_user_authenticate WHERE " + columnID + "=?;"); while (rs.next()) { - String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : ""; - int group = !salt.isEmpty() && !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; + String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : null; + int group = !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; PlayerAuth pAuth = PlayerAuth.builder() .name(rs.getString(columnName)) .realName(rs.getString(columnRealName)) - .hash(rs.getString(columnPassword)) + .hash(new EncryptedPassword(rs.getString(columnPassword), salt)) .lastLogin(rs.getLong(columnLastLogin)) .ip(rs.getString(columnIp)) .locWorld(rs.getString(lastlocWorld)) @@ -1102,7 +1086,6 @@ public class MySQL implements DataSource { .locY(rs.getDouble(lastlocY)) .locZ(rs.getDouble(lastlocZ)) .email(rs.getString(columnEmail)) - .salt(salt) .groupId(group) .build(); diff --git a/src/main/java/fr/xephi/authme/datasource/SQLite.java b/src/main/java/fr/xephi/authme/datasource/SQLite.java index 9c4301ae..590dd6bf 100644 --- a/src/main/java/fr/xephi/authme/datasource/SQLite.java +++ b/src/main/java/fr/xephi/authme/datasource/SQLite.java @@ -2,6 +2,7 @@ package fr.xephi.authme.datasource; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.Settings; import java.sql.*; @@ -174,15 +175,7 @@ public class SQLite implements DataSource { pst.setString(1, user); rs = pst.executeQuery(); if (rs.next()) { - if (rs.getString(columnIp).isEmpty()) { - return new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), "192.168.0.1", rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } else { - if (!columnSalt.isEmpty()) { - return new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnSalt), rs.getInt(columnGroup), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } else { - return new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } - } + return buildAuthFromResultSet(rs); } else { return null; } @@ -206,21 +199,25 @@ public class SQLite implements DataSource { public synchronized boolean saveAuth(PlayerAuth auth) { PreparedStatement pst = null; try { - if (columnSalt.isEmpty() && auth.getSalt().isEmpty()) { - pst = con.prepareStatement("INSERT INTO " + tableName + "(" + columnName + "," + columnPassword + "," + columnIp + "," + columnLastLogin + "," + columnRealName + ") VALUES (?,?,?,?,?);"); + EncryptedPassword password = auth.getPassword(); + if (columnSalt.isEmpty() && password.getSalt().isEmpty()) { + pst = con.prepareStatement("INSERT INTO " + tableName + "(" + columnName + "," + columnPassword + + "," + columnIp + "," + columnLastLogin + "," + columnRealName + ") VALUES (?,?,?,?,?);"); pst.setString(1, auth.getNickname()); - pst.setString(2, auth.getHash()); + pst.setString(2, password.getHash()); pst.setString(3, auth.getIp()); pst.setLong(4, auth.getLastLogin()); pst.setString(5, auth.getRealName()); pst.executeUpdate(); } else { - pst = con.prepareStatement("INSERT INTO " + tableName + "(" + columnName + "," + columnPassword + "," + columnIp + "," + columnLastLogin + "," + columnSalt + "," + columnRealName + ") VALUES (?,?,?,?,?,?);"); + pst = con.prepareStatement("INSERT INTO " + tableName + "(" + columnName + "," + columnPassword + "," + + columnIp + "," + columnLastLogin + "," + columnSalt + "," + columnRealName + + ") VALUES (?,?,?,?,?,?);"); pst.setString(1, auth.getNickname()); - pst.setString(2, auth.getHash()); + pst.setString(2, password.getHash()); pst.setString(3, auth.getIp()); pst.setLong(4, auth.getLastLogin()); - pst.setString(5, auth.getSalt()); + pst.setString(5, password.getSalt()); pst.setString(6, auth.getRealName()); pst.executeUpdate(); } @@ -244,9 +241,19 @@ public class SQLite implements DataSource { public synchronized boolean updatePassword(PlayerAuth auth) { PreparedStatement pst = null; try { - pst = con.prepareStatement("UPDATE " + tableName + " SET " + columnPassword + "=? WHERE " + columnName + "=?;"); - pst.setString(1, auth.getHash()); - pst.setString(2, auth.getNickname()); + EncryptedPassword password = auth.getPassword(); + boolean useSalt = !columnSalt.isEmpty(); + String sql = "UPDATE " + tableName + " SET " + columnPassword + " = ?" + + (useSalt ? ", " + columnSalt + " = ?" : "") + + " WHERE " + columnName + " = ?"; + pst = con.prepareStatement(sql); + pst.setString(1, password.getHash()); + if (useSalt) { + pst.setString(2, password.getSalt()); + pst.setString(3, auth.getNickname()); + } else { + pst.setString(2, auth.getNickname()); + } pst.executeUpdate(); } catch (SQLException ex) { ConsoleLogger.showError(ex.getMessage()); @@ -398,6 +405,7 @@ public class SQLite implements DataSource { ResultSet rs = null; int countIp = 0; try { + // TODO ljacqu 20151230: Simply fetch COUNT(1) and return that pst = con.prepareStatement("SELECT * FROM " + tableName + " WHERE " + columnIp + "=?;"); pst.setString(1, ip); rs = pst.executeQuery(); @@ -438,33 +446,6 @@ public class SQLite implements DataSource { return true; } - /** - * Method updateSalt. - * - * @param auth PlayerAuth - * - * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) - */ - @Override - public boolean updateSalt(PlayerAuth auth) { - if (columnSalt.isEmpty()) { - return false; - } - PreparedStatement pst = null; - try { - pst = con.prepareStatement("UPDATE " + tableName + " SET " + columnSalt + "=? WHERE " + columnName + "=?;"); - pst.setString(1, auth.getSalt()); - pst.setString(2, auth.getNickname()); - pst.executeUpdate(); - } catch (SQLException ex) { - ConsoleLogger.showError(ex.getMessage()); - return false; - } finally { - close(pst); - } - return true; - } - /** * Method close. * @@ -761,14 +742,6 @@ public class SQLite implements DataSource { return result; } - /** - * Method updateName. - * - * @param oldOne String - * @param newOne String - * - * @see fr.xephi.authme.datasource.DataSource#updateName(String, String) - */ @Override public void updateName(String oldOne, String newOne) { PreparedStatement pst = null; @@ -787,7 +760,7 @@ public class SQLite implements DataSource { /** * Method getAllAuths. * - * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuths() + * @return List */ @Override public List getAllAuths() { @@ -798,17 +771,8 @@ public class SQLite implements DataSource { pst = con.prepareStatement("SELECT * FROM " + tableName + ";"); rs = pst.executeQuery(); while (rs.next()) { - PlayerAuth pAuth; - if (rs.getString(columnIp).isEmpty()) { - pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), "127.0.0.1", rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } else { - if (!columnSalt.isEmpty()) { - pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnSalt), rs.getInt(columnGroup), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } else { - pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } - } - auths.add(pAuth); + PlayerAuth auth = buildAuthFromResultSet(rs); + auths.add(auth); } } catch (SQLException ex) { ConsoleLogger.showError(ex.getMessage()); @@ -822,7 +786,7 @@ public class SQLite implements DataSource { /** * Method getLoggedPlayers. * - * @return List * @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers() + * @return List */ @Override public List getLoggedPlayers() { @@ -833,17 +797,8 @@ public class SQLite implements DataSource { pst = con.prepareStatement("SELECT * FROM " + tableName + " WHERE " + columnLogged + "=1;"); rs = pst.executeQuery(); while (rs.next()) { - PlayerAuth pAuth; - if (rs.getString(columnIp).isEmpty()) { - pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), "127.0.0.1", rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } else { - if (!columnSalt.isEmpty()) { - pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnSalt), rs.getInt(columnGroup), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } else { - pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } - } - auths.add(pAuth); + PlayerAuth auth = buildAuthFromResultSet(rs); + auths.add(auth); } } catch (SQLException ex) { ConsoleLogger.showError(ex.getMessage()); @@ -853,4 +808,25 @@ public class SQLite implements DataSource { } return auths; } + + private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException { + String salt = !columnSalt.isEmpty() ? row.getString(columnSalt) : null; + + PlayerAuth.Builder authBuilder = PlayerAuth.builder() + .name(row.getString(columnName)) + .email(row.getString(columnEmail)) + .realName(row.getString(columnRealName)) + .hash(row.getString(columnPassword), salt) + .lastLogin(row.getLong(columnLastLogin)) + .locX(row.getDouble(lastlocX)) + .locY(row.getDouble(lastlocY)) + .locZ(row.getDouble(lastlocZ)) + .locWorld(row.getString(lastlocWorld)); + + String ip = row.getString(columnIp); + if (!ip.isEmpty()) { + authBuilder.ip(ip); + } + return authBuilder.build(); + } } diff --git a/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java b/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java index 3202f45d..731de626 100644 --- a/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java +++ b/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java @@ -7,6 +7,7 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.security.crypts.EncryptedPassword; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; @@ -65,10 +66,8 @@ public class BungeeCordMessage implements PluginMessageListener { + " has registered out from one of your server!"); } else if ("changepassword".equals(act)) { final String password = args[2]; - auth.setHash(password); - if (args.length == 4) { - auth.setSalt(args[3]); - } + final String salt = args.length >= 4 ? args[3] : null; + auth.setPassword(new EncryptedPassword(password, salt)); PlayerCache.getInstance().updatePlayer(auth); dataSource.updatePassword(auth); } diff --git a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java index 0d99d04c..a4d8d299 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -8,7 +8,6 @@ import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent; import fr.xephi.authme.permission.PlayerPermission; -import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.RandomString; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; @@ -138,7 +137,7 @@ public class AsynchronousLogin { String email = pAuth.getEmail(); boolean passwordVerified = forceLogin || plugin.getPasswordSecurity() - .comparePassword(password, pAuth.getHash(), pAuth.getSalt(), realName); + .comparePassword(password, pAuth.getPassword(), realName); if (passwordVerified && player.isOnline()) { PlayerAuth auth = PlayerAuth.builder() @@ -147,8 +146,7 @@ public class AsynchronousLogin { .ip(getIP()) .lastLogin(new Date().getTime()) .email(email) - .hash(pAuth.getHash()) - .salt(pAuth.getSalt()) + .hash(pAuth.getPassword()) .build(); database.updateSession(auth); diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java index 9796dbb0..13baacae 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -8,7 +8,7 @@ import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.PlayerPermission; -import fr.xephi.authme.security.crypts.HashResult; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.Settings; import org.bukkit.entity.Player; @@ -97,12 +97,11 @@ public class AsyncRegister { m.send(player, MessageKey.MAX_REGISTER_EXCEEDED); return; } - final HashResult hashResult = plugin.getPasswordSecurity().computeHash(password, name); + final EncryptedPassword encryptedPassword = plugin.getPasswordSecurity().computeHash(password, name); PlayerAuth auth = PlayerAuth.builder() .name(name) .realName(player.getName()) - .hash(hashResult.getHash()) - .salt(hashResult.getSalt()) + .hash(encryptedPassword) .ip(ip) .locWorld(player.getLocation().getWorld().getName()) .locX(player.getLocation().getX()) @@ -124,12 +123,11 @@ public class AsyncRegister { } private void passwordRegister() throws Exception { - final HashResult hashResult = plugin.getPasswordSecurity().computeHash(password, name); + final EncryptedPassword encryptedPassword = plugin.getPasswordSecurity().computeHash(password, name); PlayerAuth auth = PlayerAuth.builder() .name(name) .realName(player.getName()) - .hash(hashResult.getHash()) - .salt(hashResult.getSalt()) + .hash(encryptedPassword) .ip(ip) .locWorld(player.getLocation().getWorld().getName()) .locX(player.getLocation().getX()) diff --git a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java index 1545552e..58c17694 100644 --- a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java +++ b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java @@ -55,7 +55,7 @@ public class AsynchronousUnregister { public void process() { PlayerAuth cachedAuth = PlayerCache.getInstance().getAuth(name); if (force || plugin.getPasswordSecurity().comparePassword( - password, cachedAuth.getHash(), cachedAuth.getSalt(), player.getName())) { + password, cachedAuth.getPassword(), player.getName())) { if (!plugin.getDataSource().removeAuth(name)) { m.send(player, MessageKey.ERROR); return; diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index 678396b5..9ea1ccfa 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -3,12 +3,10 @@ package fr.xephi.authme.security; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.PasswordEncryptionEvent; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.security.crypts.EncryptionMethod; -import fr.xephi.authme.security.crypts.HashResult; import org.bukkit.Bukkit; -import java.util.HashMap; - /** * Manager class for password-related operations. */ @@ -24,48 +22,53 @@ public class PasswordSecurity { this.supportOldAlgorithm = supportOldAlgorithm; } - public HashResult computeHash(String password, String playerName) { + public EncryptedPassword computeHash(String password, String playerName) { return computeHash(algorithm, password, playerName); } - public HashResult computeHash(HashAlgorithm algorithm, String password, String playerName) { + public EncryptedPassword computeHash(HashAlgorithm algorithm, String password, String playerName) { EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName); return method.computeHash(password, playerName); } public boolean comparePassword(String password, String playerName) { + // TODO ljacqu 20151230: Defining a dataSource.getPassword() method would be more efficient PlayerAuth auth = dataSource.getAuth(playerName); if (auth != null) { - return comparePassword(auth.getHash(), auth.getSalt(), password, playerName); + return comparePassword(password, auth.getPassword(), playerName); } return false; } - public boolean comparePassword(String hash, String salt, String password, String playerName) { + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String playerName) { EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName); // User is not in data source, so the result will invariably be wrong because an encryption // method with hasSeparateSalt() == true NEEDS the salt to evaluate the password + String salt = encryptedPassword.getSalt(); if (method.hasSeparateSalt() && salt == null) { return false; } - return method.comparePassword(hash, password, salt, playerName) - || supportOldAlgorithm && compareWithAllEncryptionMethods(password, hash, salt, playerName); + + return method.comparePassword(password, encryptedPassword, playerName) + || supportOldAlgorithm && compareWithAllEncryptionMethods(password, encryptedPassword, playerName); } /** - * Compare the given hash with all available encryption methods to support the migration to a new encryption method. + * Compare the given hash with all available encryption methods to support + * the migration to a new encryption method. Upon a successful match, the password + * will be hashed with the new encryption method and persisted. * - * @param password The clear-text password to check - * @param hash The hash to text the password against - * @param salt The salt (or null if none available) - * @param playerName The name of the player + * @param password The clear-text password to check + * @param encryptedPassword The encrypted password to test the clear-text password against + * @param playerName The name of the player * @return True if the */ - private boolean compareWithAllEncryptionMethods(String password, String hash, String salt, String playerName) { + private boolean compareWithAllEncryptionMethods(String password, EncryptedPassword encryptedPassword, + String playerName) { for (HashAlgorithm algorithm : HashAlgorithm.values()) { if (!HashAlgorithm.CUSTOM.equals(algorithm)) { EncryptionMethod method = initializeEncryptionMethodWithoutEvent(algorithm); - if (method != null && method.comparePassword(hash, password, salt, playerName)) { + if (method != null && method.comparePassword(password, encryptedPassword, playerName)) { hashPasswordForNewAlgorithm(password, playerName); return true; } @@ -75,8 +78,8 @@ public class PasswordSecurity { } /** - * Get the encryption method from the given {@link HashAlgorithm} value and emits a - * {@link PasswordEncryptionEvent}. The encryption method from the event is returned, + * Get the encryption method from the given {@link HashAlgorithm} value and emit a + * {@link PasswordEncryptionEvent}. The encryption method from the event is then returned, * which may have been changed by an external listener. * * @param algorithm The algorithm to retrieve the encryption method for @@ -110,14 +113,10 @@ public class PasswordSecurity { private void hashPasswordForNewAlgorithm(String password, String playerName) { PlayerAuth auth = dataSource.getAuth(playerName); if (auth != null) { - HashResult hashResult = initializeEncryptionMethod(algorithm, playerName) + EncryptedPassword encryptedPassword = initializeEncryptionMethod(algorithm, playerName) .computeHash(password, playerName); - - // TODO #358: updatePassword() should just take the HashResult..., or at least hash & salt. Idem for setHash - auth.setSalt(hashResult.getSalt()); - auth.setHash(hashResult.getHash()); + auth.setPassword(encryptedPassword); dataSource.updatePassword(auth); - dataSource.updateSalt(auth); } } 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 3c0bc785..6b660d7b 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java @@ -520,14 +520,14 @@ public class BCRYPT implements EncryptionMethod { } @Override - public HashResult computeHash(String password, String name) { + public EncryptedPassword computeHash(String password, String name) { String salt = generateSalt(); - return new HashResult(hashpw(password, salt), null); + return new EncryptedPassword(hashpw(password, salt), null); } @Override - public boolean comparePassword(String hash, String password, String salt, String name) { - return checkpw(password, hash); + public boolean comparePassword(String password, EncryptedPassword hash, String name) { + return checkpw(password, hash.getHash()); } @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java index 664b3c93..57f3894f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java @@ -15,11 +15,13 @@ public class BCRYPT2Y extends HexSaltedMethod { } @Override - public boolean comparePassword(String hash, String password, String unusedSalt, String unusedName) { + public boolean comparePassword(String password, EncryptedPassword encrypted, String unusedName) { + String hash = encrypted.getHash(); if (hash.length() != 60) { return false; } // The salt is the first 29 characters of the hash + String salt = hash.substring(0, 29); return hash.equals(computeHash(password, salt, null)); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java index c5d0cd13..73fae5db 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java @@ -28,11 +28,11 @@ public class CRAZYCRYPT1 extends UsernameSaltMethod { } @Override - public HashResult computeHash(String password, String name) { + public EncryptedPassword computeHash(String password, String name) { final String text = "ÜÄaeut//&/=I " + password + "7421€547" + name + "__+IÄIH§%NK " + password; final MessageDigest md = HashUtils.getDigest(MessageDigestAlgorithm.SHA512); md.update(text.getBytes(charset), 0, text.length()); - return new HashResult(byteArrayToHexString(md.digest())); + return new EncryptedPassword(byteArrayToHexString(md.digest())); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java index 2742d0b2..a3f2ea39 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java @@ -20,8 +20,8 @@ public class CryptPBKDF2 extends HexSaltedMethod { } @Override - public boolean comparePassword(String hash, String password, String unusedSalt, String unusedName) { - String[] line = hash.split("\\$"); + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String unusedName) { + String[] line = encryptedPassword.getHash().split("\\$"); String salt = line[2]; String derivedKey = line[3]; PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 10000, derivedKey.getBytes()); diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java index 3c9b5d60..e16b4c77 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java @@ -19,8 +19,8 @@ public class CryptPBKDF2Django extends HexSaltedMethod { } @Override - public boolean comparePassword(String hash, String password, String unusedSalt, String unusedName) { - String[] line = hash.split("\\$"); + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String unusedName) { + String[] line = encryptedPassword.getHash().split("\\$"); String salt = line[2]; byte[] derivedKey = DatatypeConverter.parseBase64Binary(line[3]); PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 15000, derivedKey); diff --git a/src/main/java/fr/xephi/authme/security/crypts/HashResult.java b/src/main/java/fr/xephi/authme/security/crypts/EncryptedPassword.java similarity index 89% rename from src/main/java/fr/xephi/authme/security/crypts/HashResult.java rename to src/main/java/fr/xephi/authme/security/crypts/EncryptedPassword.java index 767a3a0c..3425801c 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/HashResult.java +++ b/src/main/java/fr/xephi/authme/security/crypts/EncryptedPassword.java @@ -3,7 +3,7 @@ package fr.xephi.authme.security.crypts; /** * The result of a hash computation. See {@link #salt} for details. */ -public class HashResult { +public class EncryptedPassword { /** The generated hash. */ private final String hash; @@ -23,7 +23,7 @@ public class HashResult { * @param hash The computed hash * @param salt The generated salt */ - public HashResult(String hash, String salt) { + public EncryptedPassword(String hash, String salt) { this.hash = hash; this.salt = salt; } @@ -33,7 +33,7 @@ public class HashResult { * * @param hash The computed hash */ - public HashResult(String hash) { + public EncryptedPassword(String hash) { this(hash, null); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java index efc98b66..9381113c 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java @@ -12,9 +12,9 @@ public interface EncryptionMethod { * @param name The name of the player (sometimes required to generate a salt with) * * @return The hash result for the password. - * @see HashResult + * @see EncryptedPassword */ - HashResult computeHash(String password, String name); + EncryptedPassword computeHash(String password, String name); /** * Hash the given password with the given salt for the given player. @@ -31,14 +31,13 @@ public interface EncryptionMethod { /** * Check whether the given hash matches the clear-text password. * - * @param hash The hash to verify - * @param password The clear-text password to verify the hash against - * @param salt The salt if it is stored separately (null otherwise) - * @param name The player name to do the check for (sometimes required for generating the salt) + * @param password The clear-text password to verify + * @param encryptedPassword The hash to check the password against + * @param name The player name to do the check for (sometimes required for generating the salt) * * @return True if the password matches, false otherwise */ - boolean comparePassword(String hash, String password, String salt, String name); + boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name); /** * Generate a new salt to hash a password with. @@ -49,7 +48,7 @@ public interface EncryptionMethod { /** * Return whether the encryption method requires the salt to be stored separately and - * passed again to {@link #comparePassword(String, String, String, String)}. Note that + * passed again to {@link #comparePassword(String, EncryptedPassword, String)}. Note that * an encryption method returning {@code false} does not imply that it uses no salt; it * may be embedded into the hash or it may use the username as salt. * diff --git a/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java b/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java index 25443420..2427e431 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java @@ -20,13 +20,13 @@ public abstract class HexSaltedMethod implements EncryptionMethod { public abstract String computeHash(String password, String salt, String name); @Override - public HashResult computeHash(String password, String name) { + public EncryptedPassword computeHash(String password, String name) { String salt = generateSalt(); - return new HashResult(computeHash(password, salt, null)); + return new EncryptedPassword(computeHash(password, salt, null)); } @Override - public abstract boolean comparePassword(String hash, String password, String salt, String name); + public abstract boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name); @Override public String generateSalt() { diff --git a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java index 7abc0536..4e42958f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java +++ b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java @@ -13,7 +13,8 @@ public class JOOMLA extends HexSaltedMethod { } @Override - public boolean comparePassword(String hash, String password, String unusedSalt, String unusedName) { + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String unusedName) { + String hash = encryptedPassword.getHash(); String[] hashParts = hash.split(":"); return hashParts.length == 2 && hash.equals(computeHash(password, hashParts[1], null)); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java index 8fb92e60..39a08b7f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java @@ -10,7 +10,8 @@ public class MD5VB extends HexSaltedMethod { } @Override - public boolean comparePassword(String hash, String password, String salt, String name) { + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) { + String hash = encryptedPassword.getHash(); String[] line = hash.split("\\$"); return line.length == 4 && hash.equals(computeHash(password, line[2], name)); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java index 753f419a..c7d85b6a 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java @@ -144,8 +144,8 @@ public class PHPBB extends HexSaltedMethod { } @Override - public boolean comparePassword(String hash, String password, String salt, String name) { - return phpbb_check_hash(password, hash); + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) { + return phpbb_check_hash(password, encryptedPassword.getHash()); } @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java index cc28507c..efe2c8dd 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java @@ -14,7 +14,8 @@ public class SHA256 extends HexSaltedMethod { } @Override - public boolean comparePassword(String hash, String password, String salt, String playerName) { + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String playerName) { + String hash = encryptedPassword.getHash(); String[] line = hash.split("\\$"); return line.length == 4 && hash.equals(computeHash(password, line[2], "")); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SMF.java b/src/main/java/fr/xephi/authme/security/crypts/SMF.java index 10361811..c1d33661 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SMF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SMF.java @@ -4,8 +4,8 @@ import fr.xephi.authme.security.HashUtils; public class SMF extends UsernameSaltMethod { - public HashResult computeHash(String password, String name) { - return new HashResult(HashUtils.sha1(name.toLowerCase() + password)); + public EncryptedPassword computeHash(String password, String name) { + return new EncryptedPassword(HashUtils.sha1(name.toLowerCase() + password)); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java index c0b2f070..c7402fcc 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java @@ -12,14 +12,14 @@ public abstract class SeparateSaltMethod implements EncryptionMethod { public abstract String generateSalt(); @Override - public HashResult computeHash(String password, String name) { + public EncryptedPassword computeHash(String password, String name) { String salt = generateSalt(); - return new HashResult(computeHash(password, salt, name), salt); + return new EncryptedPassword(computeHash(password, salt, name), salt); } @Override - public boolean comparePassword(String hash, String password, String salt, String name) { - return hash.equals(computeHash(password, salt, null)); + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) { + return encryptedPassword.getHash().equals(computeHash(password, encryptedPassword.getSalt(), null)); } @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java b/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java index b6949a72..6900b80f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java @@ -15,8 +15,8 @@ public abstract class UnsaltedMethod implements EncryptionMethod { public abstract String computeHash(String password); @Override - public HashResult computeHash(String password, String name) { - return new HashResult(computeHash(password)); + public EncryptedPassword computeHash(String password, String name) { + return new EncryptedPassword(computeHash(password)); } @Override @@ -25,8 +25,8 @@ public abstract class UnsaltedMethod implements EncryptionMethod { } @Override - public boolean comparePassword(String hash, String password, String salt, String name) { - return hash.equals(computeHash(password)); + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) { + return encryptedPassword.getHash().equals(computeHash(password)); } @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java index 1d294713..c7b46b11 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java @@ -7,18 +7,18 @@ import fr.xephi.authme.security.crypts.description.Usage; /** * Common supertype of encryption methods that use a player's username - * (or something based on it) as salt. + * (or something based on it) as embedded salt. */ @Recommendation(Usage.DO_NOT_USE) @HasSalt(SaltType.USERNAME) public abstract class UsernameSaltMethod implements EncryptionMethod { @Override - public abstract HashResult computeHash(String password, String name); + public abstract EncryptedPassword computeHash(String password, String name); @Override - public boolean comparePassword(String hash, String password, String salt, String name) { - return hash.equals(computeHash(password, name).getHash()); + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) { + return encryptedPassword.getHash().equals(computeHash(password, name).getHash()); } @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java index 5cfd954b..9f67d499 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java @@ -12,8 +12,8 @@ public class WBB4 extends HexSaltedMethod { } @Override - public boolean comparePassword(String hash, String password, String salt, String playerName) { - return BCRYPT.checkpw(password, hash, 2); + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String playerName) { + return BCRYPT.checkpw(password, encryptedPassword.getHash(), 2); } @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java index d1e6a9dc..5b198056 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java @@ -117,7 +117,8 @@ public class WORDPRESS extends UnsaltedMethod { } @Override - public boolean comparePassword(String hash, String password, String salt, String name) { + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) { + String hash = encryptedPassword.getHash(); String comparedHash = crypt(password, hash); return comparedHash.equals(hash); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java index f0ce068d..ff5236bd 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java @@ -23,7 +23,8 @@ public class XAUTH extends HexSaltedMethod { } @Override - public boolean comparePassword(String hash, String password, String salt, String playerName) { + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String playerName) { + String hash = encryptedPassword.getHash(); int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length()); String saltFromHash = hash.substring(saltPos, saltPos + 12); return hash.equals(computeHash(password, saltFromHash, null)); diff --git a/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java b/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java index 1b2f75ff..d85149b6 100644 --- a/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java +++ b/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java @@ -9,7 +9,7 @@ import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.security.crypts.HashResult; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.Settings; import org.bukkit.entity.Player; @@ -34,22 +34,21 @@ public class ChangePasswordTask implements Runnable { final String name = player.getName().toLowerCase(); PlayerAuth auth = PlayerCache.getInstance().getAuth(name); - if (passwordSecurity.comparePassword(oldPassword, auth.getHash(), auth.getSalt(), player.getName())) { - HashResult hashResult = passwordSecurity.computeHash(newPassword, name); - auth.setHash(hashResult.getHash()); - auth.setSalt(hashResult.getSalt()); + if (passwordSecurity.comparePassword(oldPassword, auth.getPassword(), player.getName())) { + EncryptedPassword encryptedPassword = passwordSecurity.computeHash(newPassword, name); + auth.setPassword(encryptedPassword); if (!plugin.getDataSource().updatePassword(auth)) { m.send(player, MessageKey.ERROR); return; } - plugin.getDataSource().updateSalt(auth); + PlayerCache.getInstance().updatePlayer(auth); m.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS); ConsoleLogger.info(player.getName() + " changed his password"); if (Settings.bungee) { - final String hash = auth.getHash(); - final String salt = auth.getSalt(); + final String hash = encryptedPassword.getHash(); + final String salt = encryptedPassword.getSalt(); plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable(){ @Override diff --git a/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java b/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java index 35e3b577..c5cfea95 100644 --- a/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java +++ b/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java @@ -1,7 +1,7 @@ package fr.xephi.authme.security; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.security.crypts.EncryptionMethod; -import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.WrapperMock; @@ -49,11 +49,11 @@ public class HashAlgorithmIntegrationTest { for (HashAlgorithm algorithm : HashAlgorithm.values()) { if (!HashAlgorithm.CUSTOM.equals(algorithm)) { EncryptionMethod method = algorithm.getClazz().newInstance(); - HashResult hashResult = method.computeHash("pwd", "name"); + EncryptedPassword encryptedPassword = method.computeHash("pwd", "name"); assertThat("Salt should not be null if method.hasSeparateSalt(), and vice versa. Method: '" - + method + "'", StringUtils.isEmpty(hashResult.getSalt()), equalTo(!method.hasSeparateSalt())); + + method + "'", StringUtils.isEmpty(encryptedPassword.getSalt()), equalTo(!method.hasSeparateSalt())); assertThat("Hash should not be empty for method '" + method + "'", - StringUtils.isEmpty(hashResult.getHash()), equalTo(false)); + StringUtils.isEmpty(encryptedPassword.getHash()), equalTo(false)); } } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java index ad3d183f..cb81164b 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java @@ -36,47 +36,42 @@ public abstract class AbstractEncryptionMethodTest { /** The encryption method to test. */ private EncryptionMethod method; /** Map with the hashes against which the entries in GIVEN_PASSWORDS are tested. */ - private Map hashes; - /** The accompanying salts for the hashes in {@link #hashes} if necessary. Can be empty otherwise. */ - private Map salts; + private Map hashes; /** * Create a new test for the given encryption method. * * @param method The encryption method to test - * @param hash0 The pre-generated hash for the first {@link #GIVEN_PASSWORDS} - * @param hash1 The pre-generated hash for the second {@link #GIVEN_PASSWORDS} - * @param hash2 The pre-generated hash for the third {@link #GIVEN_PASSWORDS} - * @param hash3 The pre-generated hash for the fourth {@link #GIVEN_PASSWORDS} + * @param computedHashes The pre-generated hashes for the elements in {@link #GIVEN_PASSWORDS} */ - public AbstractEncryptionMethodTest(EncryptionMethod method, String hash0, String hash1, - String hash2, String hash3) { - // TODO #358: Throw if method.hasSeparateSalt() is true + public AbstractEncryptionMethodTest(EncryptionMethod method, String... computedHashes) { + if (method.hasSeparateSalt()) { + throw new UnsupportedOperationException("Test must be initialized with EncryptedPassword objects if " + + "the salt is stored separately. Use the other constructor"); + } else if (computedHashes.length != GIVEN_PASSWORDS.length) { + throw new UnsupportedOperationException("Expected " + GIVEN_PASSWORDS.length + " hashes"); + } 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); - salts = new HashMap<>(); + for (int i = 0; i < GIVEN_PASSWORDS.length; ++i) { + hashes.put(GIVEN_PASSWORDS[i], new EncryptedPassword(computedHashes[i])); + } } - public AbstractEncryptionMethodTest(EncryptionMethod method, HashResult result0, HashResult result1, - HashResult result2, HashResult result3) { - // TODO #358: Throw if method.hasSeparateSalt() is false + public AbstractEncryptionMethodTest(EncryptionMethod method, EncryptedPassword result0, EncryptedPassword result1, + EncryptedPassword result2, EncryptedPassword result3) { + if (!method.hasSeparateSalt()) { + throw new UnsupportedOperationException("Salt is not stored separately, so test should be initialized" + + " with the password hashes only. Use the other constructor"); + } this.method = method; hashes = new HashMap<>(); - hashes.put(GIVEN_PASSWORDS[0], result0.getHash()); - hashes.put(GIVEN_PASSWORDS[1], result1.getHash()); - hashes.put(GIVEN_PASSWORDS[2], result2.getHash()); - hashes.put(GIVEN_PASSWORDS[3], result3.getHash()); - - salts = new HashMap<>(); - salts.put(GIVEN_PASSWORDS[0], result0.getSalt()); - salts.put(GIVEN_PASSWORDS[1], result1.getSalt()); - salts.put(GIVEN_PASSWORDS[2], result2.getSalt()); - salts.put(GIVEN_PASSWORDS[3], result3.getSalt()); + hashes.put(GIVEN_PASSWORDS[0], result0); + hashes.put(GIVEN_PASSWORDS[1], result1); + hashes.put(GIVEN_PASSWORDS[2], result2); + hashes.put(GIVEN_PASSWORDS[3], result3); } @Test @@ -108,6 +103,7 @@ public abstract class AbstractEncryptionMethodTest { for (String password : internalPasswords) { final String salt = method.generateSalt(); final String hash = method.computeHash(password, salt, USERNAME); + EncryptedPassword encryptedPassword = new EncryptedPassword(hash, salt); // Check that the computeHash(password, salt, name) method has the same output for the returned salt if (testHashEqualityForSameSalt()) { @@ -116,22 +112,20 @@ public abstract class AbstractEncryptionMethodTest { } assertTrue("Generated hash for '" + password + "' should match password (hash = '" + hash + "')", - method.comparePassword(hash, password, salt, USERNAME)); + method.comparePassword(password, encryptedPassword, USERNAME)); if (!password.equals(password.toLowerCase())) { assertFalse("Lower-case of '" + password + "' should not match generated hash '" + hash + "'", - method.comparePassword(hash, password.toLowerCase(), salt, USERNAME)); + method.comparePassword(password.toLowerCase(), encryptedPassword, USERNAME)); } if (!password.equals(password.toUpperCase())) { assertFalse("Upper-case of '" + password + "' should not match generated hash '" + hash + "'", - method.comparePassword(hash, password.toUpperCase(), salt, USERNAME)); + method.comparePassword(password.toUpperCase(), encryptedPassword, USERNAME)); } } } private boolean doesGivenHashMatch(String password, EncryptionMethod method) { - String hash = hashes.get(password); - String salt = salts.get(password); - return method.comparePassword(hash, password, salt, USERNAME); + return method.comparePassword(password, hashes.get(password), USERNAME); } // @org.junit.Test public void a() { AbstractEncryptionMethodTest.generateTest(); } @@ -150,9 +144,9 @@ public abstract class AbstractEncryptionMethodTest { } if (method.hasSeparateSalt()) { - HashResult hashResult = method.computeHash(password, USERNAME); - System.out.println(String.format("\t\tnew HashResult(\"%s\", \"%s\")%s// %s", - hashResult.getHash(), hashResult.getSalt(), delim, password)); + EncryptedPassword encryptedPassword = method.computeHash(password, USERNAME); + System.out.println(String.format("\t\tnew EncryptedPassword(\"%s\", \"%s\")%s// %s", + encryptedPassword.getHash(), encryptedPassword.getSalt(), delim, password)); } else { System.out.println("\t\t\"" + method.computeHash(password, USERNAME).getHash() + "\"" + delim + "// " + password); diff --git a/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java b/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java index 53d3e4e1..0752b984 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java @@ -7,10 +7,10 @@ public class IPB3Test extends AbstractEncryptionMethodTest { public IPB3Test() { super(new IPB3(), - new HashResult("f8ecea1ce42b5babef369ff7692dbe3f", "1715b"), //password - new HashResult("40a93731a931352e0619cdf09b975040", "ba91c"), //PassWord1 - new HashResult("a77ca982373946d5800430bd2947ba11", "a7725"), //&^%te$t?Pw@_ - new HashResult("383d7b9e2b707d6e894ec7b30e3032c3", "fa9fd")); //âË_3(íù* + new EncryptedPassword("f8ecea1ce42b5babef369ff7692dbe3f", "1715b"), //password + new EncryptedPassword("40a93731a931352e0619cdf09b975040", "ba91c"), //PassWord1 + new EncryptedPassword("a77ca982373946d5800430bd2947ba11", "a7725"), //&^%te$t?Pw@_ + new EncryptedPassword("383d7b9e2b707d6e894ec7b30e3032c3", "fa9fd")); //âË_3(íù* } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java b/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java index 2d2e965a..01e3491d 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java @@ -7,10 +7,10 @@ public class MYBBTest extends AbstractEncryptionMethodTest { public MYBBTest() { super(new MYBB(), - new HashResult("57c7a16d860833db5030738f5a465d2b", "acdc14e6"), //password - new HashResult("08fbdf721f2c42d9780b7d66df0ba830", "792fd7fb"), //PassWord1 - new HashResult("d602f38fb59ad9e185d5604f5d4ddb36", "4b5534a4"), //&^%te$t?Pw@_ - new HashResult("b3c39410d0ab8ae2a65c257820797fad", "e5a6cb14")); //âË_3(íù* + new EncryptedPassword("57c7a16d860833db5030738f5a465d2b", "acdc14e6"), //password + new EncryptedPassword("08fbdf721f2c42d9780b7d66df0ba830", "792fd7fb"), //PassWord1 + new EncryptedPassword("d602f38fb59ad9e185d5604f5d4ddb36", "4b5534a4"), //&^%te$t?Pw@_ + new EncryptedPassword("b3c39410d0ab8ae2a65c257820797fad", "e5a6cb14")); //âË_3(íù* } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java b/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java index fe2259d2..66641470 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java @@ -7,10 +7,10 @@ public class PHPFUSIONTest extends AbstractEncryptionMethodTest { public PHPFUSIONTest() { super(new PHPFUSION(), - new HashResult("f7a606c4eb3fcfbc382906476e05b06f21234a77d1a4eacc0f93f503deb69e70", "6cd1c97c55cb"), // password - new HashResult("8a9b7bb706a3347e5f684a7cb905bfb26b9a0d099358064139ab3ed1a66aeb2b", "d6012370b73f"), // PassWord1 - new HashResult("43f2f23f44c8f89e2dbf06050bc8c77dbcdf71a7b5d28c87ec657d474e63d62d", "f75400a209a4"), // &^%te$t?Pw@_ - new HashResult("4e7f4eb7e3653d7460f1cf590def4153c6fcdf8b8e16fb95538fdf9e54a95245", "d552e0f5b23a")); // âË_3(íù* + new EncryptedPassword("f7a606c4eb3fcfbc382906476e05b06f21234a77d1a4eacc0f93f503deb69e70", "6cd1c97c55cb"), // password + new EncryptedPassword("8a9b7bb706a3347e5f684a7cb905bfb26b9a0d099358064139ab3ed1a66aeb2b", "d6012370b73f"), // PassWord1 + new EncryptedPassword("43f2f23f44c8f89e2dbf06050bc8c77dbcdf71a7b5d28c87ec657d474e63d62d", "f75400a209a4"), // &^%te$t?Pw@_ + new EncryptedPassword("4e7f4eb7e3653d7460f1cf590def4153c6fcdf8b8e16fb95538fdf9e54a95245", "d552e0f5b23a")); // âË_3(íù* } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java b/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java index 338238fd..56f01de3 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java @@ -17,10 +17,10 @@ public class SALTED2MD5Test extends AbstractEncryptionMethodTest { public SALTED2MD5Test() { super(new SALTED2MD5(), - new HashResult("9f3d13dc01a6fe61fd669954174399f3", "9b5f5749"), // password - new HashResult("b28c32f624a4eb161d6adc9acb5bfc5b", "f750ba32"), // PassWord1 - new HashResult("38dcb83cc68424afe3cda012700c2bb1", "eb2c3394"), // &^%te$t?Pw@_ - new HashResult("ad25606eae5b760c8a2469d65578ac39", "04eee598")); // âË_3(íù*) + new EncryptedPassword("9f3d13dc01a6fe61fd669954174399f3", "9b5f5749"), // password + new EncryptedPassword("b28c32f624a4eb161d6adc9acb5bfc5b", "f750ba32"), // PassWord1 + new EncryptedPassword("38dcb83cc68424afe3cda012700c2bb1", "eb2c3394"), // &^%te$t?Pw@_ + new EncryptedPassword("ad25606eae5b760c8a2469d65578ac39", "04eee598")); // âË_3(íù*) } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java b/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java index f88984e9..f2976cc6 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java @@ -7,10 +7,10 @@ public class SALTEDSHA512Test extends AbstractEncryptionMethodTest { public SALTEDSHA512Test() { super(new SALTEDSHA512(), - new HashResult("dea7a37cecf5384ae8e347fd1411efb51364b6ba1b328695de3b354612c1d7010807e8b7051c40f740e498490e1f133e2c2408327d13fbdd68e1b1f6d548e624", "29f8a3c52147f987fee7ba3e0fb311bd"), // password - new HashResult("7c06225aac574d2dc7c81a2ed306637adf025715f52083e05bdab014faaa234e24a97d0e69ea0108dfa77cc9228e58be319ee677e679b5d1ad168d40e50a42f6", "8ea37b85d020b98f60c0fe9b8ec9296c"), // PassWord1 - new HashResult("55711adbe03c9616f3505f0d57077fdd528c32243eb6f9840c1a6ff9e553940d6b89790750ebd52ebda63ca793fbe9980d54057af40836820c648750fe22d49c", "9f58079631ef21d32b4710694f1f461b"), // &^%te$t?Pw@_ - new HashResult("29dc5be8702975ea4563ed3de5b145e2d2f1c37ae661bbe0d3e94d964402cf09d539d65f3b90ff6921ea3d40727f76fb38fb34d1e5c2d62238c4e0203efc372f", "048bb76168265d906f1fd1f81d0616a9")); // âË_3(íù* + new EncryptedPassword("dea7a37cecf5384ae8e347fd1411efb51364b6ba1b328695de3b354612c1d7010807e8b7051c40f740e498490e1f133e2c2408327d13fbdd68e1b1f6d548e624", "29f8a3c52147f987fee7ba3e0fb311bd"), // password + new EncryptedPassword("7c06225aac574d2dc7c81a2ed306637adf025715f52083e05bdab014faaa234e24a97d0e69ea0108dfa77cc9228e58be319ee677e679b5d1ad168d40e50a42f6", "8ea37b85d020b98f60c0fe9b8ec9296c"), // PassWord1 + new EncryptedPassword("55711adbe03c9616f3505f0d57077fdd528c32243eb6f9840c1a6ff9e553940d6b89790750ebd52ebda63ca793fbe9980d54057af40836820c648750fe22d49c", "9f58079631ef21d32b4710694f1f461b"), // &^%te$t?Pw@_ + new EncryptedPassword("29dc5be8702975ea4563ed3de5b145e2d2f1c37ae661bbe0d3e94d964402cf09d539d65f3b90ff6921ea3d40727f76fb38fb34d1e5c2d62238c4e0203efc372f", "048bb76168265d906f1fd1f81d0616a9")); // âË_3(íù* } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java b/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java index 3851cb4e..2a5d685f 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java @@ -7,10 +7,10 @@ public class WBB3Test extends AbstractEncryptionMethodTest { public WBB3Test() { super(new WBB3(), - new HashResult("8df818ef7d56075ab2744f74b98ad68a375ccac4", "b7415b355492ea60314f259a35733a3092c03e3f"), // password - new HashResult("106da5cf5df92cb845e12cf62cbdb5235b6dc693", "6110f19b2b52910dccf592a19c59126873f42e69"), // PassWord1 - new HashResult("940a9fb7acec0178c6691e8b3c14bd7d789078b1", "f9dd501ff3d1bf74904f9e89649e378429af56e7"), // &^%te$t?Pw@_ - new HashResult("0fa12e8d96c9e95f73aa91f3b76f8cdc815ec8a5", "736be8669f6159ddb2d5b47a3e6428cdb8b324de")); // âË_3(íù* + new EncryptedPassword("8df818ef7d56075ab2744f74b98ad68a375ccac4", "b7415b355492ea60314f259a35733a3092c03e3f"), // password + new EncryptedPassword("106da5cf5df92cb845e12cf62cbdb5235b6dc693", "6110f19b2b52910dccf592a19c59126873f42e69"), // PassWord1 + new EncryptedPassword("940a9fb7acec0178c6691e8b3c14bd7d789078b1", "f9dd501ff3d1bf74904f9e89649e378429af56e7"), // &^%te$t?Pw@_ + new EncryptedPassword("0fa12e8d96c9e95f73aa91f3b76f8cdc815ec8a5", "736be8669f6159ddb2d5b47a3e6428cdb8b324de")); // âË_3(íù* } } From 8b60c66cc8595821f155c5fb9ff9c6b915fa7a62 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Wed, 30 Dec 2015 18:28:06 +0100 Subject: [PATCH 17/18] Minor - adjust PlayerAuth builder methods - Rename hash() to password() - Add location(Location) builder method - Replace usages of password(new EncryptedPassword(hash, salt)) to the more terse password(String, String) builder method --- src/main/java/fr/xephi/authme/api/API.java | 2 +- src/main/java/fr/xephi/authme/api/NewAPI.java | 2 +- .../xephi/authme/cache/auth/PlayerAuth.java | 23 +- .../authme/RegisterAdminCommand.java | 2 +- .../authme/converter/RakamakConverter.java | 2 +- .../fr/xephi/authme/datasource/MySQL.java | 233 +----------------- .../fr/xephi/authme/datasource/SQLite.java | 2 +- .../authme/listener/AuthMePlayerListener.java | 5 +- .../process/login/AsynchronousLogin.java | 2 +- .../process/register/AsyncRegister.java | 14 +- 10 files changed, 30 insertions(+), 257 deletions(-) diff --git a/src/main/java/fr/xephi/authme/api/API.java b/src/main/java/fr/xephi/authme/api/API.java index 2d799b8d..dac04986 100644 --- a/src/main/java/fr/xephi/authme/api/API.java +++ b/src/main/java/fr/xephi/authme/api/API.java @@ -140,7 +140,7 @@ public class API { } PlayerAuth auth = PlayerAuth.builder() .name(name) - .hash(encryptedPassword) + .password(encryptedPassword) .lastLogin(0) .realName(playerName) .build(); diff --git a/src/main/java/fr/xephi/authme/api/NewAPI.java b/src/main/java/fr/xephi/authme/api/NewAPI.java index 2990f1df..ed16fd52 100644 --- a/src/main/java/fr/xephi/authme/api/NewAPI.java +++ b/src/main/java/fr/xephi/authme/api/NewAPI.java @@ -155,7 +155,7 @@ public class NewAPI { } PlayerAuth auth = PlayerAuth.builder() .name(name) - .hash(result) + .password(result) .realName(playerName) .build(); return plugin.getDataSource().saveAuth(auth); diff --git a/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java b/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java index 64132afa..f172f221 100644 --- a/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java +++ b/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java @@ -1,6 +1,7 @@ package fr.xephi.authme.cache.auth; import fr.xephi.authme.security.crypts.EncryptedPassword; +import org.bukkit.Location; import static com.google.common.base.Objects.firstNonNull; import static com.google.common.base.Preconditions.checkNotNull; @@ -317,7 +318,7 @@ public class PlayerAuth { + " ! LastLogin : " + lastLogin + " ! LastPosition : " + x + "," + y + "," + z + "," + world + " ! Email : " + email - + " ! Hash : {" + password.getHash() + ", " + password.getSalt() + "}"; + + " ! Password : {" + password.getHash() + ", " + password.getSalt() + "}"; } /** @@ -368,7 +369,7 @@ public class PlayerAuth { public static final class Builder { private String name; private String realName; - private EncryptedPassword hash; + private EncryptedPassword password; private String ip; private String world; private String email; @@ -381,7 +382,7 @@ public class PlayerAuth { public PlayerAuth build() { return new PlayerAuth( checkNotNull(name), - firstNonNull(hash, new EncryptedPassword("")), + firstNonNull(password, new EncryptedPassword("")), groupId, firstNonNull(ip, "127.0.0.1"), lastLogin, @@ -402,13 +403,13 @@ public class PlayerAuth { return this; } - public Builder hash(EncryptedPassword hash) { - this.hash = hash; + public Builder password(EncryptedPassword password) { + this.password = password; return this; } - public Builder hash(String hash, String salt) { - return hash(new EncryptedPassword(hash, salt)); + public Builder password(String hash, String salt) { + return password(new EncryptedPassword(hash, salt)); } public Builder ip(String ip) { @@ -416,6 +417,14 @@ public class PlayerAuth { return this; } + public Builder location(Location location) { + this.x = location.getX(); + this.y = location.getY(); + this.z = location.getZ(); + this.world = location.getWorld().getName(); + return this; + } + public Builder locWorld(String world) { this.world = world; return this; diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java index 5131ac28..d652bdb5 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java @@ -62,7 +62,7 @@ public class RegisterAdminCommand implements ExecutableCommand { PlayerAuth auth = PlayerAuth.builder() .name(playerNameLowerCase) .realName(playerName) - .hash(encryptedPassword) + .password(encryptedPassword) .build(); if (!commandService.getDataSource().saveAuth(auth)) { diff --git a/src/main/java/fr/xephi/authme/converter/RakamakConverter.java b/src/main/java/fr/xephi/authme/converter/RakamakConverter.java index a86b6826..094eb668 100644 --- a/src/main/java/fr/xephi/authme/converter/RakamakConverter.java +++ b/src/main/java/fr/xephi/authme/converter/RakamakConverter.java @@ -78,7 +78,7 @@ public class RakamakConverter implements Converter { .name(playerName) .realName(playerName) .ip(ip) - .hash(psw) + .password(psw) .lastLogin(System.currentTimeMillis()) .build(); database.saveAuth(auth); diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index 245c6934..6981eb70 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -6,7 +6,6 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.security.HashAlgorithm; -import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.StringUtils; @@ -41,11 +40,6 @@ public class MySQL implements DataSource { private final List columnOthers; private HikariDataSource ds; - /** - * Constructor for MySQL. - * - * @throws ClassNotFoundException * @throws SQLException * @throws PoolInitializationException - */ public MySQL() throws ClassNotFoundException, SQLException, PoolInitializationException { this.host = Settings.getMySQLHost; this.port = Settings.getMySQLPort; @@ -98,11 +92,6 @@ public class MySQL implements DataSource { } } - /** - * Method setConnectionArguments. - * - * @throws RuntimeException - */ private synchronized void setConnectionArguments() throws RuntimeException { ds = new HikariDataSource(); ds.setPoolName("AuthMeMYSQLPool"); @@ -118,11 +107,6 @@ public class MySQL implements DataSource { ConsoleLogger.info("Connection arguments loaded, Hikari ConnectionPool ready!"); } - /** - * Method reloadArguments. - * - * @throws RuntimeException - */ private synchronized void reloadArguments() throws RuntimeException { if (ds != null) { ds.close(); @@ -131,20 +115,10 @@ public class MySQL implements DataSource { ConsoleLogger.info("Hikari ConnectionPool arguments reloaded!"); } - /** - * Method getConnection. - * - * @return Connection * @throws SQLException - */ private synchronized Connection getConnection() throws SQLException { return ds.getConnection(); } - /** - * Method setupConnection. - * - * @throws SQLException - */ private synchronized void setupConnection() throws SQLException { try (Connection con = getConnection()) { Statement st = con.createStatement(); @@ -246,13 +220,6 @@ public class MySQL implements DataSource { ConsoleLogger.info("MySQL Setup finished"); } - /** - * Method isAuthAvailable. - * - * @param user String - * - * @return boolean * @see fr.xephi.authme.datasource.DataSource#isAuthAvailable(String) - */ @Override public synchronized boolean isAuthAvailable(String user) { try (Connection con = getConnection()) { @@ -268,13 +235,6 @@ public class MySQL implements DataSource { return false; } - /** - * Method getAuth. - * - * @param user String - * - * @return PlayerAuth * @see fr.xephi.authme.datasource.DataSource#getAuth(String) - */ @Override public synchronized PlayerAuth getAuth(String user) { PlayerAuth pAuth; @@ -287,13 +247,11 @@ public class MySQL implements DataSource { return null; } String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : null; - String hash = rs.getString(columnPassword); int group = !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; - int id = rs.getInt(columnID); pAuth = PlayerAuth.builder() .name(rs.getString(columnName)) .realName(rs.getString(columnRealName)) - .hash(new EncryptedPassword(hash, salt)) + .password(rs.getString(columnPassword), salt) .lastLogin(rs.getLong(columnLastLogin)) .ip(rs.getString(columnIp)) .locWorld(rs.getString(lastlocWorld)) @@ -313,15 +271,6 @@ public class MySQL implements DataSource { return pAuth; } - /** - * Method saveAuth. - * - * @param auth PlayerAuth - * - * @return boolean - * - * @see fr.xephi.authme.datasource.DataSource#saveAuth(PlayerAuth) - */ @Override public synchronized boolean saveAuth(PlayerAuth auth) { try (Connection con = getConnection()) { @@ -503,15 +452,6 @@ public class MySQL implements DataSource { return false; } - /** - * Method updatePassword. - * - * @param auth PlayerAuth - * - * @return boolean - * - * @see fr.xephi.authme.datasource.DataSource#updatePassword(PlayerAuth) - */ @Override public synchronized boolean updatePassword(PlayerAuth auth) { try (Connection con = getConnection()) { @@ -541,15 +481,6 @@ public class MySQL implements DataSource { return false; } - /** - * Method updateSession. - * - * @param auth PlayerAuth - * - * @return boolean - * - * @see fr.xephi.authme.datasource.DataSource#updateSession(PlayerAuth) - */ @Override public synchronized boolean updateSession(PlayerAuth auth) { try (Connection con = getConnection()) { @@ -569,15 +500,6 @@ public class MySQL implements DataSource { return false; } - /** - * Method purgeDatabase. - * - * @param until long - * - * @return int - * - * @see fr.xephi.authme.datasource.DataSource#purgeDatabase(long) - */ @Override public synchronized int purgeDatabase(long until) { int result = 0; @@ -593,15 +515,6 @@ public class MySQL implements DataSource { return result; } - /** - * Method autoPurgeDatabase. - * - * @param until long - * - * @return List - * - * @see fr.xephi.authme.datasource.DataSource#autoPurgeDatabase(long) - */ @Override public synchronized List autoPurgeDatabase(long until) { List list = new ArrayList<>(); @@ -623,15 +536,6 @@ public class MySQL implements DataSource { return list; } - /** - * Method removeAuth. - * - * @param user String - * - * @return boolean - * - * @see fr.xephi.authme.datasource.DataSource#removeAuth(String) - */ @Override public synchronized boolean removeAuth(String user) { user = user.toLowerCase(); @@ -647,15 +551,6 @@ public class MySQL implements DataSource { return false; } - /** - * Method updateQuitLoc. - * - * @param auth PlayerAuth - * - * @return boolean - * - * @see fr.xephi.authme.datasource.DataSource#updateQuitLoc(PlayerAuth) - */ @Override public synchronized boolean updateQuitLoc(PlayerAuth auth) { try (Connection con = getConnection()) { @@ -678,15 +573,6 @@ public class MySQL implements DataSource { return false; } - /** - * Method getIps. - * - * @param ip String - * - * @return int - * - * @see fr.xephi.authme.datasource.DataSource#getIps(String) - */ @Override public synchronized int getIps(String ip) { int countIp = 0; @@ -707,15 +593,6 @@ public class MySQL implements DataSource { return countIp; } - /** - * Method updateEmail. - * - * @param auth PlayerAuth - * - * @return boolean - * - * @see fr.xephi.authme.datasource.DataSource#updateEmail(PlayerAuth) - */ @Override public synchronized boolean updateEmail(PlayerAuth auth) { try (Connection con = getConnection()) { @@ -733,11 +610,6 @@ public class MySQL implements DataSource { return false; } - /** - * Method reload. - * - * @see fr.xephi.authme.datasource.DataSource#reload() - */ @Override public void reload() { try { @@ -750,11 +622,6 @@ public class MySQL implements DataSource { } } - /** - * Method close. - * - * @see fr.xephi.authme.datasource.DataSource#close() - */ @Override public synchronized void close() { if (ds != null && !ds.isClosed()) { @@ -762,15 +629,6 @@ public class MySQL implements DataSource { } } - /** - * Method getAllAuthsByName. - * - * @param auth PlayerAuth - * - * @return List - * - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByName(PlayerAuth) - */ @Override public synchronized List getAllAuthsByName(PlayerAuth auth) { List result = new ArrayList<>(); @@ -791,15 +649,6 @@ public class MySQL implements DataSource { return result; } - /** - * Method getAllAuthsByIp. - * - * @param ip String - * - * @return List - * - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByIp(String) - */ @Override public synchronized List getAllAuthsByIp(String ip) { List result = new ArrayList<>(); @@ -820,15 +669,6 @@ public class MySQL implements DataSource { return result; } - /** - * Method getAllAuthsByEmail. - * - * @param email String - * - * @return List - * - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByEmail(String) - */ @Override public synchronized List getAllAuthsByEmail(String email){ List countEmail = new ArrayList<>(); @@ -849,13 +689,6 @@ public class MySQL implements DataSource { return countEmail; } - /** - * Method purgeBanned. - * - * @param banned List - * - * @see fr.xephi.authme.datasource.DataSource#purgeBanned(List) - */ @Override public synchronized void purgeBanned(List banned) { try (Connection con = getConnection()) { @@ -871,23 +704,11 @@ public class MySQL implements DataSource { } } - /** - * Method getType. - * - * @return DataSourceType * @see fr.xephi.authme.datasource.DataSource#getType() - */ @Override public DataSourceType getType() { return DataSourceType.MYSQL; } - /** - * Method isLogged. - * - * @param user String - * - * @return boolean * @see fr.xephi.authme.datasource.DataSource#isLogged(String) - */ @Override public boolean isLogged(String user) { boolean isLogged = false; @@ -904,13 +725,6 @@ public class MySQL implements DataSource { return isLogged; } - /** - * Method setLogged. - * - * @param user String - * - * @see fr.xephi.authme.datasource.DataSource#setLogged(String) - */ @Override public void setLogged(String user) { try (Connection con = getConnection()) { @@ -926,13 +740,6 @@ public class MySQL implements DataSource { } } - /** - * Method setUnlogged. - * - * @param user String - * - * @see fr.xephi.authme.datasource.DataSource#setUnlogged(String) - */ @Override public void setUnlogged(String user) { try (Connection con = getConnection()) { @@ -948,11 +755,6 @@ public class MySQL implements DataSource { } } - /** - * Method purgeLogged. - * - * @see fr.xephi.authme.datasource.DataSource#purgeLogged() - */ @Override public void purgeLogged() { try (Connection con = getConnection()) { @@ -968,13 +770,6 @@ public class MySQL implements DataSource { } } - /** - * Method getAccountsRegistered. - * - * @return int - * - * @see fr.xephi.authme.datasource.DataSource#getAccountsRegistered() - */ @Override public int getAccountsRegistered() { int result = 0; @@ -993,14 +788,6 @@ public class MySQL implements DataSource { return result; } - /** - * Method updateName. - * - * @param oldOne String - * @param newOne String - * - * @see fr.xephi.authme.datasource.DataSource#updateName(String, String) - */ @Override public void updateName(String oldOne, String newOne) { try (Connection con = getConnection()) { @@ -1015,13 +802,6 @@ public class MySQL implements DataSource { } } - /** - * Method getAllAuths. - * - * @return List - * - * @see fr.xephi.authme.datasource.DataSource#getAllAuths() - */ @Override public List getAllAuths() { List auths = new ArrayList<>(); @@ -1035,7 +815,7 @@ public class MySQL implements DataSource { PlayerAuth pAuth = PlayerAuth.builder() .name(rs.getString(columnName)) .realName(rs.getString(columnRealName)) - .hash(new EncryptedPassword(rs.getString(columnPassword), salt)) + .password(rs.getString(columnPassword), salt) .lastLogin(rs.getLong(columnLastLogin)) .ip(rs.getString(columnIp)) .locWorld(rs.getString(lastlocWorld)) @@ -1058,13 +838,6 @@ public class MySQL implements DataSource { return auths; } - /** - * Method getLoggedPlayers. - * - * @return List - * - * @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers() - */ @Override public List getLoggedPlayers() { List auths = new ArrayList<>(); @@ -1078,7 +851,7 @@ public class MySQL implements DataSource { PlayerAuth pAuth = PlayerAuth.builder() .name(rs.getString(columnName)) .realName(rs.getString(columnRealName)) - .hash(new EncryptedPassword(rs.getString(columnPassword), salt)) + .password(rs.getString(columnPassword), salt) .lastLogin(rs.getLong(columnLastLogin)) .ip(rs.getString(columnIp)) .locWorld(rs.getString(lastlocWorld)) diff --git a/src/main/java/fr/xephi/authme/datasource/SQLite.java b/src/main/java/fr/xephi/authme/datasource/SQLite.java index 590dd6bf..bc411770 100644 --- a/src/main/java/fr/xephi/authme/datasource/SQLite.java +++ b/src/main/java/fr/xephi/authme/datasource/SQLite.java @@ -816,7 +816,7 @@ public class SQLite implements DataSource { .name(row.getString(columnName)) .email(row.getString(columnEmail)) .realName(row.getString(columnRealName)) - .hash(row.getString(columnPassword), salt) + .password(row.getString(columnPassword), salt) .lastLogin(row.getLong(columnLastLogin)) .locX(row.getDouble(lastlocX)) .locY(row.getDouble(lastlocY)) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index a5a6931f..42a11b3f 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -480,10 +480,7 @@ public class AuthMePlayerListener implements Listener { PlayerAuth auth = PlayerAuth.builder() .name(name) .realName(player.getName()) - .locX(spawn.getX()) - .locY(spawn.getY()) - .locZ(spawn.getZ()) - .locWorld(spawn.getWorld().getName()) + .location(spawn) .build(); plugin.getDataSource().updateQuitLoc(auth); } diff --git a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java index a4d8d299..8297224b 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -146,7 +146,7 @@ public class AsynchronousLogin { .ip(getIP()) .lastLogin(new Date().getTime()) .email(email) - .hash(pAuth.getPassword()) + .password(pAuth.getPassword()) .build(); database.updateSession(auth); diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java index 13baacae..3f2cbd0e 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -101,12 +101,9 @@ public class AsyncRegister { PlayerAuth auth = PlayerAuth.builder() .name(name) .realName(player.getName()) - .hash(encryptedPassword) + .password(encryptedPassword) .ip(ip) - .locWorld(player.getLocation().getWorld().getName()) - .locX(player.getLocation().getX()) - .locY(player.getLocation().getY()) - .locZ(player.getLocation().getZ()) + .location(player.getLocation()) .email(email) .build(); @@ -127,12 +124,9 @@ public class AsyncRegister { PlayerAuth auth = PlayerAuth.builder() .name(name) .realName(player.getName()) - .hash(encryptedPassword) + .password(encryptedPassword) .ip(ip) - .locWorld(player.getLocation().getWorld().getName()) - .locX(player.getLocation().getX()) - .locY(player.getLocation().getY()) - .locZ(player.getLocation().getZ()) + .location(player.getLocation()) .build(); if (!database.saveAuth(auth)) { From 332865613451997b276694ed531c1ccb08f321f9 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Wed, 30 Dec 2015 21:36:07 +0100 Subject: [PATCH 18/18] #358 Create test for PasswordSecurity, create salt column if not exists - Add test class for PasswordSecurity - Check and create the salt column in MySQL and SQLite when necessary - Add javadoc to some classes --- src/main/java/fr/xephi/authme/AuthMe.java | 8 +- .../fr/xephi/authme/datasource/MySQL.java | 9 + .../fr/xephi/authme/datasource/SQLite.java | 21 +- .../events/PasswordEncryptionEvent.java | 9 +- .../xephi/authme/security/HashAlgorithm.java | 42 ++-- .../fr/xephi/authme/security/HashUtils.java | 41 +++- .../authme/security/PasswordSecurity.java | 11 +- .../xephi/authme/security/RandomString.java | 16 ++ .../authme/security/PasswordSecurityTest.java | 224 ++++++++++++++++++ 9 files changed, 341 insertions(+), 40 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 64d293a5..04ff2a32 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -212,7 +212,6 @@ public class AuthMe extends JavaPlugin { // Set up messages & password security messages = Messages.getInstance(); - passwordSecurity = new PasswordSecurity(getDataSource(), Settings.getPasswordHash, Settings.supportOldPassword); // Connect to the database and setup tables try { @@ -225,6 +224,9 @@ public class AuthMe extends JavaPlugin { return; } + passwordSecurity = new PasswordSecurity(getDataSource(), Settings.getPasswordHash, + Bukkit.getPluginManager(), Settings.supportOldPassword); + // Set up the permissions manager and command handler permsMan = initializePermissionsManager(); commandHandler = initializeCommandHandler(permsMan, messages, passwordSecurity); @@ -525,7 +527,9 @@ public class AuthMe extends JavaPlugin { new PerformBackup(plugin).doBackup(PerformBackup.BackupCause.STOP); // Unload modules - moduleManager.unloadModules(); + if (moduleManager != null) { + moduleManager.unloadModules(); + } // Close the database if (database != null) { diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index 6981eb70..1af04cab 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -162,6 +162,15 @@ public class MySQL implements DataSource { } rs.close(); + if (!columnSalt.isEmpty()) { + rs = md.getColumns(null, null, tableName, columnSalt); + if (!rs.next()) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN " + columnSalt + " VARCHAR(255);"); + } + rs.close(); + } + rs = md.getColumns(null, null, tableName, columnIp); if (!rs.next()) { st.executeUpdate("ALTER TABLE " + tableName diff --git a/src/main/java/fr/xephi/authme/datasource/SQLite.java b/src/main/java/fr/xephi/authme/datasource/SQLite.java index bc411770..2d04662b 100644 --- a/src/main/java/fr/xephi/authme/datasource/SQLite.java +++ b/src/main/java/fr/xephi/authme/datasource/SQLite.java @@ -4,6 +4,7 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.util.StringUtils; import java.sql.*; import java.util.ArrayList; @@ -91,6 +92,13 @@ public class SQLite implements DataSource { st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + columnPassword + " VARCHAR(255) NOT NULL;"); } rs.close(); + if (!columnSalt.isEmpty()) { + rs = con.getMetaData().getColumns(null, null, tableName, columnSalt); + if (!rs.next()) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + columnSalt + " VARCHAR(255);"); + } + rs.close(); + } rs = con.getMetaData().getColumns(null, null, tableName, columnIp); if (!rs.next()) { st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + columnIp + " VARCHAR(40) NOT NULL;"); @@ -200,7 +208,11 @@ public class SQLite implements DataSource { PreparedStatement pst = null; try { EncryptedPassword password = auth.getPassword(); - if (columnSalt.isEmpty() && password.getSalt().isEmpty()) { + if (columnSalt.isEmpty()) { + if (!StringUtils.isEmpty(auth.getPassword().getSalt())) { + ConsoleLogger.showError("Warning! Detected hashed password with separate salt but the salt column " + + "is not set in the config!"); + } pst = con.prepareStatement("INSERT INTO " + tableName + "(" + columnName + "," + columnPassword + "," + columnIp + "," + columnLastLogin + "," + columnRealName + ") VALUES (?,?,?,?,?);"); pst.setString(1, auth.getNickname()); @@ -592,13 +604,6 @@ public class SQLite implements DataSource { } } - /** - * Method purgeBanned. - * - * @param banned List - * - * @see fr.xephi.authme.datasource.DataSource#purgeBanned(List) - */ @Override public void purgeBanned(List banned) { PreparedStatement pst = null; diff --git a/src/main/java/fr/xephi/authme/events/PasswordEncryptionEvent.java b/src/main/java/fr/xephi/authme/events/PasswordEncryptionEvent.java index 8519778b..572d1c9a 100644 --- a/src/main/java/fr/xephi/authme/events/PasswordEncryptionEvent.java +++ b/src/main/java/fr/xephi/authme/events/PasswordEncryptionEvent.java @@ -5,16 +5,17 @@ import org.bukkit.event.Event; import org.bukkit.event.HandlerList; /** - * This event is called when we need to compare or get an hash password, for set - * a custom EncryptionMethod + * This event is called when we need to compare or hash password and allows + * third-party listeners to change the encryption method. This is typically + * done with the {@link fr.xephi.authme.security.HashAlgorithm#CUSTOM} setting. * * @author Xephi59 */ public class PasswordEncryptionEvent extends Event { private static final HandlerList handlers = new HandlerList(); - private EncryptionMethod method = null; - private String playerName = ""; + private EncryptionMethod method; + private String playerName; public PasswordEncryptionEvent(EncryptionMethod method, String playerName) { super(false); diff --git a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java index 607df6b2..24a702dd 100644 --- a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java +++ b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java @@ -9,33 +9,33 @@ import fr.xephi.authme.security.crypts.EncryptionMethod; */ public enum HashAlgorithm { - MD5(fr.xephi.authme.security.crypts.MD5.class), - SHA1(fr.xephi.authme.security.crypts.SHA1.class), - SHA256(fr.xephi.authme.security.crypts.SHA256.class), - WHIRLPOOL(fr.xephi.authme.security.crypts.WHIRLPOOL.class), - XAUTH(fr.xephi.authme.security.crypts.XAUTH.class), - MD5VB(fr.xephi.authme.security.crypts.MD5VB.class), - PHPBB(fr.xephi.authme.security.crypts.PHPBB.class), - @Deprecated - PLAINTEXT(fr.xephi.authme.security.crypts.PLAINTEXT.class), - MYBB(fr.xephi.authme.security.crypts.MYBB.class), - IPB3(fr.xephi.authme.security.crypts.IPB3.class), - PHPFUSION(fr.xephi.authme.security.crypts.PHPFUSION.class), - SMF(fr.xephi.authme.security.crypts.SMF.class), - SALTED2MD5(fr.xephi.authme.security.crypts.SALTED2MD5.class), - JOOMLA(fr.xephi.authme.security.crypts.JOOMLA.class), BCRYPT(fr.xephi.authme.security.crypts.BCRYPT.class), - WBB3(fr.xephi.authme.security.crypts.WBB3.class), - WBB4(fr.xephi.authme.security.crypts.WBB4.class), - SHA512(fr.xephi.authme.security.crypts.SHA512.class), + BCRYPT2Y(fr.xephi.authme.security.crypts.BCRYPT2Y.class), + CRAZYCRYPT1(fr.xephi.authme.security.crypts.CRAZYCRYPT1.class), DOUBLEMD5(fr.xephi.authme.security.crypts.DOUBLEMD5.class), + IPB3(fr.xephi.authme.security.crypts.IPB3.class), + JOOMLA(fr.xephi.authme.security.crypts.JOOMLA.class), + 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(fr.xephi.authme.security.crypts.CryptPBKDF2.class), PBKDF2DJANGO(fr.xephi.authme.security.crypts.CryptPBKDF2Django.class), - WORDPRESS(fr.xephi.authme.security.crypts.WORDPRESS.class), + PHPBB(fr.xephi.authme.security.crypts.PHPBB.class), + PHPFUSION(fr.xephi.authme.security.crypts.PHPFUSION.class), + @Deprecated + PLAINTEXT(fr.xephi.authme.security.crypts.PLAINTEXT.class), ROYALAUTH(fr.xephi.authme.security.crypts.ROYALAUTH.class), - CRAZYCRYPT1(fr.xephi.authme.security.crypts.CRAZYCRYPT1.class), - BCRYPT2Y(fr.xephi.authme.security.crypts.BCRYPT2Y.class), + SALTED2MD5(fr.xephi.authme.security.crypts.SALTED2MD5.class), SALTEDSHA512(fr.xephi.authme.security.crypts.SALTEDSHA512.class), + SHA1(fr.xephi.authme.security.crypts.SHA1.class), + SHA256(fr.xephi.authme.security.crypts.SHA256.class), + SHA512(fr.xephi.authme.security.crypts.SHA512.class), + SMF(fr.xephi.authme.security.crypts.SMF.class), + WBB3(fr.xephi.authme.security.crypts.WBB3.class), + WBB4(fr.xephi.authme.security.crypts.WBB4.class), + WHIRLPOOL(fr.xephi.authme.security.crypts.WHIRLPOOL.class), + WORDPRESS(fr.xephi.authme.security.crypts.WORDPRESS.class), + XAUTH(fr.xephi.authme.security.crypts.XAUTH.class), CUSTOM(null); private final Class clazz; diff --git a/src/main/java/fr/xephi/authme/security/HashUtils.java b/src/main/java/fr/xephi/authme/security/HashUtils.java index c5ae4f89..c4fb4edf 100644 --- a/src/main/java/fr/xephi/authme/security/HashUtils.java +++ b/src/main/java/fr/xephi/authme/security/HashUtils.java @@ -4,28 +4,60 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; - +/** + * Hashing utilities (interface for common hashing algorithms). + */ public final class HashUtils { private HashUtils() { } + /** + * Generate the SHA-1 digest of the given message. + * + * @param message The message to hash + * @return The resulting SHA-1 digest + */ public static String sha1(String message) { return hash(message, MessageDigestAlgorithm.SHA1); } + /** + * Generate the SHA-256 digest of the given message. + * + * @param message The message to hash + * @return The resulting SHA-256 digest + */ public static String sha256(String message) { return hash(message, MessageDigestAlgorithm.SHA256); } + /** + * Generate the SHA-512 digest of the given message. + * + * @param message The message to hash + * @return The resulting SHA-512 digest + */ public static String sha512(String message) { return hash(message, MessageDigestAlgorithm.SHA512); } + /** + * Generate the MD5 digest of the given message. + * + * @param message The message to hash + * @return The resulting MD5 digest + */ public static String md5(String message) { return hash(message, MessageDigestAlgorithm.MD5); } + /** + * Return a {@link MessageDigest} instance for the given algorithm. + * + * @param algorithm The desired algorithm + * @return MessageDigest instance for the given algorithm + */ public static MessageDigest getDigest(MessageDigestAlgorithm algorithm) { try { return MessageDigest.getInstance(algorithm.getKey()); @@ -35,6 +67,13 @@ public final class HashUtils { } } + /** + * Hash the message with the given algorithm and return the hash in its hexadecimal notation. + * + * @param message The message to hash + * @param algorithm The algorithm to hash the message with + * @return The digest in its hexadecimal representation + */ private static String hash(String message, MessageDigestAlgorithm algorithm) { MessageDigest md = getDigest(algorithm); md.reset(); diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index 9ea1ccfa..ba1d2e02 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -5,7 +5,7 @@ import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.PasswordEncryptionEvent; import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.security.crypts.EncryptionMethod; -import org.bukkit.Bukkit; +import org.bukkit.plugin.PluginManager; /** * Manager class for password-related operations. @@ -14,11 +14,14 @@ public class PasswordSecurity { private final DataSource dataSource; private final HashAlgorithm algorithm; + private final PluginManager pluginManager; private final boolean supportOldAlgorithm; - public PasswordSecurity(DataSource dataSource, HashAlgorithm algorithm, boolean supportOldAlgorithm) { + public PasswordSecurity(DataSource dataSource, HashAlgorithm algorithm, + PluginManager pluginManager, boolean supportOldAlgorithm) { this.dataSource = dataSource; this.algorithm = algorithm; + this.pluginManager = pluginManager; this.supportOldAlgorithm = supportOldAlgorithm; } @@ -86,10 +89,10 @@ public class PasswordSecurity { * @param playerName The name of the player a password will be hashed for * @return The encryption method */ - private static EncryptionMethod initializeEncryptionMethod(HashAlgorithm algorithm, String playerName) { + private EncryptionMethod initializeEncryptionMethod(HashAlgorithm algorithm, String playerName) { EncryptionMethod method = initializeEncryptionMethodWithoutEvent(algorithm); PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName); - Bukkit.getPluginManager().callEvent(event); + pluginManager.callEvent(event); return event.getMethod(); } diff --git a/src/main/java/fr/xephi/authme/security/RandomString.java b/src/main/java/fr/xephi/authme/security/RandomString.java index cff87ef0..40274305 100644 --- a/src/main/java/fr/xephi/authme/security/RandomString.java +++ b/src/main/java/fr/xephi/authme/security/RandomString.java @@ -3,6 +3,9 @@ package fr.xephi.authme.security; import java.security.SecureRandom; import java.util.Random; +/** + * Utility for generating random strings. + */ public final class RandomString { private static final char[] chars = new char[36]; @@ -21,10 +24,23 @@ public final class RandomString { private RandomString() { } + /** + * Generate a string of the given length consisting of random characters within the range [0-9a-z]. + * + * @param length The length of the random string to generate + * @return The random string + */ public static String generate(int length) { return generate(length, chars.length); } + /** + * Generate a random hexadecimal string of the given length. In other words, the generated string + * contains characters only within the range [0-9a-f]. + * + * @param length The length of the random string to generate + * @return The random hexadecimal string + */ public static String generateHex(int length) { return generate(length, HEX_MAX_INDEX); } diff --git a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java new file mode 100644 index 00000000..ae8e49e7 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java @@ -0,0 +1,224 @@ +package fr.xephi.authme.security; + +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.events.PasswordEncryptionEvent; +import fr.xephi.authme.security.crypts.EncryptedPassword; +import fr.xephi.authme.security.crypts.EncryptionMethod; +import fr.xephi.authme.security.crypts.JOOMLA; +import fr.xephi.authme.security.crypts.PHPBB; +import org.bukkit.event.Event; +import org.bukkit.plugin.PluginManager; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Test for {@link PasswordSecurity}. + */ +public class PasswordSecurityTest { + + private PluginManager pluginManager; + private DataSource dataSource; + private EncryptionMethod method; + private Class caughtClassInEvent; + + @Before + public void setUpMocks() { + pluginManager = mock(PluginManager.class); + dataSource = mock(DataSource.class); + method = mock(EncryptionMethod.class); + caughtClassInEvent = null; + + // When the password encryption event is emitted, replace the encryption method with our mock. + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + Object[] arguments = invocation.getArguments(); + if (arguments[0] instanceof PasswordEncryptionEvent) { + PasswordEncryptionEvent event = (PasswordEncryptionEvent) arguments[0]; + caughtClassInEvent = event.getMethod() != null ? event.getMethod().getClass() : null; + event.setMethod(method); + } + return null; + } + }).when(pluginManager).callEvent(any(Event.class)); + } + + @Test + public void shouldReturnPasswordMatch() { + // given + EncryptedPassword password = new EncryptedPassword("$TEST$10$SOME_HASH", null); + String playerName = "Tester"; + String clearTextPass = "myPassTest"; + + PlayerAuth auth = mock(PlayerAuth.class); + given(auth.getPassword()).willReturn(password); + given(dataSource.getAuth(playerName)).willReturn(auth); + given(method.comparePassword(clearTextPass, password, playerName)).willReturn(true); + PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.BCRYPT, pluginManager, false); + + // when + boolean result = security.comparePassword(clearTextPass, playerName); + + // then + assertThat(result, equalTo(true)); + verify(dataSource).getAuth(playerName); + verify(pluginManager).callEvent(any(PasswordEncryptionEvent.class)); + verify(method).comparePassword(clearTextPass, password, playerName); + } + + @Test + public void shouldReturnPasswordMismatch() { + // given + EncryptedPassword password = new EncryptedPassword("$TEST$10$SOME_HASH", null); + String playerName = "My_PLayer"; + String clearTextPass = "passw0Rd1"; + + PlayerAuth auth = mock(PlayerAuth.class); + given(auth.getPassword()).willReturn(password); + given(dataSource.getAuth(playerName)).willReturn(auth); + given(method.comparePassword(clearTextPass, password, playerName)).willReturn(false); + PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.CUSTOM, pluginManager, false); + + // when + boolean result = security.comparePassword(clearTextPass, playerName); + + // then + assertThat(result, equalTo(false)); + verify(dataSource).getAuth(playerName); + verify(pluginManager).callEvent(any(PasswordEncryptionEvent.class)); + verify(method).comparePassword(clearTextPass, password, playerName); + } + + @Test + public void shouldReturnFalseIfPlayerDoesNotExist() { + // given + String playerName = "bobby"; + String clearTextPass = "tables"; + + given(dataSource.getAuth(playerName)).willReturn(null); + PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.MD5, pluginManager, false); + + // when + boolean result = security.comparePassword(clearTextPass, playerName); + + // then + assertThat(result, equalTo(false)); + verify(dataSource).getAuth(playerName); + verify(pluginManager, never()).callEvent(any(Event.class)); + verify(method, never()).comparePassword(anyString(), any(EncryptedPassword.class), anyString()); + } + + @Test + public void shouldTryOtherMethodsForFailedPassword() { + // given + // BCRYPT2Y hash for "Test" + EncryptedPassword password = + new EncryptedPassword("$2y$10$2e6d2193f43501c926e25elvWlPmWczmrfrnbZV0dUZGITjYjnkkW"); + String playerName = "somePlayer"; + String clearTextPass = "Test"; + // MD5 hash for "Test" + EncryptedPassword newPassword = new EncryptedPassword("0cbc6611f5540bd0809a388dc95a615b"); + + PlayerAuth auth = mock(PlayerAuth.class); + doCallRealMethod().when(auth).getPassword(); + doCallRealMethod().when(auth).setPassword(any(EncryptedPassword.class)); + auth.setPassword(password); + given(dataSource.getAuth(playerName)).willReturn(auth); + given(method.comparePassword(clearTextPass, password, playerName)).willReturn(false); + given(method.computeHash(clearTextPass, playerName)).willReturn(newPassword); + PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.MD5, pluginManager, true); + + // when + boolean result = security.comparePassword(clearTextPass, playerName); + + // then + assertThat(result, equalTo(true)); + verify(dataSource, times(2)).getAuth(playerName); + verify(pluginManager, times(2)).callEvent(any(PasswordEncryptionEvent.class)); + verify(method).comparePassword(clearTextPass, password, playerName); + verify(auth).setPassword(newPassword); + + ArgumentCaptor captor = ArgumentCaptor.forClass(PlayerAuth.class); + verify(dataSource).updatePassword(captor.capture()); + assertThat(captor.getValue().getPassword(), equalTo(newPassword)); + } + + @Test + public void shouldHashPassword() { + // given + String password = "MyP@ssword"; + String username = "theUserInTest"; + EncryptedPassword encryptedPassword = new EncryptedPassword("$T$est#Hash", "__someSalt__"); + given(method.computeHash(password, username)).willReturn(encryptedPassword); + PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.JOOMLA, pluginManager, true); + + // when + EncryptedPassword result = security.computeHash(password, username); + + // then + assertThat(result, equalTo(encryptedPassword)); + ArgumentCaptor captor = ArgumentCaptor.forClass(PasswordEncryptionEvent.class); + verify(pluginManager).callEvent(captor.capture()); + PasswordEncryptionEvent event = captor.getValue(); + assertThat(JOOMLA.class.equals(caughtClassInEvent), equalTo(true)); + assertThat(event.getPlayerName(), equalTo(username)); + } + + @Test + public void shouldHashPasswordWithGivenAlgorithm() { + // given + String password = "TopSecretPass#112525"; + String username = "someone12"; + EncryptedPassword encryptedPassword = new EncryptedPassword("~T!est#Hash", "__someSalt__"); + given(method.computeHash(password, username)).willReturn(encryptedPassword); + PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.JOOMLA, pluginManager, true); + + // when + EncryptedPassword result = security.computeHash(HashAlgorithm.PHPBB, password, username); + + // then + assertThat(result, equalTo(encryptedPassword)); + ArgumentCaptor captor = ArgumentCaptor.forClass(PasswordEncryptionEvent.class); + verify(pluginManager).callEvent(captor.capture()); + PasswordEncryptionEvent event = captor.getValue(); + assertThat(PHPBB.class.equals(caughtClassInEvent), equalTo(true)); + assertThat(event.getPlayerName(), equalTo(username)); + } + + @Test + public void shouldSkipCheckIfMandatorySaltIsUnavailable() { + // given + String password = "?topSecretPass\\"; + String username = "someone12"; + EncryptedPassword encryptedPassword = new EncryptedPassword("~T!est#Hash"); + given(method.computeHash(password, username)).willReturn(encryptedPassword); + given(method.hasSeparateSalt()).willReturn(true); + PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.XAUTH, pluginManager, true); + + // when + boolean result = security.comparePassword(password, encryptedPassword, username); + + // then + assertThat(result, equalTo(false)); + verify(dataSource, never()).getAuth(anyString()); + verify(pluginManager).callEvent(any(PasswordEncryptionEvent.class)); + verify(method, never()).comparePassword(anyString(), any(EncryptedPassword.class), anyString()); + } + +}