From 90a03251945624cd8d00cfc2b3ba8fc1261babb1 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 26 Dec 2015 23:59:32 +0100 Subject: [PATCH] #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 f20fd7fde..8519778b2 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 000000000..0f4f7318d --- /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 000000000..51ff2a5d4 --- /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 c94644e9b..beea0fe4e 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 d90af6e48..90901cf9c 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 336d8a4a7..a48da8f15 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 728023beb..e375ae15a 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 36e6dfe3d..c4df8ab40 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 58d30a667..e1cfff9af 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 f925c10d1..04add2be1 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 41f6fe354..36c09244c 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 8e20dbf4a..c3110c94b 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 e54f8ad3c..cbd30eb2c 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 f41c01e96..2803b1c24 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 0ce0c2e56..74229adb1 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 3147ddbbb..650591199 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 aa287ddd7..da0591aa8 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 000000000..b903a88d7 --- /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 000000000..06ebc22c9 --- /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 000000000..c7d3ba1d5 --- /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 000000000..990ee2984 --- /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 ea18df9a9..d573b2886 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 000000000..fa27de3b1 --- /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(íù* + } + +}