#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:
ljacqu
2015-12-30 17:56:22 +01:00
parent 9c4a578bec
commit a3402d573f
46 changed files with 328 additions and 564 deletions
@@ -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,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));