#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
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
+3
-3
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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], ""));
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user