#358 Make RandomString static & generate all rand. strings with it

- Remove dubious random String generator on HashUtils
- Make further hash classes use HashUtils
This commit is contained in:
ljacqu 2015-12-27 22:16:16 +01:00
parent 90a0325194
commit 513ff9a928
17 changed files with 125 additions and 123 deletions

View File

@ -41,7 +41,7 @@ public class CaptchaCommand extends PlayerCommand {
if (Settings.useCaptcha && !captcha.equals(plugin.cap.get(playerNameLowerCase))) { if (Settings.useCaptcha && !captcha.equals(plugin.cap.get(playerNameLowerCase))) {
plugin.cap.remove(playerNameLowerCase); plugin.cap.remove(playerNameLowerCase);
String randStr = new RandomString(Settings.captchaLength).nextString(); String randStr = RandomString.generate(Settings.captchaLength);
plugin.cap.put(playerNameLowerCase, randStr); plugin.cap.put(playerNameLowerCase, randStr);
commandService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, plugin.cap.get(playerNameLowerCase)); commandService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, plugin.cap.get(playerNameLowerCase));
return; return;

View File

@ -38,8 +38,7 @@ public class RecoverEmailCommand extends PlayerCommand {
return; return;
} }
try { try {
RandomString rand = new RandomString(Settings.getRecoveryPassLength); String thePass = RandomString.generate(Settings.getRecoveryPassLength);
String thePass = rand.nextString();
String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, thePass, playerName); String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, thePass, playerName);
PlayerAuth auth; PlayerAuth auth;
if (PlayerCache.getInstance().isAuthenticated(playerName)) { if (PlayerCache.getInstance().isAuthenticated(playerName)) {

View File

@ -26,12 +26,11 @@ import java.util.List;
*/ */
public class AsynchronousLogin { public class AsynchronousLogin {
private static final RandomString rdm = new RandomString(Settings.captchaLength); private final Player player;
protected final Player player; private final String name;
protected final String name; private final String realName;
protected final String realName; private final String password;
protected final String password; private final boolean forceLogin;
protected final boolean forceLogin;
private final AuthMe plugin; private final AuthMe plugin;
private final DataSource database; private final DataSource database;
private final Messages m; private final Messages m;
@ -70,7 +69,7 @@ public class AsynchronousLogin {
plugin.captcha.putIfAbsent(name, i); plugin.captcha.putIfAbsent(name, i);
} }
if (plugin.captcha.containsKey(name) && plugin.captcha.get(name) > Settings.maxLoginTry) { 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)); m.send(player, MessageKey.USAGE_CAPTCHA, plugin.cap.get(name));
return true; return true;
} }

View File

@ -3,40 +3,27 @@ package fr.xephi.authme.security;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public final class HashUtils { public final class HashUtils {
private static final SecureRandom RANDOM = new SecureRandom();
private HashUtils() { 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) { public static String sha1(String message) {
return hash(message, MessageDigestAlgorithm.SHA1); return hash(message, MessageDigestAlgorithm.SHA1);
} }
public static String md5(String message) { public static String sha256(String message) {
return hash(message, MessageDigestAlgorithm.MD5); return hash(message, MessageDigestAlgorithm.SHA256);
} }
// Only works for length up to 40! public static String sha512(String message) {
public static String generateSalt(int length) { return hash(message, MessageDigestAlgorithm.SHA512);
byte[] msg = new byte[40]; }
RANDOM.nextBytes(msg);
MessageDigest sha1 = getDigest(MessageDigestAlgorithm.SHA1); public static String md5(String message) {
sha1.reset(); return hash(message, MessageDigestAlgorithm.MD5);
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) { 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));
}
} }

View File

@ -1,13 +1,13 @@
package fr.xephi.authme.security; package fr.xephi.authme.security;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Calendar;
import java.util.Random; import java.util.Random;
public class RandomString { public final class RandomString {
private static final char[] chars = new char[36]; private static final char[] chars = new char[36];
private static final Random RANDOM = new SecureRandom(); private static final Random RANDOM = new SecureRandom();
private static final int HEX_MAX_INDEX = 15;
static { static {
for (int idx = 0; idx < 10; ++idx) { for (int idx = 0; idx < 10; ++idx) {
@ -18,30 +18,24 @@ public class RandomString {
} }
} }
private final Random random = new Random(); private RandomString() {
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);
} }
public static String generate(int length) { 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) { if (length < 0) {
throw new IllegalArgumentException("Length must be positive but was " + length); throw new IllegalArgumentException("Length must be positive but was " + length);
} }
StringBuilder sb = new StringBuilder(length); StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; ++i) { for (int i = 0; i < length; ++i) {
sb.append(chars[RANDOM.nextInt(chars.length)]); sb.append(chars[RANDOM.nextInt(maxIndex)]);
} }
return sb.toString(); return sb.toString();
} }

View File

@ -1,6 +1,6 @@
package fr.xephi.authme.security.crypts; 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.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.SaltType;
@ -10,7 +10,7 @@ import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters;
import javax.xml.bind.DatatypeConverter; import javax.xml.bind.DatatypeConverter;
@Recommendation(Usage.OK) @Recommendation(Usage.ACCEPTABLE)
@HasSalt(value = SaltType.TEXT, length = 12) @HasSalt(value = SaltType.TEXT, length = 12)
public class CryptPBKDF2Django implements EncryptionMethod { public class CryptPBKDF2Django implements EncryptionMethod {
@ -38,7 +38,7 @@ public class CryptPBKDF2Django implements EncryptionMethod {
} }
public String generateSalt() { public String generateSalt() {
return HashUtils.generateSalt(12); return RandomString.generateHex(12);
} }
} }

View File

@ -1,12 +1,13 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.HashUtils; 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.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage; import fr.xephi.authme.security.crypts.description.Usage;
@Recommendation(Usage.OK) @Recommendation(Usage.ACCEPTABLE)
@HasSalt(value = SaltType.TEXT, length = 32) @HasSalt(value = SaltType.TEXT, length = 32)
public class JOOMLA implements EncryptionMethod { public class JOOMLA implements EncryptionMethod {
@ -20,7 +21,7 @@ public class JOOMLA implements EncryptionMethod {
} }
public String generateSalt() { public String generateSalt() {
return HashUtils.generateSalt(32); return RandomString.generateHex(32);
} }
@Override @Override

View File

@ -1,6 +1,6 @@
package fr.xephi.authme.security.crypts; 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.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType; 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; import static fr.xephi.authme.security.HashUtils.md5;
@Recommendation(Usage.OK) @Recommendation(Usage.ACCEPTABLE)
@HasSalt(value = SaltType.TEXT, length = 16) @HasSalt(value = SaltType.TEXT, length = 16)
public class MD5VB implements EncryptionMethod { public class MD5VB implements EncryptionMethod {
@ -28,7 +28,7 @@ public class MD5VB implements EncryptionMethod {
} }
public String generateSalt() { public String generateSalt() {
return HashUtils.generateSalt(16); return RandomString.generateHex(16);
} }
} }

View File

@ -1,33 +1,34 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import java.math.BigInteger; import fr.xephi.authme.security.RandomString;
import java.security.MessageDigest; import fr.xephi.authme.security.crypts.description.HasSalt;
import java.security.NoSuchAlgorithmException; 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 implements EncryptionMethod {
private static String getSHA256(String message) @Override
throws NoSuchAlgorithmException { public String computeHash(String password, String salt, String name) {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); return "$SHA$" + salt + "$" + sha256(sha256(password) + salt);
sha256.reset(); }
sha256.update(message.getBytes());
byte[] digest = sha256.digest(); public String computeHash(String password, String name) {
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); return computeHash(password, generateSalt(), name);
} }
@Override @Override
public String computeHash(String password, String salt, String name) public boolean comparePassword(String hash, String password, String playerName) {
throws NoSuchAlgorithmException {
return "$SHA$" + salt + "$" + getSHA256(getSHA256(password) + salt);
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
String[] line = hash.split("\\$"); 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);
} }
} }

View File

@ -1,31 +1,30 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import java.math.BigInteger; import fr.xephi.authme.security.HashUtils;
import java.security.MessageDigest; import fr.xephi.authme.security.crypts.description.HasSalt;
import java.security.NoSuchAlgorithmException; 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 implements EncryptionMethod {
private static String getSHA512(String message) @Override
throws NoSuchAlgorithmException { public String computeHash(String password, String salt, String name) {
MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); return computeHash(password, name);
sha512.reset(); }
sha512.update(message.getBytes());
byte[] digest = sha512.digest(); public String computeHash(String password, String name) {
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); return HashUtils.sha512(password);
} }
@Override @Override
public String computeHash(String password, String salt, String name) public boolean comparePassword(String hash, String password, String playerName) {
throws NoSuchAlgorithmException {
return getSHA512(password);
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
return hash.equals(computeHash(password, "", "")); return hash.equals(computeHash(password, "", ""));
} }
public String generateSalt() {
return null;
}
} }

View File

@ -1,31 +1,30 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import java.math.BigInteger; import fr.xephi.authme.security.HashUtils;
import java.security.MessageDigest; import fr.xephi.authme.security.crypts.description.HasSalt;
import java.security.NoSuchAlgorithmException; 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 implements EncryptionMethod {
private static String getSHA1(String message) @Override
throws NoSuchAlgorithmException { public String computeHash(String password, String salt, String name) {
MessageDigest sha1 = MessageDigest.getInstance("SHA1"); return computeHash(password, name);
sha1.reset(); }
sha1.update(message.getBytes());
byte[] digest = sha1.digest(); public String computeHash(String password, String name) {
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); return HashUtils.sha1(name.toLowerCase() + password);
} }
@Override @Override
public String computeHash(String password, String salt, String name) public boolean comparePassword(String hash, String password, String playerName) {
throws NoSuchAlgorithmException { return hash.equals(computeHash(password, playerName));
return getSHA1(name.toLowerCase() + password);
} }
@Override public String generateSalt() {
public boolean comparePassword(String hash, String password, return null;
String playerName) throws NoSuchAlgorithmException {
return hash.equals(computeHash(password, null, playerName));
} }
} }

View File

@ -11,7 +11,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Arrays; import java.util.Arrays;
@Recommendation(Usage.OK) @Recommendation(Usage.ACCEPTABLE)
@HasSalt(value = SaltType.TEXT, length = 9) @HasSalt(value = SaltType.TEXT, length = 9)
public class WORDPRESS implements EncryptionMethod { public class WORDPRESS implements EncryptionMethod {

View File

@ -1,6 +1,6 @@
package fr.xephi.authme.security.crypts; 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.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.SaltType;
@ -38,7 +38,7 @@ public class XAUTH implements EncryptionMethod {
} }
public String generateSalt() { public String generateSalt() {
return HashUtils.generateSalt(12); return RandomString.generateHex(12);
} }
} }

View File

@ -1,13 +1,22 @@
package fr.xephi.authme.security.crypts.description; 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 * Describes the type of salt the encryption algorithm uses. This is purely for documentation
* purposes and is ignored by the code. * purposes and is ignored by the code.
*/ */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HasSalt { public @interface HasSalt {
/** The type of the salt. */
SaltType value(); SaltType value();
/** For text salts, the length of the salt. */
int length() default 0; int length() default 0;
} }

View File

@ -1,10 +1,18 @@
package fr.xephi.authme.security.crypts.description; 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}. * Annotation to mark a hash algorithm with the usage recommendation, see {@link Usage}.
*/ */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Recommendation { public @interface Recommendation {
/** The recommendation for using the hash algorithm. */
Usage value(); Usage value();
} }

View File

@ -8,7 +8,7 @@ public enum SaltType {
/** Random, newly generated text. */ /** Random, newly generated text. */
TEXT, TEXT,
/** The username, including variations or repetitions. */ /** Salt is based on the username, including variations and repetitions. */
USERNAME, USERNAME,
/** No salt. */ /** No salt. */

View File

@ -9,7 +9,7 @@ public enum Usage {
RECOMMENDED, RECOMMENDED,
/** There are safer algorithms that can be chosen but using the algorithm is generally OK. */ /** 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. */ /** Hash algorithm is not recommended to be used. Use only if required by another system. */
DO_NOT_USE, DO_NOT_USE,