Merge pull request #85 from AuthMe-Team/358-encryptn-mthd-refactor

358 encryptn mthd refactor
This commit is contained in:
ljacqu 2015-12-30 21:50:30 +01:00
commit 9343cfe9da
94 changed files with 2172 additions and 2073 deletions

View File

@ -82,8 +82,6 @@ typing commands or using the inventory. It can also kick players with uncommonly
<li>MyBB: MYBB</li> <li>MyBB: MYBB</li>
<li>IPB3: IPB3</li> <li>IPB3: IPB3</li>
<li>PhpFusion: PHPFUSION</li> <li>PhpFusion: PHPFUSION</li>
<li><del>Xenforo SHA1: XFSHA1</del> <strong>(Deprecated)</strong></li>
<li><del>Xenforo SHA256: XFSHA256</del> <strong>(Deprecated)</strong></li>
<li>Joomla: JOOMLA</li> <li>Joomla: JOOMLA</li>
<li>WBB3: WBB3*</li> <li>WBB3: WBB3*</li>
<li>SHA512: SHA512</li> <li>SHA512: SHA512</li>
@ -92,7 +90,7 @@ typing commands or using the inventory. It can also kick players with uncommonly
</ul></li> </ul></li>
<li>Custom MySQL tables/columns names (useful with forums databases)</li> <li>Custom MySQL tables/columns names (useful with forums databases)</li>
<li><strong>Cached database queries!</strong></li> <li><strong>Cached database queries!</strong></li>
<li><strong>Full compatible with Citizens2, CombatTag, CombatTagPlus and ChestShop!</strong></li> <li><strong>Fully compatible with Citizens2, CombatTag, CombatTagPlus and ChestShop!</strong></li>
<li>Compatible with Minecraft mods like <strong>BuildCraft or RedstoneCraft</strong></li> <li>Compatible with Minecraft mods like <strong>BuildCraft or RedstoneCraft</strong></li>
<li>Restricted users (associate a Username with an IP)</li> <li>Restricted users (associate a Username with an IP)</li>
<li>Protect player's inventory until a correct Authentication</li> <li>Protect player's inventory until a correct Authentication</li>
@ -120,7 +118,7 @@ typing commands or using the inventory. It can also kick players with uncommonly
</li><li><a href="http://dev.bukkit.org/server-mods/authme-reloaded/pages/web-site-integration/">Website Integration</a> </li><li><a href="http://dev.bukkit.org/server-mods/authme-reloaded/pages/web-site-integration/">Website Integration</a>
</li><li><a href="https://raw.githubusercontent.com/Xephi/AuthMeReloaded/master/src/main/resources/config.yml">Click here for an example of the Config file</a> </li><li><a href="https://raw.githubusercontent.com/Xephi/AuthMeReloaded/master/src/main/resources/config.yml">Click here for an example of the Config file</a>
</li><li><a href="http://dev.bukkit.org/server-mods/authme-reloaded/pages/how-to-import-database-from-rakamak/">How to convert from Rakamak</a> </li><li><a href="http://dev.bukkit.org/server-mods/authme-reloaded/pages/how-to-import-database-from-rakamak/">How to convert from Rakamak</a>
</li><li>Convert from FlatFile (auths.db but not the sqlite one ) to MySQL: /converter </li><li>Convert from FlatFile (auths.db but not the sqlite one) to MySQL: /converter
</li></ul> </li></ul>
<hr> <hr>
@ -139,5 +137,5 @@ GameHosting.it is leader in Italy as Game Server Provider. With its own DataCent
#####Credits #####Credits
<p>Team members: look at the <a href="https://github.com/AuthMe-Team/AuthMeReloaded/blob/master/team.txt">team.txt file</a> <p>Team members: look at the <a href="https://github.com/AuthMe-Team/AuthMeReloaded/blob/master/team.txt">team.txt file</a>
<p>Credit for old version of the plugin to: d4rkwarriors, fabe1337 , Whoami2 and pomo4ka</p> <p>Credit for old version of the plugin to: d4rkwarriors, fabe1337, Whoami2 and pomo4ka</p>
<p>Thanks also to: AS1LV3RN1NJA, Hoeze and eprimex</p> <p>Thanks also to: AS1LV3RN1NJA, Hoeze and eprimex</p>

View File

@ -43,6 +43,7 @@ import fr.xephi.authme.permission.PlayerPermission;
import fr.xephi.authme.process.Management; import fr.xephi.authme.process.Management;
import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.crypts.EncryptedPassword;
import fr.xephi.authme.settings.OtherAccounts; import fr.xephi.authme.settings.OtherAccounts;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.Spawn; import fr.xephi.authme.settings.Spawn;
@ -101,12 +102,13 @@ public class AuthMe extends JavaPlugin {
private Messages messages; private Messages messages;
private JsonCache playerBackup; private JsonCache playerBackup;
private ModuleManager moduleManager; private ModuleManager moduleManager;
private PasswordSecurity passwordSecurity;
private DataSource database;
// Public Instances // Public Instances
public NewAPI api; public NewAPI api;
public SendMailSSL mail; public SendMailSSL mail;
public DataManager dataManager; public DataManager dataManager;
public DataSource database;
public OtherAccounts otherAccounts; public OtherAccounts otherAccounts;
public Location essentialsSpawn; public Location essentialsSpawn;
@ -192,7 +194,6 @@ public class AuthMe extends JavaPlugin {
/** /**
* Method called when the server enables the plugin. * Method called when the server enables the plugin.
* @see org.bukkit.plugin.Plugin#onEnable()
*/ */
@Override @Override
public void onEnable() { public void onEnable() {
@ -209,12 +210,26 @@ public class AuthMe extends JavaPlugin {
return; return;
} }
// Set up messages // Set up messages & password security
messages = Messages.getInstance(); messages = Messages.getInstance();
// Connect to the database and setup tables
try {
setupDatabase();
} catch (Exception e) {
ConsoleLogger.writeStackTrace(e);
ConsoleLogger.showError(e.getMessage());
ConsoleLogger.showError("Fatal error occurred during database connection! Authme initialization ABORTED!");
stopOrUnload();
return;
}
passwordSecurity = new PasswordSecurity(getDataSource(), Settings.getPasswordHash,
Bukkit.getPluginManager(), Settings.supportOldPassword);
// Set up the permissions manager and command handler // Set up the permissions manager and command handler
permsMan = initializePermissionsManager(); permsMan = initializePermissionsManager();
commandHandler = initializeCommandHandler(permsMan, messages); commandHandler = initializeCommandHandler(permsMan, messages, passwordSecurity);
// Set up the module manager // Set up the module manager
setupModuleManager(); setupModuleManager();
@ -256,16 +271,7 @@ public class AuthMe extends JavaPlugin {
// Do a backup on start // Do a backup on start
new PerformBackup(plugin).doBackup(PerformBackup.BackupCause.START); new PerformBackup(plugin).doBackup(PerformBackup.BackupCause.START);
// Connect to the database and setup tables
try {
setupDatabase();
} catch (Exception e) {
ConsoleLogger.writeStackTrace(e);
ConsoleLogger.showError(e.getMessage());
ConsoleLogger.showError("Fatal error occurred during database connection! Authme initialization ABORTED!");
stopOrUnload();
return;
}
// Setup the inventory backup // Setup the inventory backup
playerBackup = new JsonCache(); playerBackup = new JsonCache();
@ -412,11 +418,12 @@ public class AuthMe extends JavaPlugin {
} }
} }
private CommandHandler initializeCommandHandler(PermissionsManager permissionsManager, Messages messages) { private CommandHandler initializeCommandHandler(PermissionsManager permissionsManager, Messages messages,
PasswordSecurity passwordSecurity) {
HelpProvider helpProvider = new HelpProvider(permissionsManager); HelpProvider helpProvider = new HelpProvider(permissionsManager);
Set<CommandDescription> baseCommands = CommandInitializer.buildCommands(); Set<CommandDescription> baseCommands = CommandInitializer.buildCommands();
CommandMapper mapper = new CommandMapper(baseCommands, messages, permissionsManager, helpProvider); CommandMapper mapper = new CommandMapper(baseCommands, messages, permissionsManager, helpProvider);
CommandService commandService = new CommandService(this, mapper, helpProvider, messages); CommandService commandService = new CommandService(this, mapper, helpProvider, messages, passwordSecurity);
return new CommandHandler(commandService); return new CommandHandler(commandService);
} }
@ -506,11 +513,6 @@ public class AuthMe extends JavaPlugin {
} }
} }
/**
* Method onDisable.
*
* @see org.bukkit.plugin.Plugin#onDisable()
*/
@Override @Override
public void onDisable() { public void onDisable() {
// Save player data // Save player data
@ -525,7 +527,9 @@ public class AuthMe extends JavaPlugin {
new PerformBackup(plugin).doBackup(PerformBackup.BackupCause.STOP); new PerformBackup(plugin).doBackup(PerformBackup.BackupCause.STOP);
// Unload modules // Unload modules
moduleManager.unloadModules(); if (moduleManager != null) {
moduleManager.unloadModules();
}
// Close the database // Close the database
if (database != null) { if (database != null) {
@ -539,16 +543,13 @@ public class AuthMe extends JavaPlugin {
// Stop/unload the server/plugin as defined in the configuration // Stop/unload the server/plugin as defined in the configuration
public void stopOrUnload() { public void stopOrUnload() {
if (Settings.isStopEnabled) { if (Settings.isStopEnabled) {
ConsoleLogger.showError("THE SERVER IS GOING TO SHUTDOWN AS DEFINED IN THE CONFIGURATION!"); ConsoleLogger.showError("THE SERVER IS GOING TO SHUT DOWN AS DEFINED IN THE CONFIGURATION!");
server.shutdown(); server.shutdown();
} else { } else {
server.getPluginManager().disablePlugin(AuthMe.getInstance()); server.getPluginManager().disablePlugin(AuthMe.getInstance());
} }
} }
/**
* Method setupDatabase.
*/
public void setupDatabase() throws Exception { public void setupDatabase() throws Exception {
if (database != null) if (database != null)
database.close(); database.close();
@ -574,14 +575,15 @@ public class AuthMe extends JavaPlugin {
int accounts = database.getAccountsRegistered(); int accounts = database.getAccountsRegistered();
if (accounts >= 4000) { if (accounts >= 4000) {
ConsoleLogger.showError("YOU'RE USING THE SQLITE DATABASE WITH " ConsoleLogger.showError("YOU'RE USING THE SQLITE DATABASE WITH "
+ accounts + "+ ACCOUNTS, FOR BETTER PERFORMANCES, PLEASE UPGRADE TO MYSQL!!"); + accounts + "+ ACCOUNTS; FOR BETTER PERFORMANCE, PLEASE UPGRADE TO MYSQL!!");
} }
} }
}); });
} }
if (Settings.getDataSource == DataSource.DataSourceType.FILE) { if (Settings.getDataSource == DataSource.DataSourceType.FILE) {
ConsoleLogger.showError("FlatFile backend has been detected and is now deprecated, it will be changed to SQLite... Connection will be impossible until conversion is done!"); ConsoleLogger.showError("FlatFile backend has been detected and is now deprecated, it will be changed " +
"to SQLite... Connection will be impossible until conversion is done!");
ForceFlatToSqlite converter = new ForceFlatToSqlite(database); ForceFlatToSqlite converter = new ForceFlatToSqlite(database);
DataSource source = converter.run(); DataSource source = converter.run();
if (source != null) { if (source != null) {
@ -590,12 +592,13 @@ public class AuthMe extends JavaPlugin {
} }
// TODO: Move this to another place maybe ? // TODO: Move this to another place maybe ?
if (Settings.getPasswordHash == HashAlgorithm.PLAINTEXT) if (Settings.getPasswordHash == HashAlgorithm.PLAINTEXT) {
{ ConsoleLogger.showError("Your HashAlgorithm has been detected as plaintext and is now deprecated; " +
ConsoleLogger.showError("Your HashAlgorithm has been detected has plaintext and is now deprecrated, it will be changed and hashed now to AuthMe default hashing method"); "it will be changed and hashed now to the AuthMe default hashing method");
for (PlayerAuth auth : database.getAllAuths()) for (PlayerAuth auth : database.getAllAuths()) {
{ EncryptedPassword encryptedPassword = passwordSecurity.computeHash(
auth.setHash(PasswordSecurity.getHash(HashAlgorithm.SHA256, auth.getHash(), auth.getNickname())); HashAlgorithm.SHA256, auth.getPassword().getHash(), auth.getNickname());
auth.setPassword(encryptedPassword);
database.updatePassword(auth); database.updatePassword(auth);
} }
Settings.setValue("settings.security.passwordHash", "SHA256"); Settings.setValue("settings.security.passwordHash", "SHA256");
@ -720,8 +723,8 @@ public class AuthMe extends JavaPlugin {
} }
// Save Player Data // Save Player Data
public void savePlayer(Player player) { private void savePlayer(Player player) {
if ((Utils.isNPC(player)) || (Utils.isUnrestricted(player))) { if (Utils.isNPC(player) || Utils.isUnrestricted(player)) {
return; return;
} }
String name = player.getName().toLowerCase(); String name = player.getName().toLowerCase();
@ -836,10 +839,10 @@ public class AuthMe extends JavaPlugin {
// Return the AuthMe spawn point // Return the AuthMe spawn point
private Location getAuthMeSpawn(Player player) { private Location getAuthMeSpawn(Player player) {
if ((!database.isAuthAvailable(player.getName().toLowerCase()) || !player.hasPlayedBefore()) && (Spawn.getInstance().getFirstSpawn() != null)) { if ((!database.isAuthAvailable(player.getName().toLowerCase()) || !player.hasPlayedBefore())
&& (Spawn.getInstance().getFirstSpawn() != null)) {
return Spawn.getInstance().getFirstSpawn(); return Spawn.getInstance().getFirstSpawn();
} } else if (Spawn.getInstance().getSpawn() != null) {
if (Spawn.getInstance().getSpawn() != null) {
return Spawn.getInstance().getSpawn(); return Spawn.getInstance().getSpawn();
} }
return player.getWorld().getSpawnLocation(); return player.getWorld().getSpawnLocation();
@ -889,8 +892,7 @@ public class AuthMe extends JavaPlugin {
* *
*/ */
@Deprecated @Deprecated
public void getVerygamesIp(final Player player) public void getVerygamesIp(final Player player) {
{
final String name = player.getName().toLowerCase(); final String name = player.getName().toLowerCase();
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable(){ Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable(){
@Override @Override
@ -986,4 +988,12 @@ public class AuthMe extends JavaPlugin {
return management; return management;
} }
public DataSource getDataSource() {
return database;
}
public PasswordSecurity getPasswordSecurity() {
return passwordSecurity;
}
} }

View File

@ -4,7 +4,7 @@ import fr.xephi.authme.AuthMe;
import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.auth.PlayerCache;
import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.security.crypts.EncryptedPassword;
import fr.xephi.authme.util.Utils; import fr.xephi.authme.util.Utils;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
@ -12,34 +12,37 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import java.security.NoSuchAlgorithmException;
/** /**
* Deprecated API of AuthMe. Please use {@link NewAPI} instead.
*/ */
@Deprecated
public class API { public class API {
public static final String newline = System.getProperty("line.separator"); public static final String newline = System.getProperty("line.separator");
public static AuthMe instance; public static AuthMe instance;
private static PasswordSecurity passwordSecurity;
/** /**
* Constructor for API. * Constructor for the deprecated API.
* *
* @param instance AuthMe * @param instance AuthMe
*/ */
@Deprecated @Deprecated
public API(AuthMe instance) { public API(AuthMe instance) {
API.instance = instance; API.instance = instance;
passwordSecurity = instance.getPasswordSecurity();
} }
/** /**
* Hook into AuthMe * Hook into AuthMe.
* *
* @return AuthMe instance * @return AuthMe instance
*/ */
@Deprecated @Deprecated
public static AuthMe hookAuthMe() { public static AuthMe hookAuthMe() {
if (instance != null) if (instance != null) {
return instance; return instance;
}
Plugin plugin = Bukkit.getServer().getPluginManager().getPlugin("AuthMe"); Plugin plugin = Bukkit.getServer().getPluginManager().getPlugin("AuthMe");
if (plugin == null || !(plugin instanceof AuthMe)) { if (plugin == null || !(plugin instanceof AuthMe)) {
return null; return null;
@ -49,9 +52,10 @@ public class API {
} }
/** /**
* @param player * Return whether the player is authenticated.
* *
* @return true if player is authenticate * @param player The player to verify
* @return true if the player is authenticated
*/ */
@Deprecated @Deprecated
public static boolean isAuthenticated(Player player) { public static boolean isAuthenticated(Player player) {
@ -59,8 +63,9 @@ public class API {
} }
/** /**
* @param player * Return whether the player is unrestricted.
* *
* @param player The player to verify
* @return true if the player is unrestricted * @return true if the player is unrestricted
*/ */
@Deprecated @Deprecated
@ -68,13 +73,6 @@ public class API {
return Utils.isUnrestricted(player); return Utils.isUnrestricted(player);
} }
/**
* Method getLastLocation.
*
* @param player Player
*
* @return Location
*/
@Deprecated @Deprecated
public static Location getLastLocation(Player player) { public static Location getLastLocation(Player player) {
try { try {
@ -92,13 +90,6 @@ public class API {
} }
} }
/**
* Method setPlayerInventory.
*
* @param player Player
* @param content ItemStack[]
* @param armor ItemStack[]
*/
@Deprecated @Deprecated
public static void setPlayerInventory(Player player, ItemStack[] content, public static void setPlayerInventory(Player player, ItemStack[] content,
ItemStack[] armor) { ItemStack[] armor) {
@ -110,93 +101,72 @@ public class API {
} }
/** /**
* @param playerName * Check whether the given player name is registered.
* *
* @param playerName The player name to verify
* @return true if player is registered * @return true if player is registered
*/ */
@Deprecated @Deprecated
public static boolean isRegistered(String playerName) { public static boolean isRegistered(String playerName) {
String player = playerName.toLowerCase(); String player = playerName.toLowerCase();
return instance.database.isAuthAvailable(player); return instance.getDataSource().isAuthAvailable(player);
} }
/** /**
* @param playerName String * Check the password for the given player.
* @param passwordToCheck String
* *
* @return true if the password is correct , false else * @param playerName The name of the player
* @param passwordToCheck The password to check
* @return true if the password is correct, false otherwise
*/ */
@Deprecated @Deprecated
public static boolean checkPassword(String playerName, public static boolean checkPassword(String playerName, String passwordToCheck) {
String passwordToCheck) { return isRegistered(playerName) && passwordSecurity.comparePassword(passwordToCheck, playerName);
if (!isRegistered(playerName))
return false;
String player = playerName.toLowerCase();
PlayerAuth auth = instance.database.getAuth(player);
try {
return PasswordSecurity.comparePasswordWithHash(passwordToCheck, auth.getHash(), playerName);
} catch (NoSuchAlgorithmException e) {
return false;
}
} }
/** /**
* Register a player * Register a player.
* *
* @param playerName String * @param playerName The name of the player
* @param password String * @param password The password
* * @return true if the player was registered correctly
* @return true if the player is register correctly
*/ */
@Deprecated @Deprecated
public static boolean registerPlayer(String playerName, String password) { public static boolean registerPlayer(String playerName, String password) {
try { String name = playerName.toLowerCase();
String name = playerName.toLowerCase(); EncryptedPassword encryptedPassword = passwordSecurity.computeHash(password, name);
String hash = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); if (isRegistered(name)) {
if (isRegistered(name)) {
return false;
}
PlayerAuth auth = new PlayerAuth(name, hash, "198.18.0.1", 0, "your@email.com", playerName);
return instance.database.saveAuth(auth);
} catch (NoSuchAlgorithmException ex) {
return false; return false;
} }
PlayerAuth auth = PlayerAuth.builder()
.name(name)
.password(encryptedPassword)
.lastLogin(0)
.realName(playerName)
.build();
return instance.getDataSource().saveAuth(auth);
} }
/** /**
* Force a player to login * Force a player to log in.
* *
* @param player * player * @param player The player to log in
*/ */
@Deprecated @Deprecated
public static void forceLogin(Player player) { public static void forceLogin(Player player) {
instance.getManagement().performLogin(player, "dontneed", true); instance.getManagement().performLogin(player, "dontneed", true);
} }
/**
* Method getPlugin.
*
* @return AuthMe
*/
@Deprecated @Deprecated
public AuthMe getPlugin() { public AuthMe getPlugin() {
return instance; return instance;
} }
/** /**
* @param player * Check whether the player is an NPC.
* *
* @return true if player is a npc * @param player The player to verify
*/ * @return true if player is an npc
@Deprecated
public boolean isaNPC(Player player) {
return Utils.isNPC(player);
}
/**
* @param player
*
* @return true if player is a npc
*/ */
@Deprecated @Deprecated
public boolean isNPC(Player player) { public boolean isNPC(Player player) {

View File

@ -3,8 +3,7 @@ package fr.xephi.authme.api;
import fr.xephi.authme.AuthMe; import fr.xephi.authme.AuthMe;
import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.auth.PlayerCache;
import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.EncryptedPassword;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.util.Utils; import fr.xephi.authme.util.Utils;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
@ -12,9 +11,8 @@ import org.bukkit.Server;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import java.security.NoSuchAlgorithmException;
/** /**
* The current API of AuthMe.
*/ */
public class NewAPI { public class NewAPI {
@ -42,33 +40,36 @@ public class NewAPI {
/** /**
* Hook into AuthMe * Hook into AuthMe
* *
* @return AuthMe plugin * @return The API object
*/ */
public static NewAPI getInstance() { public static NewAPI getInstance() {
if (singleton != null) if (singleton != null) {
return singleton; return singleton;
}
Plugin p = Bukkit.getServer().getPluginManager().getPlugin("AuthMe"); Plugin p = Bukkit.getServer().getPluginManager().getPlugin("AuthMe");
if (p == null || !(p instanceof AuthMe)) { if (p == null || !(p instanceof AuthMe)) {
return null; return null;
} }
AuthMe authme = (AuthMe) p; AuthMe authme = (AuthMe) p;
singleton = (new NewAPI(authme)); singleton = new NewAPI(authme);
return singleton; return singleton;
} }
/** /**
* Method getPlugin. * Return the plugin instance.
* *
* @return AuthMe * @return The AuthMe instance
*/ */
public AuthMe getPlugin() { public AuthMe getPlugin() {
return plugin; return plugin;
} }
/** /**
* @param player * Return whether the given player is authenticated.
* *
* @return true if player is authenticate * @param player The player to verify
*
* @return true if the player is authenticated
*/ */
public boolean isAuthenticated(Player player) { public boolean isAuthenticated(Player player) {
return PlayerCache.getInstance().isAuthenticated(player.getName()); return PlayerCache.getInstance().isAuthenticated(player.getName());
@ -93,15 +94,15 @@ public class NewAPI {
} }
/** /**
* Method getLastLocation. * Get the last location of a player.
* *
* @param player Player * @param player Player The player to process
* *
* @return Location * @return Location The location of the player
*/ */
public Location getLastLocation(Player player) { public Location getLastLocation(Player player) {
try { try {
PlayerAuth auth = PlayerCache.getInstance().getAuth(player.getName().toLowerCase()); PlayerAuth auth = PlayerCache.getInstance().getAuth(player.getName());
if (auth != null) { if (auth != null) {
return new Location(Bukkit.getWorld(auth.getWorld()), auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ()); return new Location(Bukkit.getWorld(auth.getWorld()), auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ());
@ -115,87 +116,83 @@ public class NewAPI {
} }
/** /**
* @param playerName * Return whether the player is registered.
* *
* @return true if player is registered * @param playerName The player name to check
*
* @return true if player is registered, false otherwise
*/ */
public boolean isRegistered(String playerName) { public boolean isRegistered(String playerName) {
String player = playerName.toLowerCase(); String player = playerName.toLowerCase();
return plugin.database.isAuthAvailable(player); return plugin.getDataSource().isAuthAvailable(player);
} }
/** /**
* @param playerName String * Check the password for the given player.
* @param passwordToCheck String
* *
* @return true if the password is correct , false else * @param playerName The player to check the password for
* @param passwordToCheck The password to check
*
* @return true if the password is correct, false otherwise
*/ */
public boolean checkPassword(String playerName, String passwordToCheck) { public boolean checkPassword(String playerName, String passwordToCheck) {
if (!isRegistered(playerName)) return isRegistered(playerName) && plugin.getPasswordSecurity().comparePassword(passwordToCheck, playerName);
return false;
String player = playerName.toLowerCase();
PlayerAuth auth = plugin.database.getAuth(player);
try {
return PasswordSecurity.comparePasswordWithHash(passwordToCheck, auth.getHash(), playerName);
} catch (NoSuchAlgorithmException e) {
return false;
}
} }
/** /**
* Register a player * Register a player.
* *
* @param playerName String * @param playerName The player to register
* @param password String * @param password The password to register the player with
* *
* @return true if the player is register correctly * @return true if the player was registered successfully
*/ */
public boolean registerPlayer(String playerName, String password) { public boolean registerPlayer(String playerName, String password) {
try { String name = playerName.toLowerCase();
String name = playerName.toLowerCase(); EncryptedPassword result = plugin.getPasswordSecurity().computeHash(password, name);
String hash = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); if (isRegistered(name)) {
if (isRegistered(name)) {
return false;
}
PlayerAuth auth = new PlayerAuth(name, hash, "192.168.0.1", 0, "your@email.com", playerName);
return plugin.database.saveAuth(auth);
} catch (NoSuchAlgorithmException ex) {
return false; return false;
} }
PlayerAuth auth = PlayerAuth.builder()
.name(name)
.password(result)
.realName(playerName)
.build();
return plugin.getDataSource().saveAuth(auth);
} }
/** /**
* Force a player to login * Force a player to login.
* *
* @param player * player * @param player The player to log in
*/ */
public void forceLogin(Player player) { public void forceLogin(Player player) {
plugin.getManagement().performLogin(player, "dontneed", true); plugin.getManagement().performLogin(player, "dontneed", true);
} }
/** /**
* Force a player to logout * Force a player to logout.
* *
* @param player * player * @param player The player to log out
*/ */
public void forceLogout(Player player) { public void forceLogout(Player player) {
plugin.getManagement().performLogout(player); plugin.getManagement().performLogout(player);
} }
/** /**
* Force a player to register * Force a player to register.
* *
* @param player * player * @param player The player to register
* @param password String * @param password The password to use
*/ */
public void forceRegister(Player player, String password) { public void forceRegister(Player player, String password) {
plugin.getManagement().performRegister(player, password, null); plugin.getManagement().performRegister(player, password, null);
} }
/** /**
* Force a player to unregister * Force a player to unregister.
* *
* @param player * player * @param player The player to unregister
*/ */
public void forceUnregister(Player player) { public void forceUnregister(Player player) {
plugin.getManagement().performUnregister(player, "", true); plugin.getManagement().performUnregister(player, "", true);

View File

@ -1,11 +1,10 @@
package fr.xephi.authme.cache.auth; package fr.xephi.authme.cache.auth;
import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.crypts.EncryptedPassword;
import fr.xephi.authme.settings.Settings; import org.bukkit.Location;
import static com.google.common.base.Objects.firstNonNull; import static com.google.common.base.Objects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.nullToEmpty;
/** /**
@ -13,14 +12,13 @@ import static com.google.common.base.Strings.nullToEmpty;
public class PlayerAuth { public class PlayerAuth {
private String nickname; private String nickname;
private String hash; private EncryptedPassword password;
private String ip; private String ip;
private long lastLogin; private long lastLogin;
private double x; private double x;
private double y; private double y;
private double z; private double z;
private String world; private String world;
private String salt;
private int groupId; private int groupId;
private String email; private String email;
private String realName; private String realName;
@ -41,7 +39,7 @@ public class PlayerAuth {
* @param realName String * @param realName String
*/ */
public PlayerAuth(String nickname, String ip, long lastLogin, String realName) { public PlayerAuth(String nickname, String ip, long lastLogin, String realName) {
this(nickname, "", "", -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); this(nickname, new EncryptedPassword(""), -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName);
} }
/** /**
@ -55,7 +53,8 @@ public class PlayerAuth {
* @param realName String * @param realName String
*/ */
public PlayerAuth(String nickname, double x, double y, double z, String world, String realName) { public PlayerAuth(String nickname, double x, double y, double z, String world, String realName) {
this(nickname, "", "", -1, "127.0.0.1", System.currentTimeMillis(), x, y, z, world, "your@email.com", realName); this(nickname, new EncryptedPassword(""), -1, "127.0.0.1", System.currentTimeMillis(), x, y, z, world,
"your@email.com", realName);
} }
/** /**
@ -68,7 +67,7 @@ public class PlayerAuth {
* @param realName String * @param realName String
*/ */
public PlayerAuth(String nickname, String hash, String ip, long lastLogin, String realName) { public PlayerAuth(String nickname, String hash, String ip, long lastLogin, String realName) {
this(nickname, hash, "", -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); this(nickname, new EncryptedPassword(hash), -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName);
} }
/** /**
@ -82,7 +81,7 @@ public class PlayerAuth {
* @param realName String * @param realName String
*/ */
public PlayerAuth(String nickname, String hash, String ip, long lastLogin, String email, String realName) { public PlayerAuth(String nickname, String hash, String ip, long lastLogin, String email, String realName) {
this(nickname, hash, "", -1, ip, lastLogin, 0, 0, 0, "world", email, realName); this(nickname, new EncryptedPassword(hash), -1, ip, lastLogin, 0, 0, 0, "world", email, realName);
} }
/** /**
@ -96,7 +95,7 @@ public class PlayerAuth {
* @param realName String * @param realName String
*/ */
public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, String realName) { public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, String realName) {
this(nickname, hash, salt, -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); this(nickname, new EncryptedPassword(hash, salt), -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName);
} }
/** /**
@ -113,8 +112,9 @@ public class PlayerAuth {
* @param email String * @param email String
* @param realName String * @param realName String
*/ */
public PlayerAuth(String nickname, String hash, String ip, long lastLogin, double x, double y, double z, String world, String email, String realName) { public PlayerAuth(String nickname, String hash, String ip, long lastLogin, double x, double y, double z,
this(nickname, hash, "", -1, ip, lastLogin, x, y, z, world, email, realName); String world, String email, String realName) {
this(nickname, new EncryptedPassword(hash), -1, ip, lastLogin, x, y, z, world, email, realName);
} }
/** /**
@ -132,8 +132,10 @@ public class PlayerAuth {
* @param email String * @param email String
* @param realName String * @param realName String
*/ */
public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, double x, double y, double z, String world, String email, String realName) { public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, double x, double y,
this(nickname, hash, salt, -1, ip, lastLogin, x, y, z, world, email, realName); double z, String world, String email, String realName) {
this(nickname, new EncryptedPassword(hash, salt), -1, ip, lastLogin,
x, y, z, world, email, realName);
} }
/** /**
@ -147,38 +149,37 @@ public class PlayerAuth {
* @param lastLogin long * @param lastLogin long
* @param realName String * @param realName String
*/ */
public PlayerAuth(String nickname, String hash, String salt, int groupId, String ip, long lastLogin, String realName) {
this(nickname, hash, salt, groupId, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName);
}
/**
* Constructor for PlayerAuth.
*
* @param nickname String
* @param hash String
* @param salt String
* @param groupId int
* @param ip String
* @param lastLogin long
* @param x double
* @param y double
* @param z double
* @param world String
* @param email String
* @param realName String
*/
public PlayerAuth(String nickname, String hash, String salt, int groupId, String ip, public PlayerAuth(String nickname, String hash, String salt, int groupId, String ip,
long lastLogin, double x, double y, double z, String world, String email, long lastLogin, String realName) {
String realName) { this(nickname, new EncryptedPassword(hash, salt), groupId, ip, lastLogin,
0, 0, 0, "world", "your@email.com", realName);
}
/**
* Constructor for PlayerAuth.
*
* @param nickname String
* @param password String
* @param groupId int
* @param ip String
* @param lastLogin long
* @param x double
* @param y double
* @param z double
* @param world String
* @param email String
* @param realName String
*/
public PlayerAuth(String nickname, EncryptedPassword password, int groupId, String ip, long lastLogin,
double x, double y, double z, String world, String email, String realName) {
this.nickname = nickname.toLowerCase(); this.nickname = nickname.toLowerCase();
this.hash = hash; this.password = password;
this.ip = ip; this.ip = ip;
this.lastLogin = lastLogin; this.lastLogin = lastLogin;
this.x = x; this.x = x;
this.y = y; this.y = y;
this.z = z; this.z = z;
this.world = world; this.world = world;
this.salt = salt;
this.groupId = groupId; this.groupId = groupId;
this.email = email; this.email = email;
this.realName = realName; this.realName = realName;
@ -191,237 +192,108 @@ public class PlayerAuth {
*/ */
public void set(PlayerAuth auth) { public void set(PlayerAuth auth) {
this.setEmail(auth.getEmail()); this.setEmail(auth.getEmail());
this.setHash(auth.getHash()); this.setPassword(auth.getPassword());
this.setIp(auth.getIp()); this.setIp(auth.getIp());
this.setLastLogin(auth.getLastLogin()); this.setLastLogin(auth.getLastLogin());
this.setNickname(auth.getNickname()); this.setNickname(auth.getNickname());
this.setQuitLocX(auth.getQuitLocX()); this.setQuitLocX(auth.getQuitLocX());
this.setQuitLocY(auth.getQuitLocY()); this.setQuitLocY(auth.getQuitLocY());
this.setQuitLocZ(auth.getQuitLocZ()); this.setQuitLocZ(auth.getQuitLocZ());
this.setSalt(auth.getSalt());
this.setWorld(auth.getWorld()); this.setWorld(auth.getWorld());
this.setRealName(auth.getRealName()); this.setRealName(auth.getRealName());
} }
/**
* Method setNickname.
*
* @param nickname String
*/
public void setNickname(String nickname) { public void setNickname(String nickname) {
this.nickname = nickname.toLowerCase(); this.nickname = nickname.toLowerCase();
} }
/**
* Method getNickname.
*
* @return String
*/
public String getNickname() { public String getNickname() {
return nickname; return nickname;
} }
/**
* Method getRealName.
*
* @return String
*/
public String getRealName() { public String getRealName() {
return realName; return realName;
} }
/**
* Method setRealName.
*
* @param realName String
*/
public void setRealName(String realName) { public void setRealName(String realName) {
this.realName = realName; this.realName = realName;
} }
/**
* Method getGroupId.
*
* @return int
*/
public int getGroupId() { public int getGroupId() {
return groupId; return groupId;
} }
/**
* Method getQuitLocX.
*
* @return double
*/
public double getQuitLocX() { public double getQuitLocX() {
return x; return x;
} }
/**
* Method setQuitLocX.
*
* @param d double
*/
public void setQuitLocX(double d) { public void setQuitLocX(double d) {
this.x = d; this.x = d;
} }
/**
* Method getQuitLocY.
*
* @return double
*/
public double getQuitLocY() { public double getQuitLocY() {
return y; return y;
} }
/**
* Method setQuitLocY.
*
* @param d double
*/
public void setQuitLocY(double d) { public void setQuitLocY(double d) {
this.y = d; this.y = d;
} }
/**
* Method getQuitLocZ.
*
* @return double
*/
public double getQuitLocZ() { public double getQuitLocZ() {
return z; return z;
} }
/**
* Method setQuitLocZ.
*
* @param d double
*/
public void setQuitLocZ(double d) { public void setQuitLocZ(double d) {
this.z = d; this.z = d;
} }
/**
* Method getWorld.
*
* @return String
*/
public String getWorld() { public String getWorld() {
return world; return world;
} }
/**
* Method setWorld.
*
* @param world String
*/
public void setWorld(String world) { public void setWorld(String world) {
this.world = world; this.world = world;
} }
/**
* Method getIp.
*
* @return String
*/
public String getIp() { public String getIp() {
return ip; return ip;
} }
/**
* Method setIp.
*
* @param ip String
*/
public void setIp(String ip) { public void setIp(String ip) {
this.ip = ip; this.ip = ip;
} }
/**
* Method getLastLogin.
*
* @return long
*/
public long getLastLogin() { public long getLastLogin() {
return lastLogin; return lastLogin;
} }
/**
* Method setLastLogin.
*
* @param lastLogin long
*/
public void setLastLogin(long lastLogin) { public void setLastLogin(long lastLogin) {
this.lastLogin = lastLogin; this.lastLogin = lastLogin;
} }
/**
* Method getEmail.
*
* @return String
*/
public String getEmail() { public String getEmail() {
return email; return email;
} }
/**
* Method setEmail.
*
* @param email String
*/
public void setEmail(String email) { public void setEmail(String email) {
this.email = email; this.email = email;
} }
/** public EncryptedPassword getPassword() {
* Method getSalt. // TODO #358: Check whether this check is really necessary. It's been here since the first commit.
* /*if (Settings.getPasswordHash == HashAlgorithm.MD5VB) {
* @return String
*/
public String getSalt() {
return this.salt;
}
/**
* Method setSalt.
*
* @param salt String
*/
public void setSalt(String salt) {
this.salt = salt;
}
/**
* Method getHash.
*
* @return String
*/
public String getHash() {
if (Settings.getPasswordHash == HashAlgorithm.MD5VB) {
if (salt != null && !salt.isEmpty() && Settings.getPasswordHash == HashAlgorithm.MD5VB) { if (salt != null && !salt.isEmpty() && Settings.getPasswordHash == HashAlgorithm.MD5VB) {
return "$MD5vb$" + salt + "$" + hash; return "$MD5vb$" + salt + "$" + hash;
} }
} }*/
return hash; return password;
} }
/** public void setPassword(EncryptedPassword password) {
* Method setHash. this.password = password;
*
* @param hash String
*/
public void setHash(String hash) {
this.hash = hash;
} }
/**
* Method equals.
*
* @param obj Object
*
* @return boolean
*/
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (!(obj instanceof PlayerAuth)) { if (!(obj instanceof PlayerAuth)) {
@ -431,11 +303,6 @@ public class PlayerAuth {
return other.getIp().equals(this.ip) && other.getNickname().equals(this.nickname); return other.getIp().equals(this.ip) && other.getNickname().equals(this.nickname);
} }
/**
* Method hashCode.
*
* @return int
*/
@Override @Override
public int hashCode() { public int hashCode() {
int hashCode = 7; int hashCode = 7;
@ -444,20 +311,14 @@ public class PlayerAuth {
return hashCode; return hashCode;
} }
/**
* Method toString.
*
* @return String
*/
@Override @Override
public String toString() { public String toString() {
return ("Player : " + nickname + " | " + realName return "Player : " + nickname + " | " + realName
+ " ! IP : " + ip + " ! IP : " + ip
+ " ! LastLogin : " + lastLogin + " ! LastLogin : " + lastLogin
+ " ! LastPosition : " + x + "," + y + "," + z + "," + world + " ! LastPosition : " + x + "," + y + "," + z + "," + world
+ " ! Email : " + email + " ! Email : " + email
+ " ! Hash : " + hash + " ! Password : {" + password.getHash() + ", " + password.getSalt() + "}";
+ " ! Salt : " + salt);
} }
/** /**
@ -472,8 +333,8 @@ public class PlayerAuth {
str.append(this.realName).append(d); str.append(this.realName).append(d);
str.append(this.ip).append(d); str.append(this.ip).append(d);
str.append(this.email).append(d); str.append(this.email).append(d);
str.append(this.hash).append(d); str.append(this.password.getHash()).append(d);
str.append(this.salt).append(d); str.append(this.password.getSalt()).append(d);
str.append(this.groupId).append(d); str.append(this.groupId).append(d);
str.append(this.lastLogin).append(d); str.append(this.lastLogin).append(d);
str.append(this.world).append(d); str.append(this.world).append(d);
@ -492,8 +353,7 @@ public class PlayerAuth {
this.realName = args[1]; this.realName = args[1];
this.ip = args[2]; this.ip = args[2];
this.email = args[3]; this.email = args[3];
this.hash = args[4]; this.password = new EncryptedPassword(args[4], args[5]);
this.salt = args[5];
this.groupId = Integer.parseInt(args[6]); this.groupId = Integer.parseInt(args[6]);
this.lastLogin = Long.parseLong(args[7]); this.lastLogin = Long.parseLong(args[7]);
this.world = args[8]; this.world = args[8];
@ -509,8 +369,7 @@ public class PlayerAuth {
public static final class Builder { public static final class Builder {
private String name; private String name;
private String realName; private String realName;
private String hash; private EncryptedPassword password;
private String salt;
private String ip; private String ip;
private String world; private String world;
private String email; private String email;
@ -523,8 +382,7 @@ public class PlayerAuth {
public PlayerAuth build() { public PlayerAuth build() {
return new PlayerAuth( return new PlayerAuth(
checkNotNull(name), checkNotNull(name),
nullToEmpty(hash), firstNonNull(password, new EncryptedPassword("")),
nullToEmpty(salt),
groupId, groupId,
firstNonNull(ip, "127.0.0.1"), firstNonNull(ip, "127.0.0.1"),
lastLogin, lastLogin,
@ -545,14 +403,13 @@ public class PlayerAuth {
return this; return this;
} }
public Builder hash(String hash) { public Builder password(EncryptedPassword password) {
this.hash = hash; this.password = password;
return this; return this;
} }
public Builder salt(String salt) { public Builder password(String hash, String salt) {
this.salt = salt; return password(new EncryptedPassword(hash, salt));
return this;
} }
public Builder ip(String ip) { public Builder ip(String ip) {
@ -560,6 +417,14 @@ public class PlayerAuth {
return this; return this;
} }
public Builder location(Location location) {
this.x = location.getX();
this.y = location.getY();
this.z = location.getZ();
this.world = location.getWorld().getName();
return this;
}
public Builder locWorld(String world) { public Builder locWorld(String world) {
this.world = world; this.world = world;
return this; return this;

View File

@ -7,6 +7,7 @@ import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.output.Messages; import fr.xephi.authme.output.Messages;
import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.process.Management; import fr.xephi.authme.process.Management;
import fr.xephi.authme.security.PasswordSecurity;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.util.List; import java.util.List;
@ -21,6 +22,7 @@ public class CommandService {
private final Messages messages; private final Messages messages;
private final HelpProvider helpProvider; private final HelpProvider helpProvider;
private final CommandMapper commandMapper; private final CommandMapper commandMapper;
private final PasswordSecurity passwordSecurity;
/** /**
* Constructor. * Constructor.
@ -30,11 +32,13 @@ public class CommandService {
* @param helpProvider Help provider * @param helpProvider Help provider
* @param messages Messages instance * @param messages Messages instance
*/ */
public CommandService(AuthMe authMe, CommandMapper commandMapper, HelpProvider helpProvider, Messages messages) { public CommandService(AuthMe authMe, CommandMapper commandMapper, HelpProvider helpProvider, Messages messages,
PasswordSecurity passwordSecurity) {
this.authMe = authMe; this.authMe = authMe;
this.messages = messages; this.messages = messages;
this.helpProvider = helpProvider; this.helpProvider = helpProvider;
this.commandMapper = commandMapper; this.commandMapper = commandMapper;
this.passwordSecurity = passwordSecurity;
} }
/** /**
@ -88,8 +92,16 @@ public class CommandService {
* @return The used data source * @return The used data source
*/ */
public DataSource getDataSource() { public DataSource getDataSource() {
// TODO ljacqu 20151222: Add getter for .database and rename the field to dataSource return authMe.getDataSource();
return authMe.database; }
/**
* Return the PasswordSecurity instance.
*
* @return The password security instance
*/
public PasswordSecurity getPasswordSecurity() {
return passwordSecurity;
} }
/** /**

View File

@ -1,6 +1,5 @@
package fr.xephi.authme.command.executable.authme; package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.auth.PlayerCache;
@ -8,13 +7,10 @@ import fr.xephi.authme.command.CommandService;
import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.output.Messages; import fr.xephi.authme.security.crypts.EncryptedPassword;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.security.NoSuchAlgorithmException;
import java.util.List; import java.util.List;
/** /**
@ -60,13 +56,6 @@ public class ChangePasswordAdminCommand implements ExecutableCommand {
@Override @Override
public void run() { public void run() {
DataSource dataSource = commandService.getDataSource(); DataSource dataSource = commandService.getDataSource();
String hash;
try {
hash = PasswordSecurity.getHash(Settings.getPasswordHash, playerPass, playerNameLowerCase);
} catch (NoSuchAlgorithmException e) {
commandService.send(sender, MessageKey.ERROR);
return;
}
PlayerAuth auth = null; PlayerAuth auth = null;
if (PlayerCache.getInstance().isAuthenticated(playerNameLowerCase)) { if (PlayerCache.getInstance().isAuthenticated(playerNameLowerCase)) {
auth = PlayerCache.getInstance().getAuth(playerNameLowerCase); auth = PlayerCache.getInstance().getAuth(playerNameLowerCase);
@ -77,17 +66,17 @@ public class ChangePasswordAdminCommand implements ExecutableCommand {
commandService.send(sender, MessageKey.UNKNOWN_USER); commandService.send(sender, MessageKey.UNKNOWN_USER);
return; return;
} }
auth.setHash(hash);
if (PasswordSecurity.userSalt.containsKey(playerNameLowerCase)) { // TODO #358: Do we always pass lowercase name?? In that case we need to do that in PasswordSecurity!
auth.setSalt(PasswordSecurity.userSalt.get(playerNameLowerCase)); EncryptedPassword encryptedPassword = commandService.getPasswordSecurity().computeHash(playerPass, playerNameLowerCase);
commandService.getDataSource().updateSalt(auth); auth.setPassword(encryptedPassword);
}
if (!dataSource.updatePassword(auth)) { if (!dataSource.updatePassword(auth)) {
commandService.send(sender, MessageKey.ERROR); commandService.send(sender, MessageKey.ERROR);
return; } else {
commandService.send(sender, MessageKey.PASSWORD_CHANGED_SUCCESS);
ConsoleLogger.info(playerNameLowerCase + "'s password changed");
} }
commandService.send(sender, MessageKey.PASSWORD_CHANGED_SUCCESS);
ConsoleLogger.info(playerNameLowerCase + "'s password changed");
} }
}); });

View File

@ -24,7 +24,7 @@ public class PurgeBannedPlayersCommand implements ExecutableCommand {
} }
// Purge the banned players // Purge the banned players
plugin.database.purgeBanned(bannedPlayers); plugin.getDataSource().purgeBanned(bannedPlayers);
if (Settings.purgeEssentialsFile && plugin.ess != null) if (Settings.purgeEssentialsFile && plugin.ess != null)
plugin.dataManager.purgeEssentials(bannedPlayers); plugin.dataManager.purgeEssentials(bannedPlayers);
if (Settings.purgePlayerDat) if (Settings.purgePlayerDat)

View File

@ -5,12 +5,11 @@ import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.CommandService;
import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.EncryptedPassword;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.security.NoSuchAlgorithmException;
import java.util.List; import java.util.List;
/** /**
@ -41,7 +40,8 @@ public class RegisterAdminCommand implements ExecutableCommand {
commandService.send(sender, MessageKey.PASSWORD_IS_USERNAME_ERROR); commandService.send(sender, MessageKey.PASSWORD_IS_USERNAME_ERROR);
return; return;
} }
if (playerPassLowerCase.length() < Settings.getPasswordMinLen || playerPassLowerCase.length() > Settings.passwordMaxLength) { if (playerPassLowerCase.length() < Settings.getPasswordMinLen
|| playerPassLowerCase.length() > Settings.passwordMaxLength) {
commandService.send(sender, MessageKey.INVALID_PASSWORD_LENGTH); commandService.send(sender, MessageKey.INVALID_PASSWORD_LENGTH);
return; return;
} }
@ -53,30 +53,29 @@ public class RegisterAdminCommand implements ExecutableCommand {
@Override @Override
public void run() { public void run() {
try { if (commandService.getDataSource().isAuthAvailable(playerNameLowerCase)) {
if (commandService.getDataSource().isAuthAvailable(playerNameLowerCase)) { commandService.send(sender, MessageKey.NAME_ALREADY_REGISTERED);
commandService.send(sender, MessageKey.NAME_ALREADY_REGISTERED); return;
return;
}
String hash = PasswordSecurity.getHash(Settings.getPasswordHash, playerPass, playerNameLowerCase);
PlayerAuth auth = new PlayerAuth(playerNameLowerCase, hash, "192.168.0.1", 0L, "your@email.com", playerName);
if (PasswordSecurity.userSalt.containsKey(playerNameLowerCase) && PasswordSecurity.userSalt.get(playerNameLowerCase) != null)
auth.setSalt(PasswordSecurity.userSalt.get(playerNameLowerCase));
else auth.setSalt("");
if (!commandService.getDataSource().saveAuth(auth)) {
commandService.send(sender, MessageKey.ERROR);
return;
}
commandService.getDataSource().setUnlogged(playerNameLowerCase);
if (Bukkit.getPlayerExact(playerName) != null)
Bukkit.getPlayerExact(playerName).kickPlayer("An admin just registered you, please log again");
commandService.send(sender, MessageKey.REGISTER_SUCCESS);
ConsoleLogger.info(playerNameLowerCase + " registered");
} catch (NoSuchAlgorithmException ex) {
ConsoleLogger.showError(ex.getMessage());
commandService.send(sender, MessageKey.ERROR);
} }
EncryptedPassword encryptedPassword = commandService.getPasswordSecurity()
.computeHash(playerPass, playerNameLowerCase);
PlayerAuth auth = PlayerAuth.builder()
.name(playerNameLowerCase)
.realName(playerName)
.password(encryptedPassword)
.build();
if (!commandService.getDataSource().saveAuth(auth)) {
commandService.send(sender, MessageKey.ERROR);
return;
}
commandService.getDataSource().setUnlogged(playerNameLowerCase);
if (Bukkit.getPlayerExact(playerName) != null) {
Bukkit.getPlayerExact(playerName).kickPlayer("An admin just registered you, please log again");
} else {
commandService.send(sender, MessageKey.REGISTER_SUCCESS);
ConsoleLogger.info(playerName + " registered");
}
} }
}); });
} }

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

@ -1,20 +1,18 @@
package fr.xephi.authme.command.executable.email; package fr.xephi.authme.command.executable.email;
import fr.xephi.authme.AuthMe; import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.auth.PlayerCache;
import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.CommandService;
import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.RandomString; import fr.xephi.authme.security.RandomString;
import fr.xephi.authme.security.crypts.EncryptedPassword;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.StringUtils;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.security.NoSuchAlgorithmException;
import java.util.List; import java.util.List;
public class RecoverEmailCommand extends PlayerCommand { public class RecoverEmailCommand extends PlayerCommand {
@ -37,37 +35,32 @@ public class RecoverEmailCommand extends PlayerCommand {
commandService.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); commandService.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR);
return; return;
} }
try {
RandomString rand = new RandomString(Settings.getRecoveryPassLength);
String thePass = rand.nextString();
String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, thePass, playerName);
PlayerAuth auth;
if (PlayerCache.getInstance().isAuthenticated(playerName)) {
auth = PlayerCache.getInstance().getAuth(playerName);
} else if (dataSource.isAuthAvailable(playerName)) {
auth = dataSource.getAuth(playerName);
} else {
commandService.send(player, MessageKey.UNKNOWN_USER);
return;
}
if (Settings.getmailAccount.equals("") || Settings.getmailAccount.isEmpty()) {
commandService.send(player, MessageKey.ERROR);
return;
}
if (!playerMail.equalsIgnoreCase(auth.getEmail()) || playerMail.equalsIgnoreCase("your@email.com") String thePass = RandomString.generate(Settings.getRecoveryPassLength);
|| auth.getEmail().equalsIgnoreCase("your@email.com")) { EncryptedPassword hashNew = commandService.getPasswordSecurity().computeHash(thePass, playerName);
commandService.send(player, MessageKey.INVALID_EMAIL); PlayerAuth auth;
return; if (PlayerCache.getInstance().isAuthenticated(playerName)) {
} auth = PlayerCache.getInstance().getAuth(playerName);
auth.setHash(hashNew); } else if (dataSource.isAuthAvailable(playerName)) {
dataSource.updatePassword(auth); auth = dataSource.getAuth(playerName);
plugin.mail.main(auth, thePass); } else {
commandService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); commandService.send(player, MessageKey.UNKNOWN_USER);
} catch (NoSuchAlgorithmException | NoClassDefFoundError ex) { return;
ConsoleLogger.showError(StringUtils.formatException(ex));
commandService.send(player, MessageKey.ERROR);
} }
if (StringUtils.isEmpty(Settings.getmailAccount)) {
commandService.send(player, MessageKey.ERROR);
return;
}
if (!playerMail.equalsIgnoreCase(auth.getEmail()) || playerMail.equalsIgnoreCase("your@email.com")
|| auth.getEmail().equalsIgnoreCase("your@email.com")) {
commandService.send(player, MessageKey.INVALID_EMAIL);
return;
}
auth.setPassword(hashNew);
dataSource.updatePassword(auth);
plugin.mail.main(auth, thePass);
commandService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
} else { } else {
commandService.send(player, MessageKey.REGISTER_EMAIL_MESSAGE); commandService.send(player, MessageKey.REGISTER_EMAIL_MESSAGE);
} }

View File

@ -27,7 +27,7 @@ public class CrazyLoginConverter implements Converter {
* @param sender CommandSender * @param sender CommandSender
*/ */
public CrazyLoginConverter(AuthMe instance, CommandSender sender) { public CrazyLoginConverter(AuthMe instance, CommandSender sender) {
this.database = instance.database; this.database = instance.getDataSource();
this.sender = sender; this.sender = sender;
} }

View File

@ -6,6 +6,7 @@ import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.crypts.EncryptedPassword;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -13,7 +14,6 @@ import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -28,15 +28,12 @@ public class RakamakConverter implements Converter {
public RakamakConverter(AuthMe instance, CommandSender sender) { public RakamakConverter(AuthMe instance, CommandSender sender) {
this.instance = instance; this.instance = instance;
this.database = instance.database; this.database = instance.getDataSource();
this.sender = sender; this.sender = sender;
} }
public RakamakConverter getInstance() {
return this;
}
@Override @Override
// TODO ljacqu 20151229: Restructure this into smaller portions
public void run() { public void run() {
HashAlgorithm hash = Settings.getPasswordHash; HashAlgorithm hash = Settings.getPasswordHash;
boolean useIP = Settings.rakamakUseIp; boolean useIP = Settings.rakamakUseIp;
@ -45,7 +42,7 @@ public class RakamakConverter implements Converter {
File source = new File(Settings.PLUGIN_FOLDER, fileName); File source = new File(Settings.PLUGIN_FOLDER, fileName);
File ipfiles = new File(Settings.PLUGIN_FOLDER, ipFileName); File ipfiles = new File(Settings.PLUGIN_FOLDER, ipFileName);
HashMap<String, String> playerIP = new HashMap<>(); HashMap<String, String> playerIP = new HashMap<>();
HashMap<String, String> playerPSW = new HashMap<>(); HashMap<String, EncryptedPassword> playerPSW = new HashMap<>();
try { try {
BufferedReader users; BufferedReader users;
BufferedReader ipFile; BufferedReader ipFile;
@ -61,30 +58,29 @@ public class RakamakConverter implements Converter {
} }
} }
ipFile.close(); ipFile.close();
users = new BufferedReader(new FileReader(source)); users = new BufferedReader(new FileReader(source));
PasswordSecurity passwordSecurity = instance.getPasswordSecurity();
while ((line = users.readLine()) != null) { while ((line = users.readLine()) != null) {
if (line.contains("=")) { if (line.contains("=")) {
String[] arguments = line.split("="); String[] arguments = line.split("=");
try { EncryptedPassword encryptedPassword = passwordSecurity.computeHash(hash, arguments[1], arguments[0]);
playerPSW.put(arguments[0], PasswordSecurity.getHash(hash, arguments[1], arguments[0])); playerPSW.put(arguments[0], encryptedPassword);
} catch (NoSuchAlgorithmException e) {
ConsoleLogger.showError(e.getMessage());
}
} }
} }
users.close(); users.close();
for (Entry<String, String> m : playerPSW.entrySet()) { for (Entry<String, EncryptedPassword> m : playerPSW.entrySet()) {
String playerName = m.getKey(); String playerName = m.getKey();
String psw = playerPSW.get(playerName); EncryptedPassword psw = playerPSW.get(playerName);
String ip; String ip = useIP ? playerIP.get(playerName) : "127.0.0.1";
if (useIP) { PlayerAuth auth = PlayerAuth.builder()
ip = playerIP.get(playerName); .name(playerName)
} else { .realName(playerName)
ip = "127.0.0.1"; .ip(ip)
} .password(psw)
PlayerAuth auth = new PlayerAuth(playerName, psw, ip, System.currentTimeMillis(), playerName); .lastLogin(System.currentTimeMillis())
if (PasswordSecurity.userSalt.containsKey(playerName)) .build();
auth.setSalt(PasswordSecurity.userSalt.get(playerName));
database.saveAuth(auth); database.saveAuth(auth);
} }
ConsoleLogger.info("Rakamak database has been imported correctly"); ConsoleLogger.info("Rakamak database has been imported correctly");

View File

@ -15,7 +15,7 @@ public class RoyalAuthConverter implements Converter {
public RoyalAuthConverter(AuthMe plugin) { public RoyalAuthConverter(AuthMe plugin) {
this.plugin = plugin; this.plugin = plugin;
this.data = plugin.database; this.data = plugin.getDataSource();
} }
@Override @Override

View File

@ -21,16 +21,14 @@ public class SqliteToSql implements Converter {
@Override @Override
public void run() { public void run() {
if (plugin.database.getType() != DataSourceType.MYSQL) if (plugin.getDataSource().getType() != DataSourceType.MYSQL) {
{
sender.sendMessage("Please config your mySQL connection and re-run this command"); sender.sendMessage("Please config your mySQL connection and re-run this command");
return; return;
} }
try { try {
SQLite data = new SQLite(); SQLite data = new SQLite();
for (PlayerAuth auth : data.getAllAuths()) for (PlayerAuth auth : data.getAllAuths()) {
{ plugin.getDataSource().saveAuth(auth);
plugin.database.saveAuth(auth);
} }
} catch (Exception e) { } catch (Exception e) {
sender.sendMessage(plugin.getMessages().retrieve(MessageKey.ERROR)); sender.sendMessage(plugin.getMessages().retrieve(MessageKey.ERROR));

View File

@ -23,7 +23,7 @@ class vAuthFileReader {
*/ */
public vAuthFileReader(AuthMe plugin) { public vAuthFileReader(AuthMe plugin) {
this.plugin = plugin; this.plugin = plugin;
this.database = plugin.database; this.database = plugin.getDataSource();
} }
public void convert() { public void convert() {

View File

@ -30,7 +30,7 @@ class xAuthToFlat {
*/ */
public xAuthToFlat(AuthMe instance, CommandSender sender) { public xAuthToFlat(AuthMe instance, CommandSender sender) {
this.instance = instance; this.instance = instance;
this.database = instance.database; this.database = instance.getDataSource();
this.sender = sender; this.sender = sender;
} }

View File

@ -270,24 +270,6 @@ public class CacheDataSource implements DataSource {
return result; return result;
} }
/**
* Method updateSalt.
*
* @param auth PlayerAuth
*
* @return boolean
*
* @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth)
*/
@Override
public synchronized boolean updateSalt(final PlayerAuth auth) {
boolean result = source.updateSalt(auth);
if (result) {
cachedAuths.refresh(auth.getNickname());
}
return result;
}
/** /**
* Method getAllAuthsByName. * Method getAllAuthsByName.
* *

View File

@ -134,15 +134,6 @@ public interface DataSource {
*/ */
boolean updateEmail(PlayerAuth auth); boolean updateEmail(PlayerAuth auth);
/**
* Method updateSalt.
*
* @param auth PlayerAuth
*
* @return boolean
*/
boolean updateSalt(PlayerAuth auth);
void close(); void close();
void reload(); void reload();

View File

@ -102,7 +102,7 @@ public class FlatFile implements DataSource {
BufferedWriter bw = null; BufferedWriter bw = null;
try { try {
bw = new BufferedWriter(new FileWriter(source, true)); bw = new BufferedWriter(new FileWriter(source, true));
bw.write(auth.getNickname() + ":" + auth.getHash() + ":" + auth.getIp() + ":" + auth.getLastLogin() + ":" + auth.getQuitLocX() + ":" + auth.getQuitLocY() + ":" + auth.getQuitLocZ() + ":" + auth.getWorld() + ":" + auth.getEmail() + "\n"); bw.write(auth.getNickname() + ":" + auth.getPassword() + ":" + auth.getIp() + ":" + auth.getLastLogin() + ":" + auth.getQuitLocX() + ":" + auth.getQuitLocY() + ":" + auth.getQuitLocZ() + ":" + auth.getWorld() + ":" + auth.getEmail() + "\n");
} catch (IOException ex) { } catch (IOException ex) {
ConsoleLogger.showError(ex.getMessage()); ConsoleLogger.showError(ex.getMessage());
return false; return false;
@ -137,25 +137,26 @@ public class FlatFile implements DataSource {
while ((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
String[] args = line.split(":"); String[] args = line.split(":");
if (args[0].equals(auth.getNickname())) { if (args[0].equals(auth.getNickname())) {
// Note ljacqu 20151230: This does not persist the salt; it is not supported in flat file.
switch (args.length) { switch (args.length) {
case 4: { case 4: {
newAuth = new PlayerAuth(args[0], auth.getHash(), args[2], Long.parseLong(args[3]), 0, 0, 0, "world", "your@email.com", args[0]); newAuth = new PlayerAuth(args[0], auth.getPassword().getHash(), args[2], Long.parseLong(args[3]), 0, 0, 0, "world", "your@email.com", args[0]);
break; break;
} }
case 7: { case 7: {
newAuth = new PlayerAuth(args[0], auth.getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), "world", "your@email.com", args[0]); newAuth = new PlayerAuth(args[0], auth.getPassword().getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), "world", "your@email.com", args[0]);
break; break;
} }
case 8: { case 8: {
newAuth = new PlayerAuth(args[0], auth.getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), args[7], "your@email.com", args[0]); newAuth = new PlayerAuth(args[0], auth.getPassword().getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), args[7], "your@email.com", args[0]);
break; break;
} }
case 9: { case 9: {
newAuth = new PlayerAuth(args[0], auth.getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), args[7], args[8], args[0]); newAuth = new PlayerAuth(args[0], auth.getPassword().getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), args[7], args[8], args[0]);
break; break;
} }
default: { default: {
newAuth = new PlayerAuth(args[0], auth.getHash(), args[2], 0, 0, 0, 0, "world", "your@email.com", args[0]); newAuth = new PlayerAuth(args[0], auth.getPassword().getHash(), args[2], 0, 0, 0, 0, "world", "your@email.com", args[0]);
break; break;
} }
} }
@ -600,18 +601,6 @@ public class FlatFile implements DataSource {
return true; return true;
} }
/**
* Method updateSalt.
*
* @param auth PlayerAuth
*
* @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth)
*/
@Override
public boolean updateSalt(PlayerAuth auth) {
return false;
}
/** /**
* Method getAllAuthsByName. * Method getAllAuthsByName.
* *

View File

@ -7,6 +7,7 @@ import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.util.StringUtils;
import java.sql.*; import java.sql.*;
import java.util.ArrayList; import java.util.ArrayList;
@ -39,11 +40,6 @@ public class MySQL implements DataSource {
private final List<String> columnOthers; private final List<String> columnOthers;
private HikariDataSource ds; private HikariDataSource ds;
/**
* Constructor for MySQL.
*
* @throws ClassNotFoundException * @throws SQLException * @throws PoolInitializationException
*/
public MySQL() throws ClassNotFoundException, SQLException, PoolInitializationException { public MySQL() throws ClassNotFoundException, SQLException, PoolInitializationException {
this.host = Settings.getMySQLHost; this.host = Settings.getMySQLHost;
this.port = Settings.getMySQLPort; this.port = Settings.getMySQLPort;
@ -96,11 +92,6 @@ public class MySQL implements DataSource {
} }
} }
/**
* Method setConnectionArguments.
*
* @throws RuntimeException
*/
private synchronized void setConnectionArguments() throws RuntimeException { private synchronized void setConnectionArguments() throws RuntimeException {
ds = new HikariDataSource(); ds = new HikariDataSource();
ds.setPoolName("AuthMeMYSQLPool"); ds.setPoolName("AuthMeMYSQLPool");
@ -116,11 +107,6 @@ public class MySQL implements DataSource {
ConsoleLogger.info("Connection arguments loaded, Hikari ConnectionPool ready!"); ConsoleLogger.info("Connection arguments loaded, Hikari ConnectionPool ready!");
} }
/**
* Method reloadArguments.
*
* @throws RuntimeException
*/
private synchronized void reloadArguments() throws RuntimeException { private synchronized void reloadArguments() throws RuntimeException {
if (ds != null) { if (ds != null) {
ds.close(); ds.close();
@ -129,20 +115,10 @@ public class MySQL implements DataSource {
ConsoleLogger.info("Hikari ConnectionPool arguments reloaded!"); ConsoleLogger.info("Hikari ConnectionPool arguments reloaded!");
} }
/**
* Method getConnection.
*
* @return Connection * @throws SQLException
*/
private synchronized Connection getConnection() throws SQLException { private synchronized Connection getConnection() throws SQLException {
return ds.getConnection(); return ds.getConnection();
} }
/**
* Method setupConnection.
*
* @throws SQLException
*/
private synchronized void setupConnection() throws SQLException { private synchronized void setupConnection() throws SQLException {
try (Connection con = getConnection()) { try (Connection con = getConnection()) {
Statement st = con.createStatement(); Statement st = con.createStatement();
@ -186,6 +162,15 @@ public class MySQL implements DataSource {
} }
rs.close(); rs.close();
if (!columnSalt.isEmpty()) {
rs = md.getColumns(null, null, tableName, columnSalt);
if (!rs.next()) {
st.executeUpdate("ALTER TABLE " + tableName
+ " ADD COLUMN " + columnSalt + " VARCHAR(255);");
}
rs.close();
}
rs = md.getColumns(null, null, tableName, columnIp); rs = md.getColumns(null, null, tableName, columnIp);
if (!rs.next()) { if (!rs.next()) {
st.executeUpdate("ALTER TABLE " + tableName st.executeUpdate("ALTER TABLE " + tableName
@ -244,13 +229,6 @@ public class MySQL implements DataSource {
ConsoleLogger.info("MySQL Setup finished"); ConsoleLogger.info("MySQL Setup finished");
} }
/**
* Method isAuthAvailable.
*
* @param user String
*
* @return boolean * @see fr.xephi.authme.datasource.DataSource#isAuthAvailable(String)
*/
@Override @Override
public synchronized boolean isAuthAvailable(String user) { public synchronized boolean isAuthAvailable(String user) {
try (Connection con = getConnection()) { try (Connection con = getConnection()) {
@ -266,13 +244,6 @@ public class MySQL implements DataSource {
return false; return false;
} }
/**
* Method getAuth.
*
* @param user String
*
* @return PlayerAuth * @see fr.xephi.authme.datasource.DataSource#getAuth(String)
*/
@Override @Override
public synchronized PlayerAuth getAuth(String user) { public synchronized PlayerAuth getAuth(String user) {
PlayerAuth pAuth; PlayerAuth pAuth;
@ -284,13 +255,12 @@ public class MySQL implements DataSource {
if (!rs.next()) { if (!rs.next()) {
return null; return null;
} }
String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : ""; String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : null;
int group = !salt.isEmpty() && !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; int group = !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1;
int id = rs.getInt(columnID);
pAuth = PlayerAuth.builder() pAuth = PlayerAuth.builder()
.name(rs.getString(columnName)) .name(rs.getString(columnName))
.realName(rs.getString(columnRealName)) .realName(rs.getString(columnRealName))
.hash(rs.getString(columnPassword)) .password(rs.getString(columnPassword), salt)
.lastLogin(rs.getLong(columnLastLogin)) .lastLogin(rs.getLong(columnLastLogin))
.ip(rs.getString(columnIp)) .ip(rs.getString(columnIp))
.locWorld(rs.getString(lastlocWorld)) .locWorld(rs.getString(lastlocWorld))
@ -298,21 +268,10 @@ public class MySQL implements DataSource {
.locY(rs.getDouble(lastlocY)) .locY(rs.getDouble(lastlocY))
.locZ(rs.getDouble(lastlocZ)) .locZ(rs.getDouble(lastlocZ))
.email(rs.getString(columnEmail)) .email(rs.getString(columnEmail))
.salt(salt)
.groupId(group) .groupId(group)
.build(); .build();
rs.close(); rs.close();
pst.close(); pst.close();
if (Settings.getPasswordHash == HashAlgorithm.XENFORO) {
pst = con.prepareStatement("SELECT data FROM xf_user_authenticate WHERE " + columnID + "=?;");
pst.setInt(1, id);
rs = pst.executeQuery();
if (rs.next()) {
Blob blob = rs.getBlob("data");
byte[] bytes = blob.getBytes(1, (int) blob.length());
pAuth.setHash(new String(bytes));
}
}
} catch (SQLException ex) { } catch (SQLException ex) {
ConsoleLogger.showError(ex.getMessage()); ConsoleLogger.showError(ex.getMessage());
ConsoleLogger.writeStackTrace(ex); ConsoleLogger.writeStackTrace(ex);
@ -321,15 +280,6 @@ public class MySQL implements DataSource {
return pAuth; return pAuth;
} }
/**
* Method saveAuth.
*
* @param auth PlayerAuth
*
* @return boolean
*
* @see fr.xephi.authme.datasource.DataSource#saveAuth(PlayerAuth)
*/
@Override @Override
public synchronized boolean saveAuth(PlayerAuth auth) { public synchronized boolean saveAuth(PlayerAuth auth) {
try (Connection con = getConnection()) { try (Connection con = getConnection()) {
@ -338,7 +288,7 @@ public class MySQL implements DataSource {
ResultSet rs; ResultSet rs;
String sql; String sql;
boolean useSalt = !columnSalt.isEmpty() || !auth.getSalt().isEmpty(); boolean useSalt = !columnSalt.isEmpty() || !StringUtils.isEmpty(auth.getPassword().getSalt());
sql = "INSERT INTO " + tableName + "(" sql = "INSERT INTO " + tableName + "("
+ columnName + "," + columnPassword + "," + columnIp + "," + columnName + "," + columnPassword + "," + columnIp + ","
+ columnLastLogin + "," + columnRealName + columnLastLogin + "," + columnRealName
@ -346,12 +296,12 @@ public class MySQL implements DataSource {
+ ") VALUES (?,?,?,?,?" + (useSalt ? ",?" : "") + ");"; + ") VALUES (?,?,?,?,?" + (useSalt ? ",?" : "") + ");";
pst = con.prepareStatement(sql); pst = con.prepareStatement(sql);
pst.setString(1, auth.getNickname()); pst.setString(1, auth.getNickname());
pst.setString(2, auth.getHash()); pst.setString(2, auth.getPassword().getHash());
pst.setString(3, auth.getIp()); pst.setString(3, auth.getIp());
pst.setLong(4, auth.getLastLogin()); pst.setLong(4, auth.getLastLogin());
pst.setString(5, auth.getRealName()); pst.setString(5, auth.getRealName());
if (useSalt) { if (useSalt) {
pst.setString(6, auth.getSalt()); pst.setString(6, auth.getPassword().getSalt());
} }
pst.executeUpdate(); pst.executeUpdate();
pst.close(); pst.close();
@ -502,25 +452,6 @@ public class MySQL implements DataSource {
} }
rs.close(); rs.close();
pst.close(); pst.close();
} else if (Settings.getPasswordHash == HashAlgorithm.XENFORO) {
pst = con.prepareStatement("SELECT " + columnID + " FROM " + tableName + " WHERE " + columnName + "=?;");
pst.setString(1, auth.getNickname());
rs = pst.executeQuery();
if (rs.next()) {
int id = rs.getInt(columnID);
// Insert password in the correct table
pst2 = con.prepareStatement("INSERT INTO xf_user_authenticate (user_id, scheme_class, data) VALUES (?,?,?);");
pst2.setInt(1, id);
pst2.setString(2, "XenForo_Authentication_Core12");
byte[] bytes = auth.getHash().getBytes();
Blob blob = con.createBlob();
blob.setBytes(1, bytes);
pst2.setBlob(3, blob);
pst2.executeUpdate();
pst2.close();
}
rs.close();
pst.close();
} }
return true; return true;
} catch (SQLException ex) { } catch (SQLException ex) {
@ -530,52 +461,27 @@ public class MySQL implements DataSource {
return false; return false;
} }
/**
* Method updatePassword.
*
* @param auth PlayerAuth
*
* @return boolean
*
* @see fr.xephi.authme.datasource.DataSource#updatePassword(PlayerAuth)
*/
@Override @Override
public synchronized boolean updatePassword(PlayerAuth auth) { public synchronized boolean updatePassword(PlayerAuth auth) {
try (Connection con = getConnection()) { try (Connection con = getConnection()) {
String sql = "UPDATE " + tableName + " SET " + columnPassword + "=? WHERE " + columnName + "=?;"; boolean useSalt = !columnSalt.isEmpty();
PreparedStatement pst = con.prepareStatement(sql); PreparedStatement pst;
pst.setString(1, auth.getHash()); if (useSalt) {
pst.setString(2, auth.getNickname()); String sql = String.format("UPDATE %s SET %s = ?, %s = ? WHERE %s = ?;",
tableName, columnPassword, columnSalt, columnName);
pst = con.prepareStatement(sql);
pst.setString(1, auth.getPassword().getHash());
pst.setString(2, auth.getPassword().getSalt());
pst.setString(3, auth.getNickname());
} else {
String sql = String.format("UPDATE %s SET %s = ? WHERE %s = ?;",
tableName, columnPassword, columnName);
pst = con.prepareStatement(sql);
pst.setString(1, auth.getPassword().getHash());
pst.setString(2, auth.getNickname());
}
pst.executeUpdate(); pst.executeUpdate();
pst.close(); pst.close();
if (Settings.getPasswordHash == HashAlgorithm.XENFORO) {
sql = "SELECT " + columnID + " FROM " + tableName + " WHERE " + columnName + "=?;";
pst = con.prepareStatement(sql);
pst.setString(1, auth.getNickname());
ResultSet rs = pst.executeQuery();
if (rs.next()) {
int id = rs.getInt(columnID);
// Insert password in the correct table
sql = "UPDATE xf_user_authenticate SET data=? WHERE " + columnID + "=?;";
PreparedStatement pst2 = con.prepareStatement(sql);
byte[] bytes = auth.getHash().getBytes();
Blob blob = con.createBlob();
blob.setBytes(1, bytes);
pst2.setBlob(1, blob);
pst2.setInt(2, id);
pst2.executeUpdate();
pst2.close();
// ...
sql = "UPDATE xf_user_authenticate SET scheme_class=? WHERE " + columnID + "=?;";
pst2 = con.prepareStatement(sql);
pst2.setString(1, "XenForo_Authentication_Core12");
pst2.setInt(2, id);
pst2.executeUpdate();
pst2.close();
}
rs.close();
pst.close();
}
return true; return true;
} catch (SQLException ex) { } catch (SQLException ex) {
ConsoleLogger.showError(ex.getMessage()); ConsoleLogger.showError(ex.getMessage());
@ -584,15 +490,6 @@ public class MySQL implements DataSource {
return false; return false;
} }
/**
* Method updateSession.
*
* @param auth PlayerAuth
*
* @return boolean
*
* @see fr.xephi.authme.datasource.DataSource#updateSession(PlayerAuth)
*/
@Override @Override
public synchronized boolean updateSession(PlayerAuth auth) { public synchronized boolean updateSession(PlayerAuth auth) {
try (Connection con = getConnection()) { try (Connection con = getConnection()) {
@ -612,15 +509,6 @@ public class MySQL implements DataSource {
return false; return false;
} }
/**
* Method purgeDatabase.
*
* @param until long
*
* @return int
*
* @see fr.xephi.authme.datasource.DataSource#purgeDatabase(long)
*/
@Override @Override
public synchronized int purgeDatabase(long until) { public synchronized int purgeDatabase(long until) {
int result = 0; int result = 0;
@ -636,15 +524,6 @@ public class MySQL implements DataSource {
return result; return result;
} }
/**
* Method autoPurgeDatabase.
*
* @param until long
*
* @return List
*
* @see fr.xephi.authme.datasource.DataSource#autoPurgeDatabase(long)
*/
@Override @Override
public synchronized List<String> autoPurgeDatabase(long until) { public synchronized List<String> autoPurgeDatabase(long until) {
List<String> list = new ArrayList<>(); List<String> list = new ArrayList<>();
@ -666,37 +545,11 @@ public class MySQL implements DataSource {
return list; return list;
} }
/**
* Method removeAuth.
*
* @param user String
*
* @return boolean
*
* @see fr.xephi.authme.datasource.DataSource#removeAuth(String)
*/
@Override @Override
public synchronized boolean removeAuth(String user) { public synchronized boolean removeAuth(String user) {
user = user.toLowerCase(); user = user.toLowerCase();
try (Connection con = getConnection()) { try (Connection con = getConnection()) {
String sql; PreparedStatement pst = con.prepareStatement("DELETE FROM " + tableName + " WHERE " + columnName + "=?;");
PreparedStatement pst;
if (Settings.getPasswordHash == HashAlgorithm.XENFORO) {
sql = "SELECT " + columnID + " FROM " + tableName + " WHERE " + columnName + "=?;";
pst = con.prepareStatement(sql);
pst.setString(1, user);
ResultSet rs = pst.executeQuery();
if (rs.next()) {
int id = rs.getInt(columnID);
sql = "DELETE FROM xf_user_authenticate WHERE " + columnID + "=" + id;
Statement st = con.createStatement();
st.executeUpdate(sql);
st.close();
}
rs.close();
pst.close();
}
pst = con.prepareStatement("DELETE FROM " + tableName + " WHERE " + columnName + "=?;");
pst.setString(1, user); pst.setString(1, user);
pst.executeUpdate(); pst.executeUpdate();
return true; return true;
@ -707,15 +560,6 @@ public class MySQL implements DataSource {
return false; return false;
} }
/**
* Method updateQuitLoc.
*
* @param auth PlayerAuth
*
* @return boolean
*
* @see fr.xephi.authme.datasource.DataSource#updateQuitLoc(PlayerAuth)
*/
@Override @Override
public synchronized boolean updateQuitLoc(PlayerAuth auth) { public synchronized boolean updateQuitLoc(PlayerAuth auth) {
try (Connection con = getConnection()) { try (Connection con = getConnection()) {
@ -738,15 +582,6 @@ public class MySQL implements DataSource {
return false; return false;
} }
/**
* Method getIps.
*
* @param ip String
*
* @return int
*
* @see fr.xephi.authme.datasource.DataSource#getIps(String)
*/
@Override @Override
public synchronized int getIps(String ip) { public synchronized int getIps(String ip) {
int countIp = 0; int countIp = 0;
@ -767,15 +602,6 @@ public class MySQL implements DataSource {
return countIp; return countIp;
} }
/**
* Method updateEmail.
*
* @param auth PlayerAuth
*
* @return boolean
*
* @see fr.xephi.authme.datasource.DataSource#updateEmail(PlayerAuth)
*/
@Override @Override
public synchronized boolean updateEmail(PlayerAuth auth) { public synchronized boolean updateEmail(PlayerAuth auth) {
try (Connection con = getConnection()) { try (Connection con = getConnection()) {
@ -793,40 +619,6 @@ public class MySQL implements DataSource {
return false; return false;
} }
/**
* Method updateSalt.
*
* @param auth PlayerAuth
*
* @return boolean
*
* @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth)
*/
@Override
public synchronized boolean updateSalt(PlayerAuth auth) {
if (columnSalt.isEmpty()) {
return false;
}
try (Connection con = getConnection()) {
String sql = "UPDATE " + tableName + " SET " + columnSalt + " =? WHERE " + columnName + "=?;";
PreparedStatement pst = con.prepareStatement(sql);
pst.setString(1, auth.getSalt());
pst.setString(2, auth.getNickname());
pst.executeUpdate();
pst.close();
return true;
} catch (SQLException ex) {
ConsoleLogger.showError(ex.getMessage());
ConsoleLogger.writeStackTrace(ex);
}
return false;
}
/**
* Method reload.
*
* @see fr.xephi.authme.datasource.DataSource#reload()
*/
@Override @Override
public void reload() { public void reload() {
try { try {
@ -839,11 +631,6 @@ public class MySQL implements DataSource {
} }
} }
/**
* Method close.
*
* @see fr.xephi.authme.datasource.DataSource#close()
*/
@Override @Override
public synchronized void close() { public synchronized void close() {
if (ds != null && !ds.isClosed()) { if (ds != null && !ds.isClosed()) {
@ -851,15 +638,6 @@ public class MySQL implements DataSource {
} }
} }
/**
* Method getAllAuthsByName.
*
* @param auth PlayerAuth
*
* @return List
*
* @see fr.xephi.authme.datasource.DataSource#getAllAuthsByName(PlayerAuth)
*/
@Override @Override
public synchronized List<String> getAllAuthsByName(PlayerAuth auth) { public synchronized List<String> getAllAuthsByName(PlayerAuth auth) {
List<String> result = new ArrayList<>(); List<String> result = new ArrayList<>();
@ -880,15 +658,6 @@ public class MySQL implements DataSource {
return result; return result;
} }
/**
* Method getAllAuthsByIp.
*
* @param ip String
*
* @return List
*
* @see fr.xephi.authme.datasource.DataSource#getAllAuthsByIp(String)
*/
@Override @Override
public synchronized List<String> getAllAuthsByIp(String ip) { public synchronized List<String> getAllAuthsByIp(String ip) {
List<String> result = new ArrayList<>(); List<String> result = new ArrayList<>();
@ -909,15 +678,6 @@ public class MySQL implements DataSource {
return result; return result;
} }
/**
* Method getAllAuthsByEmail.
*
* @param email String
*
* @return List
*
* @see fr.xephi.authme.datasource.DataSource#getAllAuthsByEmail(String)
*/
@Override @Override
public synchronized List<String> getAllAuthsByEmail(String email){ public synchronized List<String> getAllAuthsByEmail(String email){
List<String> countEmail = new ArrayList<>(); List<String> countEmail = new ArrayList<>();
@ -938,13 +698,6 @@ public class MySQL implements DataSource {
return countEmail; return countEmail;
} }
/**
* Method purgeBanned.
*
* @param banned List<String>
*
* @see fr.xephi.authme.datasource.DataSource#purgeBanned(List)
*/
@Override @Override
public synchronized void purgeBanned(List<String> banned) { public synchronized void purgeBanned(List<String> banned) {
try (Connection con = getConnection()) { try (Connection con = getConnection()) {
@ -960,23 +713,11 @@ public class MySQL implements DataSource {
} }
} }
/**
* Method getType.
*
* @return DataSourceType * @see fr.xephi.authme.datasource.DataSource#getType()
*/
@Override @Override
public DataSourceType getType() { public DataSourceType getType() {
return DataSourceType.MYSQL; return DataSourceType.MYSQL;
} }
/**
* Method isLogged.
*
* @param user String
*
* @return boolean * @see fr.xephi.authme.datasource.DataSource#isLogged(String)
*/
@Override @Override
public boolean isLogged(String user) { public boolean isLogged(String user) {
boolean isLogged = false; boolean isLogged = false;
@ -993,13 +734,6 @@ public class MySQL implements DataSource {
return isLogged; return isLogged;
} }
/**
* Method setLogged.
*
* @param user String
*
* @see fr.xephi.authme.datasource.DataSource#setLogged(String)
*/
@Override @Override
public void setLogged(String user) { public void setLogged(String user) {
try (Connection con = getConnection()) { try (Connection con = getConnection()) {
@ -1015,13 +749,6 @@ public class MySQL implements DataSource {
} }
} }
/**
* Method setUnlogged.
*
* @param user String
*
* @see fr.xephi.authme.datasource.DataSource#setUnlogged(String)
*/
@Override @Override
public void setUnlogged(String user) { public void setUnlogged(String user) {
try (Connection con = getConnection()) { try (Connection con = getConnection()) {
@ -1037,11 +764,6 @@ public class MySQL implements DataSource {
} }
} }
/**
* Method purgeLogged.
*
* @see fr.xephi.authme.datasource.DataSource#purgeLogged()
*/
@Override @Override
public void purgeLogged() { public void purgeLogged() {
try (Connection con = getConnection()) { try (Connection con = getConnection()) {
@ -1057,13 +779,6 @@ public class MySQL implements DataSource {
} }
} }
/**
* Method getAccountsRegistered.
*
* @return int
*
* @see fr.xephi.authme.datasource.DataSource#getAccountsRegistered()
*/
@Override @Override
public int getAccountsRegistered() { public int getAccountsRegistered() {
int result = 0; int result = 0;
@ -1082,14 +797,6 @@ public class MySQL implements DataSource {
return result; return result;
} }
/**
* Method updateName.
*
* @param oldOne String
* @param newOne String
*
* @see fr.xephi.authme.datasource.DataSource#updateName(String, String)
*/
@Override @Override
public void updateName(String oldOne, String newOne) { public void updateName(String oldOne, String newOne) {
try (Connection con = getConnection()) { try (Connection con = getConnection()) {
@ -1104,13 +811,6 @@ public class MySQL implements DataSource {
} }
} }
/**
* Method getAllAuths.
*
* @return List
*
* @see fr.xephi.authme.datasource.DataSource#getAllAuths()
*/
@Override @Override
public List<PlayerAuth> getAllAuths() { public List<PlayerAuth> getAllAuths() {
List<PlayerAuth> auths = new ArrayList<>(); List<PlayerAuth> auths = new ArrayList<>();
@ -1119,12 +819,12 @@ public class MySQL implements DataSource {
ResultSet rs = st.executeQuery("SELECT * FROM " + tableName); ResultSet rs = st.executeQuery("SELECT * FROM " + tableName);
PreparedStatement pst = con.prepareStatement("SELECT data FROM xf_user_authenticate WHERE " + columnID + "=?;"); PreparedStatement pst = con.prepareStatement("SELECT data FROM xf_user_authenticate WHERE " + columnID + "=?;");
while (rs.next()) { while (rs.next()) {
String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : ""; String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : null;
int group = !salt.isEmpty() && !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; int group = !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1;
PlayerAuth pAuth = PlayerAuth.builder() PlayerAuth pAuth = PlayerAuth.builder()
.name(rs.getString(columnName)) .name(rs.getString(columnName))
.realName(rs.getString(columnRealName)) .realName(rs.getString(columnRealName))
.hash(rs.getString(columnPassword)) .password(rs.getString(columnPassword), salt)
.lastLogin(rs.getLong(columnLastLogin)) .lastLogin(rs.getLong(columnLastLogin))
.ip(rs.getString(columnIp)) .ip(rs.getString(columnIp))
.locWorld(rs.getString(lastlocWorld)) .locWorld(rs.getString(lastlocWorld))
@ -1132,21 +832,9 @@ public class MySQL implements DataSource {
.locY(rs.getDouble(lastlocY)) .locY(rs.getDouble(lastlocY))
.locZ(rs.getDouble(lastlocZ)) .locZ(rs.getDouble(lastlocZ))
.email(rs.getString(columnEmail)) .email(rs.getString(columnEmail))
.salt(salt)
.groupId(group) .groupId(group)
.build(); .build();
if (Settings.getPasswordHash == HashAlgorithm.XENFORO) {
int id = rs.getInt(columnID);
pst.setInt(1, id);
ResultSet rs2 = pst.executeQuery();
if (rs2.next()) {
Blob blob = rs2.getBlob("data");
byte[] bytes = blob.getBytes(1, (int) blob.length());
pAuth.setHash(new String(bytes));
}
rs2.close();
}
auths.add(pAuth); auths.add(pAuth);
} }
pst.close(); pst.close();
@ -1159,13 +847,6 @@ public class MySQL implements DataSource {
return auths; return auths;
} }
/**
* Method getLoggedPlayers.
*
* @return List
*
* @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers()
*/
@Override @Override
public List<PlayerAuth> getLoggedPlayers() { public List<PlayerAuth> getLoggedPlayers() {
List<PlayerAuth> auths = new ArrayList<>(); List<PlayerAuth> auths = new ArrayList<>();
@ -1174,12 +855,12 @@ public class MySQL implements DataSource {
ResultSet rs = st.executeQuery("SELECT * FROM " + tableName + " WHERE " + columnLogged + "=1;"); ResultSet rs = st.executeQuery("SELECT * FROM " + tableName + " WHERE " + columnLogged + "=1;");
PreparedStatement pst = con.prepareStatement("SELECT data FROM xf_user_authenticate WHERE " + columnID + "=?;"); PreparedStatement pst = con.prepareStatement("SELECT data FROM xf_user_authenticate WHERE " + columnID + "=?;");
while (rs.next()) { while (rs.next()) {
String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : ""; String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : null;
int group = !salt.isEmpty() && !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; int group = !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1;
PlayerAuth pAuth = PlayerAuth.builder() PlayerAuth pAuth = PlayerAuth.builder()
.name(rs.getString(columnName)) .name(rs.getString(columnName))
.realName(rs.getString(columnRealName)) .realName(rs.getString(columnRealName))
.hash(rs.getString(columnPassword)) .password(rs.getString(columnPassword), salt)
.lastLogin(rs.getLong(columnLastLogin)) .lastLogin(rs.getLong(columnLastLogin))
.ip(rs.getString(columnIp)) .ip(rs.getString(columnIp))
.locWorld(rs.getString(lastlocWorld)) .locWorld(rs.getString(lastlocWorld))
@ -1187,21 +868,9 @@ public class MySQL implements DataSource {
.locY(rs.getDouble(lastlocY)) .locY(rs.getDouble(lastlocY))
.locZ(rs.getDouble(lastlocZ)) .locZ(rs.getDouble(lastlocZ))
.email(rs.getString(columnEmail)) .email(rs.getString(columnEmail))
.salt(salt)
.groupId(group) .groupId(group)
.build(); .build();
if (Settings.getPasswordHash == HashAlgorithm.XENFORO) {
int id = rs.getInt(columnID);
pst.setInt(1, id);
ResultSet rs2 = pst.executeQuery();
if (rs2.next()) {
Blob blob = rs2.getBlob("data");
byte[] bytes = blob.getBytes(1, (int) blob.length());
pAuth.setHash(new String(bytes));
}
rs2.close();
}
auths.add(pAuth); auths.add(pAuth);
} }
} catch (Exception ex) { } catch (Exception ex) {

View File

@ -2,7 +2,9 @@ package fr.xephi.authme.datasource;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.security.crypts.EncryptedPassword;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.util.StringUtils;
import java.sql.*; import java.sql.*;
import java.util.ArrayList; import java.util.ArrayList;
@ -90,6 +92,13 @@ public class SQLite implements DataSource {
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + columnPassword + " VARCHAR(255) NOT NULL;"); st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + columnPassword + " VARCHAR(255) NOT NULL;");
} }
rs.close(); rs.close();
if (!columnSalt.isEmpty()) {
rs = con.getMetaData().getColumns(null, null, tableName, columnSalt);
if (!rs.next()) {
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + columnSalt + " VARCHAR(255);");
}
rs.close();
}
rs = con.getMetaData().getColumns(null, null, tableName, columnIp); rs = con.getMetaData().getColumns(null, null, tableName, columnIp);
if (!rs.next()) { if (!rs.next()) {
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + columnIp + " VARCHAR(40) NOT NULL;"); st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + columnIp + " VARCHAR(40) NOT NULL;");
@ -174,15 +183,7 @@ public class SQLite implements DataSource {
pst.setString(1, user); pst.setString(1, user);
rs = pst.executeQuery(); rs = pst.executeQuery();
if (rs.next()) { if (rs.next()) {
if (rs.getString(columnIp).isEmpty()) { return buildAuthFromResultSet(rs);
return new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), "192.168.0.1", rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName));
} else {
if (!columnSalt.isEmpty()) {
return new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnSalt), rs.getInt(columnGroup), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName));
} else {
return new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName));
}
}
} else { } else {
return null; return null;
} }
@ -206,21 +207,29 @@ public class SQLite implements DataSource {
public synchronized boolean saveAuth(PlayerAuth auth) { public synchronized boolean saveAuth(PlayerAuth auth) {
PreparedStatement pst = null; PreparedStatement pst = null;
try { try {
if (columnSalt.isEmpty() && auth.getSalt().isEmpty()) { EncryptedPassword password = auth.getPassword();
pst = con.prepareStatement("INSERT INTO " + tableName + "(" + columnName + "," + columnPassword + "," + columnIp + "," + columnLastLogin + "," + columnRealName + ") VALUES (?,?,?,?,?);"); if (columnSalt.isEmpty()) {
if (!StringUtils.isEmpty(auth.getPassword().getSalt())) {
ConsoleLogger.showError("Warning! Detected hashed password with separate salt but the salt column "
+ "is not set in the config!");
}
pst = con.prepareStatement("INSERT INTO " + tableName + "(" + columnName + "," + columnPassword +
"," + columnIp + "," + columnLastLogin + "," + columnRealName + ") VALUES (?,?,?,?,?);");
pst.setString(1, auth.getNickname()); pst.setString(1, auth.getNickname());
pst.setString(2, auth.getHash()); pst.setString(2, password.getHash());
pst.setString(3, auth.getIp()); pst.setString(3, auth.getIp());
pst.setLong(4, auth.getLastLogin()); pst.setLong(4, auth.getLastLogin());
pst.setString(5, auth.getRealName()); pst.setString(5, auth.getRealName());
pst.executeUpdate(); pst.executeUpdate();
} else { } else {
pst = con.prepareStatement("INSERT INTO " + tableName + "(" + columnName + "," + columnPassword + "," + columnIp + "," + columnLastLogin + "," + columnSalt + "," + columnRealName + ") VALUES (?,?,?,?,?,?);"); pst = con.prepareStatement("INSERT INTO " + tableName + "(" + columnName + "," + columnPassword + ","
+ columnIp + "," + columnLastLogin + "," + columnSalt + "," + columnRealName
+ ") VALUES (?,?,?,?,?,?);");
pst.setString(1, auth.getNickname()); pst.setString(1, auth.getNickname());
pst.setString(2, auth.getHash()); pst.setString(2, password.getHash());
pst.setString(3, auth.getIp()); pst.setString(3, auth.getIp());
pst.setLong(4, auth.getLastLogin()); pst.setLong(4, auth.getLastLogin());
pst.setString(5, auth.getSalt()); pst.setString(5, password.getSalt());
pst.setString(6, auth.getRealName()); pst.setString(6, auth.getRealName());
pst.executeUpdate(); pst.executeUpdate();
} }
@ -244,9 +253,19 @@ public class SQLite implements DataSource {
public synchronized boolean updatePassword(PlayerAuth auth) { public synchronized boolean updatePassword(PlayerAuth auth) {
PreparedStatement pst = null; PreparedStatement pst = null;
try { try {
pst = con.prepareStatement("UPDATE " + tableName + " SET " + columnPassword + "=? WHERE " + columnName + "=?;"); EncryptedPassword password = auth.getPassword();
pst.setString(1, auth.getHash()); boolean useSalt = !columnSalt.isEmpty();
pst.setString(2, auth.getNickname()); String sql = "UPDATE " + tableName + " SET " + columnPassword + " = ?"
+ (useSalt ? ", " + columnSalt + " = ?" : "")
+ " WHERE " + columnName + " = ?";
pst = con.prepareStatement(sql);
pst.setString(1, password.getHash());
if (useSalt) {
pst.setString(2, password.getSalt());
pst.setString(3, auth.getNickname());
} else {
pst.setString(2, auth.getNickname());
}
pst.executeUpdate(); pst.executeUpdate();
} catch (SQLException ex) { } catch (SQLException ex) {
ConsoleLogger.showError(ex.getMessage()); ConsoleLogger.showError(ex.getMessage());
@ -398,6 +417,7 @@ public class SQLite implements DataSource {
ResultSet rs = null; ResultSet rs = null;
int countIp = 0; int countIp = 0;
try { try {
// TODO ljacqu 20151230: Simply fetch COUNT(1) and return that
pst = con.prepareStatement("SELECT * FROM " + tableName + " WHERE " + columnIp + "=?;"); pst = con.prepareStatement("SELECT * FROM " + tableName + " WHERE " + columnIp + "=?;");
pst.setString(1, ip); pst.setString(1, ip);
rs = pst.executeQuery(); rs = pst.executeQuery();
@ -438,33 +458,6 @@ public class SQLite implements DataSource {
return true; return true;
} }
/**
* Method updateSalt.
*
* @param auth PlayerAuth
*
* @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth)
*/
@Override
public boolean updateSalt(PlayerAuth auth) {
if (columnSalt.isEmpty()) {
return false;
}
PreparedStatement pst = null;
try {
pst = con.prepareStatement("UPDATE " + tableName + " SET " + columnSalt + "=? WHERE " + columnName + "=?;");
pst.setString(1, auth.getSalt());
pst.setString(2, auth.getNickname());
pst.executeUpdate();
} catch (SQLException ex) {
ConsoleLogger.showError(ex.getMessage());
return false;
} finally {
close(pst);
}
return true;
}
/** /**
* Method close. * Method close.
* *
@ -611,13 +604,6 @@ public class SQLite implements DataSource {
} }
} }
/**
* Method purgeBanned.
*
* @param banned List<String>
*
* @see fr.xephi.authme.datasource.DataSource#purgeBanned(List<String>)
*/
@Override @Override
public void purgeBanned(List<String> banned) { public void purgeBanned(List<String> banned) {
PreparedStatement pst = null; PreparedStatement pst = null;
@ -761,14 +747,6 @@ public class SQLite implements DataSource {
return result; return result;
} }
/**
* Method updateName.
*
* @param oldOne String
* @param newOne String
*
* @see fr.xephi.authme.datasource.DataSource#updateName(String, String)
*/
@Override @Override
public void updateName(String oldOne, String newOne) { public void updateName(String oldOne, String newOne) {
PreparedStatement pst = null; PreparedStatement pst = null;
@ -787,7 +765,7 @@ public class SQLite implements DataSource {
/** /**
* Method getAllAuths. * Method getAllAuths.
* *
* @return List<PlayerAuth> * @see fr.xephi.authme.datasource.DataSource#getAllAuths() * @return List<PlayerAuth>
*/ */
@Override @Override
public List<PlayerAuth> getAllAuths() { public List<PlayerAuth> getAllAuths() {
@ -798,17 +776,8 @@ public class SQLite implements DataSource {
pst = con.prepareStatement("SELECT * FROM " + tableName + ";"); pst = con.prepareStatement("SELECT * FROM " + tableName + ";");
rs = pst.executeQuery(); rs = pst.executeQuery();
while (rs.next()) { while (rs.next()) {
PlayerAuth pAuth; PlayerAuth auth = buildAuthFromResultSet(rs);
if (rs.getString(columnIp).isEmpty()) { auths.add(auth);
pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), "127.0.0.1", rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName));
} else {
if (!columnSalt.isEmpty()) {
pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnSalt), rs.getInt(columnGroup), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName));
} else {
pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName));
}
}
auths.add(pAuth);
} }
} catch (SQLException ex) { } catch (SQLException ex) {
ConsoleLogger.showError(ex.getMessage()); ConsoleLogger.showError(ex.getMessage());
@ -822,7 +791,7 @@ public class SQLite implements DataSource {
/** /**
* Method getLoggedPlayers. * Method getLoggedPlayers.
* *
* @return List<PlayerAuth> * @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers() * @return List<PlayerAuth>
*/ */
@Override @Override
public List<PlayerAuth> getLoggedPlayers() { public List<PlayerAuth> getLoggedPlayers() {
@ -833,17 +802,8 @@ public class SQLite implements DataSource {
pst = con.prepareStatement("SELECT * FROM " + tableName + " WHERE " + columnLogged + "=1;"); pst = con.prepareStatement("SELECT * FROM " + tableName + " WHERE " + columnLogged + "=1;");
rs = pst.executeQuery(); rs = pst.executeQuery();
while (rs.next()) { while (rs.next()) {
PlayerAuth pAuth; PlayerAuth auth = buildAuthFromResultSet(rs);
if (rs.getString(columnIp).isEmpty()) { auths.add(auth);
pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), "127.0.0.1", rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName));
} else {
if (!columnSalt.isEmpty()) {
pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnSalt), rs.getInt(columnGroup), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName));
} else {
pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName));
}
}
auths.add(pAuth);
} }
} catch (SQLException ex) { } catch (SQLException ex) {
ConsoleLogger.showError(ex.getMessage()); ConsoleLogger.showError(ex.getMessage());
@ -853,4 +813,25 @@ public class SQLite implements DataSource {
} }
return auths; return auths;
} }
private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException {
String salt = !columnSalt.isEmpty() ? row.getString(columnSalt) : null;
PlayerAuth.Builder authBuilder = PlayerAuth.builder()
.name(row.getString(columnName))
.email(row.getString(columnEmail))
.realName(row.getString(columnRealName))
.password(row.getString(columnPassword), salt)
.lastLogin(row.getLong(columnLastLogin))
.locX(row.getDouble(lastlocX))
.locY(row.getDouble(lastlocY))
.locZ(row.getDouble(lastlocZ))
.locWorld(row.getString(lastlocWorld));
String ip = row.getString(columnIp);
if (!ip.isEmpty()) {
authBuilder.ip(ip);
}
return authBuilder.build();
}
} }

View File

@ -5,75 +5,41 @@ import org.bukkit.event.Event;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
/** /**
* <p> * This event is called when we need to compare or hash password and allows
* This event is called when we need to compare or get an hash password, for set * third-party listeners to change the encryption method. This is typically
* a custom EncryptionMethod * done with the {@link fr.xephi.authme.security.HashAlgorithm#CUSTOM} setting.
* </p>
* *
* @author Xephi59 * @author Xephi59
* @version $Revision: 1.0 $
* @see fr.xephi.authme.security.crypts.EncryptionMethod
*/ */
public class PasswordEncryptionEvent extends Event { public class PasswordEncryptionEvent extends Event {
private static final HandlerList handlers = new HandlerList(); private static final HandlerList handlers = new HandlerList();
private EncryptionMethod method = null; private EncryptionMethod method;
private String playerName = ""; private String playerName;
/**
* Constructor for PasswordEncryptionEvent.
*
* @param method EncryptionMethod
* @param playerName String
*/
public PasswordEncryptionEvent(EncryptionMethod method, String playerName) { public PasswordEncryptionEvent(EncryptionMethod method, String playerName) {
super(false); super(false);
this.method = method; this.method = method;
this.playerName = playerName; this.playerName = playerName;
} }
/**
* Method getHandlerList.
*
* @return HandlerList
*/
public static HandlerList getHandlerList() { public static HandlerList getHandlerList() {
return handlers; return handlers;
} }
/**
* Method getHandlers.
*
* @return HandlerList
*/
@Override @Override
public HandlerList getHandlers() { public HandlerList getHandlers() {
return handlers; return handlers;
} }
/**
* Method getMethod.
*
* @return EncryptionMethod
*/
public EncryptionMethod getMethod() { public EncryptionMethod getMethod() {
return method; return method;
} }
/**
* Method setMethod.
*
* @param method EncryptionMethod
*/
public void setMethod(EncryptionMethod method) { public void setMethod(EncryptionMethod method) {
this.method = method; this.method = method;
} }
/**
* Method getPlayerName.
*
* @return String
*/
public String getPlayerName() { public String getPlayerName() {
return playerName; return playerName;
} }

View File

@ -6,6 +6,8 @@ import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.security.crypts.EncryptedPassword;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener; import org.bukkit.plugin.messaging.PluginMessageListener;
@ -24,15 +26,6 @@ public class BungeeCordMessage implements PluginMessageListener {
this.plugin = plugin; this.plugin = plugin;
} }
/**
* Method onPluginMessageReceived.
*
* @param channel String
* @param player Player
* @param message byte[]
*
* @see org.bukkit.plugin.messaging.PluginMessageListener#onPluginMessageReceived(String, Player, byte[])
*/
@Override @Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) { public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!channel.equals("BungeeCord")) { if (!channel.equals("BungeeCord")) {
@ -50,21 +43,22 @@ public class BungeeCordMessage implements PluginMessageListener {
final String[] args = str.split(";"); final String[] args = str.split(";");
final String act = args[0]; final String act = args[0];
final String name = args[1]; final String name = args[1];
final DataSource dataSource = plugin.getDataSource();
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() {
@Override @Override
public void run() { public void run() {
PlayerAuth auth = plugin.database.getAuth(name); PlayerAuth auth = dataSource.getAuth(name);
if (auth == null) { if (auth == null) {
return; return;
} }
if ("login".equals(act)) { if ("login".equals(act)) {
PlayerCache.getInstance().updatePlayer(auth); PlayerCache.getInstance().updatePlayer(auth);
plugin.database.setLogged(name); dataSource.setLogged(name);
ConsoleLogger.info("Player " + auth.getNickname() ConsoleLogger.info("Player " + auth.getNickname()
+ " has logged in from one of your server!"); + " has logged in from one of your server!");
} else if ("logout".equals(act)) { } else if ("logout".equals(act)) {
PlayerCache.getInstance().removePlayer(name); PlayerCache.getInstance().removePlayer(name);
plugin.database.setUnlogged(name); dataSource.setUnlogged(name);
ConsoleLogger.info("Player " + auth.getNickname() ConsoleLogger.info("Player " + auth.getNickname()
+ " has logged out from one of your server!"); + " has logged out from one of your server!");
} else if ("register".equals(act)) { } else if ("register".equals(act)) {
@ -72,11 +66,10 @@ public class BungeeCordMessage implements PluginMessageListener {
+ " has registered out from one of your server!"); + " has registered out from one of your server!");
} else if ("changepassword".equals(act)) { } else if ("changepassword".equals(act)) {
final String password = args[2]; final String password = args[2];
auth.setHash(password); final String salt = args.length >= 4 ? args[3] : null;
if (args.length == 4) auth.setPassword(new EncryptedPassword(password, salt));
auth.setSalt(args[3]);
PlayerCache.getInstance().updatePlayer(auth); PlayerCache.getInstance().updatePlayer(auth);
plugin.database.updatePassword(auth); dataSource.updatePassword(auth);
} }
} }

View File

@ -89,7 +89,7 @@ public class AuthMePlayerListener implements Listener {
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() {
@Override @Override
public void run() { public void run() {
if (plugin.database.isAuthAvailable(player.getName().toLowerCase())) { if (plugin.getDataSource().isAuthAvailable(player.getName().toLowerCase())) {
m.send(player, MessageKey.LOGIN_MESSAGE); m.send(player, MessageKey.LOGIN_MESSAGE);
} else { } else {
if (Settings.emailRegistration) { if (Settings.emailRegistration) {
@ -221,8 +221,9 @@ public class AuthMePlayerListener implements Listener {
@EventHandler(priority = EventPriority.HIGHEST) @EventHandler(priority = EventPriority.HIGHEST)
public void onPreLogin(AsyncPlayerPreLoginEvent event) { public void onPreLogin(AsyncPlayerPreLoginEvent event) {
PlayerAuth auth = plugin.database.getAuth(event.getName()); PlayerAuth auth = plugin.getDataSource().getAuth(event.getName());
if (auth != null && auth.getRealName() != null && !auth.getRealName().isEmpty() && !auth.getRealName().equals("Player") && !auth.getRealName().equals(event.getName())) { if (auth != null && auth.getRealName() != null && !auth.getRealName().isEmpty() &&
!auth.getRealName().equals("Player") && !auth.getRealName().equals(event.getName())) {
event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER);
event.setKickMessage("You should join using username: " + ChatColor.AQUA + auth.getRealName() + event.setKickMessage("You should join using username: " + ChatColor.AQUA + auth.getRealName() +
ChatColor.RESET + "\nnot: " + ChatColor.RED + event.getName()); // TODO: write a better message ChatColor.RESET + "\nnot: " + ChatColor.RED + event.getName()); // TODO: write a better message
@ -231,7 +232,7 @@ public class AuthMePlayerListener implements Listener {
if (auth != null && auth.getRealName().equals("Player")) { if (auth != null && auth.getRealName().equals("Player")) {
auth.setRealName(event.getName()); auth.setRealName(event.getName());
plugin.database.saveAuth(auth); plugin.getDataSource().saveAuth(auth);
} }
if (auth == null && Settings.enableProtection) { if (auth == null && Settings.enableProtection) {
@ -302,7 +303,7 @@ public class AuthMePlayerListener implements Listener {
} }
final String name = player.getName().toLowerCase(); final String name = player.getName().toLowerCase();
boolean isAuthAvailable = plugin.database.isAuthAvailable(name); boolean isAuthAvailable = plugin.getDataSource().isAuthAvailable(name);
if (Settings.isKickNonRegisteredEnabled && !isAuthAvailable) { if (Settings.isKickNonRegisteredEnabled && !isAuthAvailable) {
if (Settings.antiBotInAction) { if (Settings.antiBotInAction) {
@ -475,9 +476,13 @@ public class AuthMePlayerListener implements Listener {
Player player = event.getPlayer(); Player player = event.getPlayer();
String name = player.getName().toLowerCase(); String name = player.getName().toLowerCase();
Location spawn = plugin.getSpawnLocation(player); Location spawn = plugin.getSpawnLocation(player);
if (Settings.isSaveQuitLocationEnabled && plugin.database.isAuthAvailable(name)) { if (Settings.isSaveQuitLocationEnabled && plugin.getDataSource().isAuthAvailable(name)) {
PlayerAuth auth = new PlayerAuth(name, spawn.getX(), spawn.getY(), spawn.getZ(), spawn.getWorld().getName(), player.getName()); PlayerAuth auth = PlayerAuth.builder()
plugin.database.updateQuitLoc(auth); .name(name)
.realName(player.getName())
.location(spawn)
.build();
plugin.getDataSource().updateQuitLoc(auth);
} }
if (spawn != null && spawn.getWorld() != null) { if (spawn != null && spawn.getWorld() != null) {
event.setRespawnLocation(spawn); event.setRespawnLocation(spawn);

View File

@ -33,7 +33,7 @@ public class Management {
@Override @Override
public void run() { public void run() {
new AsynchronousLogin(player, password, forceLogin, plugin, plugin.database).process(); new AsynchronousLogin(player, password, forceLogin, plugin, plugin.getDataSource()).process();
} }
}); });
} }
@ -43,7 +43,7 @@ public class Management {
@Override @Override
public void run() { public void run() {
new AsynchronousLogout(player, plugin, plugin.database).process(); new AsynchronousLogout(player, plugin, plugin.getDataSource()).process();
} }
}); });
} }
@ -53,7 +53,7 @@ public class Management {
@Override @Override
public void run() { public void run() {
new AsyncRegister(player, password, email, plugin, plugin.database).process(); new AsyncRegister(player, password, email, plugin, plugin.getDataSource()).process();
} }
}); });
} }
@ -73,7 +73,7 @@ public class Management {
@Override @Override
public void run() { public void run() {
new AsynchronousJoin(player, plugin, plugin.database).process(); new AsynchronousJoin(player, plugin, plugin.getDataSource()).process();
} }
}); });
@ -84,7 +84,7 @@ public class Management {
@Override @Override
public void run() { public void run() {
new AsynchronousQuit(player, plugin, plugin.database, isKick).process(); new AsynchronousQuit(player, plugin, plugin.getDataSource(), isKick).process();
} }
}); });

View File

@ -40,7 +40,7 @@ public class AsyncChangeEmail {
if (Settings.getmaxRegPerEmail > 0) { if (Settings.getmaxRegPerEmail > 0) {
if (!plugin.getPermissionsManager().hasPermission(player, PlayerPermission.ALLOW_MULTIPLE_ACCOUNTS) if (!plugin.getPermissionsManager().hasPermission(player, PlayerPermission.ALLOW_MULTIPLE_ACCOUNTS)
&& plugin.database.getAllAuthsByEmail(newEmail).size() >= Settings.getmaxRegPerEmail) { && plugin.getDataSource().getAllAuthsByEmail(newEmail).size() >= Settings.getmaxRegPerEmail) {
m.send(player, MessageKey.MAX_REGISTER_EXCEEDED); m.send(player, MessageKey.MAX_REGISTER_EXCEEDED);
return; return;
} }
@ -68,7 +68,7 @@ public class AsyncChangeEmail {
} }
String old = auth.getEmail(); String old = auth.getEmail();
auth.setEmail(newEmail); auth.setEmail(newEmail);
if (!plugin.database.updateEmail(auth)) { if (!plugin.getDataSource().updateEmail(auth)) {
m.send(player, MessageKey.ERROR); m.send(player, MessageKey.ERROR);
auth.setEmail(old); auth.setEmail(old);
return; return;
@ -81,7 +81,7 @@ public class AsyncChangeEmail {
} }
m.send(player, MessageKey.EMAIL_CHANGED_SUCCESS); m.send(player, MessageKey.EMAIL_CHANGED_SUCCESS);
} else { } else {
if (plugin.database.isAuthAvailable(playerName)) { if (plugin.getDataSource().isAuthAvailable(playerName)) {
m.send(player, MessageKey.LOGIN_MESSAGE); m.send(player, MessageKey.LOGIN_MESSAGE);
} else { } else {
if (Settings.emailRegistration) { if (Settings.emailRegistration) {

View File

@ -8,7 +8,6 @@ import fr.xephi.authme.cache.limbo.LimboCache;
import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent; import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent;
import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.permission.PlayerPermission;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.RandomString; import fr.xephi.authme.security.RandomString;
import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.output.Messages; import fr.xephi.authme.output.Messages;
@ -26,12 +25,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 +68,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;
} }
@ -120,8 +118,7 @@ public class AsynchronousLogin {
return null; return null;
} }
if (Settings.preventOtherCase && !player.getName().equals(pAuth.getRealName())) if (Settings.preventOtherCase && !player.getName().equals(pAuth.getRealName())) {
{
// TODO: Add a message like : MessageKey.INVALID_NAME_CASE // TODO: Add a message like : MessageKey.INVALID_NAME_CASE
m.send(player, MessageKey.USERNAME_ALREADY_ONLINE_ERROR); m.send(player, MessageKey.USERNAME_ALREADY_ONLINE_ERROR);
return null; return null;
@ -138,19 +135,19 @@ public class AsynchronousLogin {
if (pAuth == null || needsCaptcha()) if (pAuth == null || needsCaptcha())
return; return;
String hash = pAuth.getHash();
String email = pAuth.getEmail(); String email = pAuth.getEmail();
boolean passwordVerified = true; boolean passwordVerified = forceLogin || plugin.getPasswordSecurity()
if (!forceLogin) .comparePassword(password, pAuth.getPassword(), realName);
try {
passwordVerified = PasswordSecurity.comparePasswordWithHash(password, hash, realName);
} catch (Exception ex) {
ConsoleLogger.showError(ex.getMessage());
m.send(player, MessageKey.ERROR);
return;
}
if (passwordVerified && player.isOnline()) { if (passwordVerified && player.isOnline()) {
PlayerAuth auth = new PlayerAuth(name, hash, getIP(), new Date().getTime(), email, realName); PlayerAuth auth = PlayerAuth.builder()
.name(name)
.realName(realName)
.ip(getIP())
.lastLogin(new Date().getTime())
.email(email)
.password(pAuth.getPassword())
.build();
database.updateSession(auth); database.updateSession(auth);
if (Settings.useCaptcha) { if (Settings.useCaptcha) {

View File

@ -8,7 +8,7 @@ import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.output.Messages; import fr.xephi.authme.output.Messages;
import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.permission.PlayerPermission;
import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.EncryptedPassword;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -44,7 +44,10 @@ public class AsyncRegister {
} else if (!Settings.isRegistrationEnabled) { } else if (!Settings.isRegistrationEnabled) {
m.send(player, MessageKey.REGISTRATION_DISABLED); m.send(player, MessageKey.REGISTRATION_DISABLED);
return false; return false;
} else if (passLow.contains("delete") || passLow.contains("where") || passLow.contains("insert") || passLow.contains("modify") || passLow.contains("from") || passLow.contains("select") || passLow.contains(";") || passLow.contains("null") || !passLow.matches(Settings.getPassRegex)) { } else if (passLow.contains("delete") || passLow.contains("where") || passLow.contains("insert")
|| passLow.contains("modify") || passLow.contains("from") || passLow.contains("select")
|| passLow.contains(";") || passLow.contains("null") || !passLow.matches(Settings.getPassRegex)) {
// TODO #308: Remove check for SQL keywords
m.send(player, MessageKey.PASSWORD_MATCH_ERROR); m.send(player, MessageKey.PASSWORD_MATCH_ERROR);
return false; return false;
} else if (passLow.equalsIgnoreCase(player.getName())) { } else if (passLow.equalsIgnoreCase(player.getName())) {
@ -87,26 +90,21 @@ public class AsyncRegister {
} }
} }
private void emailRegister() throws Exception { private void emailRegister() {
if (Settings.getmaxRegPerEmail > 0 if (Settings.getmaxRegPerEmail > 0
&& !plugin.getPermissionsManager().hasPermission(player, PlayerPermission.ALLOW_MULTIPLE_ACCOUNTS) && !plugin.getPermissionsManager().hasPermission(player, PlayerPermission.ALLOW_MULTIPLE_ACCOUNTS)
&& database.getAllAuthsByEmail(email).size() >= Settings.getmaxRegPerEmail) { && database.getAllAuthsByEmail(email).size() >= Settings.getmaxRegPerEmail) {
m.send(player, MessageKey.MAX_REGISTER_EXCEEDED); m.send(player, MessageKey.MAX_REGISTER_EXCEEDED);
return; return;
} }
final String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); final EncryptedPassword encryptedPassword = plugin.getPasswordSecurity().computeHash(password, name);
final String salt = PasswordSecurity.userSalt.get(name);
PlayerAuth auth = PlayerAuth.builder() PlayerAuth auth = PlayerAuth.builder()
.name(name) .name(name)
.realName(player.getName()) .realName(player.getName())
.hash(hashNew) .password(encryptedPassword)
.ip(ip) .ip(ip)
.locWorld(player.getLocation().getWorld().getName()) .location(player.getLocation())
.locX(player.getLocation().getX())
.locY(player.getLocation().getY())
.locZ(player.getLocation().getZ())
.email(email) .email(email)
.salt(salt != null ? salt : "")
.build(); .build();
if (!database.saveAuth(auth)) { if (!database.saveAuth(auth)) {
@ -122,18 +120,13 @@ public class AsyncRegister {
} }
private void passwordRegister() throws Exception { private void passwordRegister() throws Exception {
final String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); final EncryptedPassword encryptedPassword = plugin.getPasswordSecurity().computeHash(password, name);
final String salt = PasswordSecurity.userSalt.get(name);
PlayerAuth auth = PlayerAuth.builder() PlayerAuth auth = PlayerAuth.builder()
.name(name) .name(name)
.realName(player.getName()) .realName(player.getName())
.hash(hashNew) .password(encryptedPassword)
.ip(ip) .ip(ip)
.locWorld(player.getLocation().getWorld().getName()) .location(player.getLocation())
.locX(player.getLocation().getX())
.locY(player.getLocation().getY())
.locZ(player.getLocation().getZ())
.salt(salt != null ? salt : "")
.build(); .build();
if (!database.saveAuth(auth)) { if (!database.saveAuth(auth)) {

View File

@ -2,11 +2,11 @@ package fr.xephi.authme.process.unregister;
import fr.xephi.authme.AuthMe; import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.auth.PlayerCache;
import fr.xephi.authme.cache.backup.JsonCache; import fr.xephi.authme.cache.backup.JsonCache;
import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.cache.limbo.LimboCache;
import fr.xephi.authme.cache.limbo.LimboPlayer; import fr.xephi.authme.cache.limbo.LimboPlayer;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.output.Messages; import fr.xephi.authme.output.Messages;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
@ -20,16 +20,12 @@ import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask; import org.bukkit.scheduler.BukkitTask;
import java.security.NoSuchAlgorithmException;
/**
*/
public class AsynchronousUnregister { public class AsynchronousUnregister {
protected final Player player; private final Player player;
protected final String name; private final String name;
protected final String password; private final String password;
protected final boolean force; private final boolean force;
private final AuthMe plugin; private final AuthMe plugin;
private final Messages m; private final Messages m;
private final JsonCache playerCache; private final JsonCache playerCache;
@ -52,65 +48,59 @@ public class AsynchronousUnregister {
this.playerCache = new JsonCache(); this.playerCache = new JsonCache();
} }
/**
* Method getIp.
*
* @return String
*/
protected String getIp() { protected String getIp() {
return plugin.getIP(player); return plugin.getIP(player);
} }
public void process() { public void process() {
try { PlayerAuth cachedAuth = PlayerCache.getInstance().getAuth(name);
if (force || PasswordSecurity.comparePasswordWithHash(password, PlayerCache.getInstance().getAuth(name).getHash(), player.getName())) { if (force || plugin.getPasswordSecurity().comparePassword(
if (!plugin.database.removeAuth(name)) { password, cachedAuth.getPassword(), player.getName())) {
m.send(player, MessageKey.ERROR); if (!plugin.getDataSource().removeAuth(name)) {
return; m.send(player, MessageKey.ERROR);
return;
}
int timeOut = Settings.getRegistrationTimeout * 20;
if (Settings.isForcedRegistrationEnabled) {
Utils.teleportToSpawn(player);
player.saveData();
PlayerCache.getInstance().removePlayer(player.getName().toLowerCase());
if (!Settings.getRegisteredGroup.isEmpty()) {
Utils.setGroup(player, GroupType.UNREGISTERED);
} }
int timeOut = Settings.getRegistrationTimeout * 20; LimboCache.getInstance().addLimboPlayer(player);
if (Settings.isForcedRegistrationEnabled) { LimboPlayer limboPlayer = LimboCache.getInstance().getLimboPlayer(name);
Utils.teleportToSpawn(player); int interval = Settings.getWarnMessageInterval;
player.saveData(); BukkitScheduler scheduler = plugin.getServer().getScheduler();
PlayerCache.getInstance().removePlayer(player.getName().toLowerCase()); if (timeOut != 0) {
if (!Settings.getRegisteredGroup.isEmpty()) { BukkitTask id = scheduler.runTaskLaterAsynchronously(plugin,
Utils.setGroup(player, GroupType.UNREGISTERED); new TimeoutTask(plugin, name, player), timeOut);
} limboPlayer.setTimeoutTaskId(id);
LimboCache.getInstance().addLimboPlayer(player);
LimboPlayer limboPlayer = LimboCache.getInstance().getLimboPlayer(name);
int interval = Settings.getWarnMessageInterval;
BukkitScheduler scheduler = plugin.getServer().getScheduler();
if (timeOut != 0) {
BukkitTask id = scheduler.runTaskLaterAsynchronously(plugin,
new TimeoutTask(plugin, name, player), timeOut);
limboPlayer.setTimeoutTaskId(id);
}
limboPlayer.setMessageTaskId(scheduler.runTaskAsynchronously(plugin,
new MessageTask(plugin, name, m.retrieve(MessageKey.REGISTER_MESSAGE), interval)));
m.send(player, MessageKey.UNREGISTERED_SUCCESS);
ConsoleLogger.info(player.getDisplayName() + " unregistered himself");
return;
}
if (!Settings.unRegisteredGroup.isEmpty()) {
Utils.setGroup(player, Utils.GroupType.UNREGISTERED);
}
PlayerCache.getInstance().removePlayer(name);
// check if Player cache File Exist and delete it, preventing
// duplication of items
if (playerCache.doesCacheExist(player)) {
playerCache.removeCache(player);
}
// Apply blind effect
if (Settings.applyBlindEffect) {
player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeOut, 2));
} }
limboPlayer.setMessageTaskId(scheduler.runTaskAsynchronously(plugin,
new MessageTask(plugin, name, m.retrieve(MessageKey.REGISTER_MESSAGE), interval)));
m.send(player, MessageKey.UNREGISTERED_SUCCESS); m.send(player, MessageKey.UNREGISTERED_SUCCESS);
ConsoleLogger.info(player.getDisplayName() + " unregistered himself"); ConsoleLogger.info(player.getDisplayName() + " unregistered himself");
Utils.teleportToSpawn(player); return;
} else {
m.send(player, MessageKey.WRONG_PASSWORD);
} }
} catch (NoSuchAlgorithmException ignored) { if (!Settings.unRegisteredGroup.isEmpty()) {
Utils.setGroup(player, Utils.GroupType.UNREGISTERED);
}
PlayerCache.getInstance().removePlayer(name);
// check if Player cache File Exist and delete it, preventing
// duplication of items
if (playerCache.doesCacheExist(player)) {
playerCache.removeCache(player);
}
// Apply blind effect
if (Settings.applyBlindEffect) {
player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeOut, 2));
}
m.send(player, MessageKey.UNREGISTERED_SUCCESS);
ConsoleLogger.info(player.getDisplayName() + " unregistered himself");
Utils.teleportToSpawn(player);
} else {
m.send(player, MessageKey.WRONG_PASSWORD);
} }
} }
} }

View File

@ -3,40 +3,42 @@ package fr.xephi.authme.security;
import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.EncryptionMethod;
/** /**
* The list of hash algorithms supported by AuthMe. The implementing class must define a public
* constructor which takes either no arguments, or a DataSource object (when the salt is stored
* separately, writes to the database are necessary).
*/ */
public enum HashAlgorithm { public enum HashAlgorithm {
MD5(fr.xephi.authme.security.crypts.MD5.class),
SHA1(fr.xephi.authme.security.crypts.SHA1.class),
SHA256(fr.xephi.authme.security.crypts.SHA256.class),
WHIRLPOOL(fr.xephi.authme.security.crypts.WHIRLPOOL.class),
XAUTH(fr.xephi.authme.security.crypts.XAUTH.class),
MD5VB(fr.xephi.authme.security.crypts.MD5VB.class),
PHPBB(fr.xephi.authme.security.crypts.PHPBB.class),
@Deprecated
PLAINTEXT(fr.xephi.authme.security.crypts.PLAINTEXT.class),
MYBB(fr.xephi.authme.security.crypts.MYBB.class),
IPB3(fr.xephi.authme.security.crypts.IPB3.class),
PHPFUSION(fr.xephi.authme.security.crypts.PHPFUSION.class),
SMF(fr.xephi.authme.security.crypts.SMF.class),
XENFORO(fr.xephi.authme.security.crypts.XF.class),
SALTED2MD5(fr.xephi.authme.security.crypts.SALTED2MD5.class),
JOOMLA(fr.xephi.authme.security.crypts.JOOMLA.class),
BCRYPT(fr.xephi.authme.security.crypts.BCRYPT.class), BCRYPT(fr.xephi.authme.security.crypts.BCRYPT.class),
WBB3(fr.xephi.authme.security.crypts.WBB3.class), BCRYPT2Y(fr.xephi.authme.security.crypts.BCRYPT2Y.class),
WBB4(fr.xephi.authme.security.crypts.WBB4.class), CRAZYCRYPT1(fr.xephi.authme.security.crypts.CRAZYCRYPT1.class),
SHA512(fr.xephi.authme.security.crypts.SHA512.class),
DOUBLEMD5(fr.xephi.authme.security.crypts.DOUBLEMD5.class), DOUBLEMD5(fr.xephi.authme.security.crypts.DOUBLEMD5.class),
IPB3(fr.xephi.authme.security.crypts.IPB3.class),
JOOMLA(fr.xephi.authme.security.crypts.JOOMLA.class),
MD5(fr.xephi.authme.security.crypts.MD5.class),
MD5VB(fr.xephi.authme.security.crypts.MD5VB.class),
MYBB(fr.xephi.authme.security.crypts.MYBB.class),
PBKDF2(fr.xephi.authme.security.crypts.CryptPBKDF2.class), PBKDF2(fr.xephi.authme.security.crypts.CryptPBKDF2.class),
PBKDF2DJANGO(fr.xephi.authme.security.crypts.CryptPBKDF2Django.class), PBKDF2DJANGO(fr.xephi.authme.security.crypts.CryptPBKDF2Django.class),
WORDPRESS(fr.xephi.authme.security.crypts.WORDPRESS.class), PHPBB(fr.xephi.authme.security.crypts.PHPBB.class),
PHPFUSION(fr.xephi.authme.security.crypts.PHPFUSION.class),
@Deprecated
PLAINTEXT(fr.xephi.authme.security.crypts.PLAINTEXT.class),
ROYALAUTH(fr.xephi.authme.security.crypts.ROYALAUTH.class), ROYALAUTH(fr.xephi.authme.security.crypts.ROYALAUTH.class),
CRAZYCRYPT1(fr.xephi.authme.security.crypts.CRAZYCRYPT1.class), SALTED2MD5(fr.xephi.authme.security.crypts.SALTED2MD5.class),
BCRYPT2Y(fr.xephi.authme.security.crypts.BCRYPT2Y.class),
SALTEDSHA512(fr.xephi.authme.security.crypts.SALTEDSHA512.class), SALTEDSHA512(fr.xephi.authme.security.crypts.SALTEDSHA512.class),
SHA1(fr.xephi.authme.security.crypts.SHA1.class),
SHA256(fr.xephi.authme.security.crypts.SHA256.class),
SHA512(fr.xephi.authme.security.crypts.SHA512.class),
SMF(fr.xephi.authme.security.crypts.SMF.class),
WBB3(fr.xephi.authme.security.crypts.WBB3.class),
WBB4(fr.xephi.authme.security.crypts.WBB4.class),
WHIRLPOOL(fr.xephi.authme.security.crypts.WHIRLPOOL.class),
WORDPRESS(fr.xephi.authme.security.crypts.WORDPRESS.class),
XAUTH(fr.xephi.authme.security.crypts.XAUTH.class),
CUSTOM(null); CUSTOM(null);
final Class<? extends EncryptionMethod> clazz; private final Class<? extends EncryptionMethod> clazz;
/** /**
* Constructor for HashAlgorithm. * Constructor for HashAlgorithm.

View File

@ -0,0 +1,85 @@
package fr.xephi.authme.security;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Hashing utilities (interface for common hashing algorithms).
*/
public final class HashUtils {
private HashUtils() {
}
/**
* Generate the SHA-1 digest of the given message.
*
* @param message The message to hash
* @return The resulting SHA-1 digest
*/
public static String sha1(String message) {
return hash(message, MessageDigestAlgorithm.SHA1);
}
/**
* Generate the SHA-256 digest of the given message.
*
* @param message The message to hash
* @return The resulting SHA-256 digest
*/
public static String sha256(String message) {
return hash(message, MessageDigestAlgorithm.SHA256);
}
/**
* Generate the SHA-512 digest of the given message.
*
* @param message The message to hash
* @return The resulting SHA-512 digest
*/
public static String sha512(String message) {
return hash(message, MessageDigestAlgorithm.SHA512);
}
/**
* Generate the MD5 digest of the given message.
*
* @param message The message to hash
* @return The resulting MD5 digest
*/
public static String md5(String message) {
return hash(message, MessageDigestAlgorithm.MD5);
}
/**
* Return a {@link MessageDigest} instance for the given algorithm.
*
* @param algorithm The desired algorithm
* @return MessageDigest instance for the given algorithm
*/
public static MessageDigest getDigest(MessageDigestAlgorithm algorithm) {
try {
return MessageDigest.getInstance(algorithm.getKey());
} catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException("Your system seems not to support the hash algorithm '"
+ algorithm.getKey() + "'");
}
}
/**
* Hash the message with the given algorithm and return the hash in its hexadecimal notation.
*
* @param message The message to hash
* @param algorithm The algorithm to hash the message with
* @return The digest in its hexadecimal representation
*/
private static String hash(String message, MessageDigestAlgorithm algorithm) {
MessageDigest md = getDigest(algorithm);
md.reset();
md.update(message.getBytes());
byte[] digest = md.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
}
}

View File

@ -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 <a href="http://docs.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html#AppA">
* Crypto Spec Appendix A: Standard Names</a>
*/
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;
}
}

View File

@ -1,182 +1,126 @@
package fr.xephi.authme.security; package fr.xephi.authme.security;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.PasswordEncryptionEvent; import fr.xephi.authme.events.PasswordEncryptionEvent;
import fr.xephi.authme.security.crypts.BCRYPT; import fr.xephi.authme.security.crypts.EncryptedPassword;
import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.EncryptionMethod;
import fr.xephi.authme.settings.Settings; import org.bukkit.plugin.PluginManager;
import org.bukkit.Bukkit;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashMap;
/** /**
* Manager class for password-related operations.
*/ */
public class PasswordSecurity { public class PasswordSecurity {
public static final HashMap<String, String> userSalt = new HashMap<>(); private final DataSource dataSource;
private static final SecureRandom rnd = new SecureRandom(); private final HashAlgorithm algorithm;
private final PluginManager pluginManager;
private final boolean supportOldAlgorithm;
public static String createSalt(int length) public PasswordSecurity(DataSource dataSource, HashAlgorithm algorithm,
throws NoSuchAlgorithmException { PluginManager pluginManager, boolean supportOldAlgorithm) {
byte[] msg = new byte[40]; this.dataSource = dataSource;
rnd.nextBytes(msg); this.algorithm = algorithm;
MessageDigest sha1 = MessageDigest.getInstance("SHA1"); this.pluginManager = pluginManager;
sha1.reset(); this.supportOldAlgorithm = supportOldAlgorithm;
byte[] digest = sha1.digest(msg);
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)).substring(0, length);
} }
public static String getHash(HashAlgorithm alg, String password, public EncryptedPassword computeHash(String password, String playerName) {
String playerName) throws NoSuchAlgorithmException { return computeHash(algorithm, password, playerName);
EncryptionMethod method;
try {
if (alg != HashAlgorithm.CUSTOM)
method = alg.getClazz().newInstance();
else method = null;
} catch (InstantiationException | IllegalAccessException e) {
throw new NoSuchAlgorithmException("Problem with hash algorithm '" + alg + "'", e);
}
String salt = "";
switch (alg) {
case SHA256:
salt = createSalt(16);
break;
case MD5VB:
salt = createSalt(16);
break;
case XAUTH:
salt = createSalt(12);
break;
case MYBB:
salt = createSalt(8);
userSalt.put(playerName, salt);
break;
case IPB3:
salt = createSalt(5);
userSalt.put(playerName, salt);
break;
case PHPFUSION:
salt = createSalt(12);
userSalt.put(playerName, salt);
break;
case SALTED2MD5:
salt = createSalt(Settings.saltLength);
userSalt.put(playerName, salt);
break;
case JOOMLA:
salt = createSalt(32);
userSalt.put(playerName, salt);
break;
case BCRYPT:
salt = BCRYPT.gensalt(Settings.bCryptLog2Rounds);
userSalt.put(playerName, salt);
break;
case WBB3:
salt = createSalt(40);
userSalt.put(playerName, salt);
break;
case WBB4:
salt = BCRYPT.gensalt(8);
userSalt.put(playerName, salt);
break;
case PBKDF2DJANGO:
case PBKDF2:
salt = createSalt(12);
userSalt.put(playerName, salt);
break;
case SMF:
return method.computeHash(password, null, playerName);
case PHPBB:
salt = createSalt(16);
userSalt.put(playerName, salt);
break;
case BCRYPT2Y:
salt = createSalt(16);
userSalt.put(playerName, salt);
break;
case SALTEDSHA512:
salt = createSalt(32);
userSalt.put(playerName, salt);
break;
case MD5:
case SHA1:
case WHIRLPOOL:
case PLAINTEXT:
case XENFORO:
case SHA512:
case ROYALAUTH:
case CRAZYCRYPT1:
case DOUBLEMD5:
case WORDPRESS:
case CUSTOM:
break;
default:
throw new NoSuchAlgorithmException("Unknown hash algorithm");
}
PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName);
Bukkit.getPluginManager().callEvent(event);
method = event.getMethod();
if (method == null)
throw new NoSuchAlgorithmException("Unknown hash algorithm");
return method.computeHash(password, salt, playerName);
} }
public static boolean comparePasswordWithHash(String password, String hash, public EncryptedPassword computeHash(HashAlgorithm algorithm, String password, String playerName) {
String playerName) throws NoSuchAlgorithmException { EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName);
HashAlgorithm algorithm = Settings.getPasswordHash; return method.computeHash(password, playerName);
EncryptionMethod method; }
try {
if (algorithm != HashAlgorithm.CUSTOM) {
method = algorithm.getClazz().newInstance();
} else {
method = null;
}
PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName); public boolean comparePassword(String password, String playerName) {
Bukkit.getPluginManager().callEvent(event); // TODO ljacqu 20151230: Defining a dataSource.getPassword() method would be more efficient
method = event.getMethod(); PlayerAuth auth = dataSource.getAuth(playerName);
if (auth != null) {
if (method == null) return comparePassword(password, auth.getPassword(), playerName);
throw new NoSuchAlgorithmException("Unknown hash algorithm");
if (method.comparePassword(hash, password, playerName))
return true;
if (Settings.supportOldPassword) {
if (compareWithAllEncryptionMethod(password, hash, playerName))
return true;
}
} catch (InstantiationException | IllegalAccessException e) {
throw new NoSuchAlgorithmException("Problem with this hash algorithm");
} }
return false; return false;
} }
private static boolean compareWithAllEncryptionMethod(String password, public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String playerName) {
String hash, String playerName) { EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName);
for (HashAlgorithm algo : HashAlgorithm.values()) { // User is not in data source, so the result will invariably be wrong because an encryption
if (algo != HashAlgorithm.CUSTOM) { // method with hasSeparateSalt() == true NEEDS the salt to evaluate the password
try { String salt = encryptedPassword.getSalt();
EncryptionMethod method = algo.getClazz().newInstance(); if (method.hasSeparateSalt() && salt == null) {
if (method.comparePassword(hash, password, playerName)) { return false;
PlayerAuth nAuth = AuthMe.getInstance().database.getAuth(playerName); }
if (nAuth != null) {
nAuth.setHash(getHash(Settings.getPasswordHash, password, playerName)); return method.comparePassword(password, encryptedPassword, playerName)
nAuth.setSalt(userSalt.containsKey(playerName) ? userSalt.get(playerName) : ""); || supportOldAlgorithm && compareWithAllEncryptionMethods(password, encryptedPassword, playerName);
AuthMe.getInstance().database.updatePassword(nAuth); }
AuthMe.getInstance().database.updateSalt(nAuth);
} /**
return true; * Compare the given hash with all available encryption methods to support
} * the migration to a new encryption method. Upon a successful match, the password
} catch (Exception ignored) { * will be hashed with the new encryption method and persisted.
*
* @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, EncryptedPassword encryptedPassword,
String playerName) {
for (HashAlgorithm algorithm : HashAlgorithm.values()) {
if (!HashAlgorithm.CUSTOM.equals(algorithm)) {
EncryptionMethod method = initializeEncryptionMethodWithoutEvent(algorithm);
if (method != null && method.comparePassword(password, encryptedPassword, playerName)) {
hashPasswordForNewAlgorithm(password, playerName);
return true;
} }
} }
} }
return false; return false;
} }
/**
* 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
* @param playerName The name of the player a password will be hashed for
* @return The encryption method
*/
private EncryptionMethod initializeEncryptionMethod(HashAlgorithm algorithm, String playerName) {
EncryptionMethod method = initializeEncryptionMethodWithoutEvent(algorithm);
PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName);
pluginManager.callEvent(event);
return event.getMethod();
}
/**
* Initialize the encryption method corresponding to the given hash algorithm.
*
* @param algorithm The algorithm to retrieve the encryption method for
* @return The associated encryption method
*/
private static EncryptionMethod initializeEncryptionMethodWithoutEvent(HashAlgorithm algorithm) {
try {
return HashAlgorithm.CUSTOM.equals(algorithm)
? null
: algorithm.getClazz().newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new UnsupportedOperationException("Constructor for '" + algorithm.getClazz().getSimpleName()
+ "' could not be invoked. (Is there no default constructor?)", e);
}
}
private void hashPasswordForNewAlgorithm(String password, String playerName) {
PlayerAuth auth = dataSource.getAuth(playerName);
if (auth != null) {
EncryptedPassword encryptedPassword = initializeEncryptionMethod(algorithm, playerName)
.computeHash(password, playerName);
auth.setPassword(encryptedPassword);
dataSource.updatePassword(auth);
}
}
} }

View File

@ -1,13 +1,16 @@
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 { /**
* Utility for generating random strings.
*/
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 = 16;
static { static {
for (int idx = 0; idx < 10; ++idx) { for (int idx = 0; idx < 10; ++idx) {
@ -18,30 +21,37 @@ 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);
} }
/**
* Generate a string of the given length consisting of random characters within the range [0-9a-z].
*
* @param length The length of the random string to generate
* @return The random string
*/
public static String generate(int length) { public static String generate(int length) {
return generate(length, chars.length);
}
/**
* Generate a random hexadecimal string of the given length. In other words, the generated string
* contains characters only within the range [0-9a-f].
*
* @param length The length of the random string to generate
* @return The random hexadecimal string
*/
public static String generateHex(int length) {
return generate(length, HEX_MAX_INDEX);
}
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

@ -13,8 +13,13 @@
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package fr.xephi.authme.security.crypts; 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 fr.xephi.authme.settings.Settings;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
/** /**
@ -59,11 +64,13 @@ import java.security.SecureRandom;
* @author Damien Miller * @author Damien Miller
* @version 0.2 * @version 0.2
*/ */
@Recommendation(Usage.RECOMMENDED) // provided the salt length is >= 8
@HasSalt(value = SaltType.TEXT) // length depends on Settings.bCryptLog2Rounds
public class BCRYPT implements EncryptionMethod { public class BCRYPT implements EncryptionMethod {
// BCrypt parameters // BCrypt parameters
private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10; 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 // Blowfish parameters
private static final int BLOWFISH_NUM_ROUNDS = 16; private static final int BLOWFISH_NUM_ROUNDS = 16;
@ -508,14 +515,28 @@ public class BCRYPT implements EncryptionMethod {
} }
@Override @Override
public String computeHash(String password, String salt, String name) public String computeHash(String password, String salt, String name) {
throws NoSuchAlgorithmException {
return hashpw(password, salt); return hashpw(password, salt);
} }
@Override @Override
public boolean comparePassword(String hash, String password, public EncryptedPassword computeHash(String password, String name) {
String playerName) throws NoSuchAlgorithmException { String salt = generateSalt();
return checkpw(password, hash); return new EncryptedPassword(hashpw(password, salt), null);
}
@Override
public boolean comparePassword(String password, EncryptedPassword hash, String name) {
return checkpw(password, hash.getHash());
}
@Override
public String generateSalt() {
return BCRYPT.gensalt(Settings.bCryptLog2Rounds);
}
@Override
public boolean hasSeparateSalt() {
return false;
} }
} }

View File

@ -1,26 +1,34 @@
package fr.xephi.authme.security.crypts; 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.RECOMMENDED)
*/ public class BCRYPT2Y extends HexSaltedMethod {
public class BCRYPT2Y implements EncryptionMethod {
@Override @Override
public String computeHash(String password, String salt, String name) public String computeHash(String password, String salt, String name) {
throws NoSuchAlgorithmException { if (salt.length() == 22) {
if (salt.length() == 22)
salt = "$2y$10$" + salt; salt = "$2y$10$" + salt;
return (BCRYPT.hashpw(password, salt)); }
return BCRYPT.hashpw(password, salt);
} }
@Override @Override
public boolean comparePassword(String hash, String password, public boolean comparePassword(String password, EncryptedPassword encrypted, String unusedName) {
String playerName) throws NoSuchAlgorithmException { String hash = encrypted.getHash();
String ok = hash.substring(0, 29); if (hash.length() != 60) {
if (ok.length() != 29)
return false; return false;
return hash.equals(computeHash(password, ok, playerName)); }
// The salt is the first 29 characters of the hash
String salt = hash.substring(0, 29);
return hash.equals(computeHash(password, salt, null));
}
@Override
public int getSaltLength() {
return 22;
} }
} }

View File

@ -1,18 +1,24 @@
package fr.xephi.authme.security.crypts; 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.nio.charset.Charset;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/** @Recommendation(Usage.DO_NOT_USE)
*/ @HasSalt(SaltType.USERNAME)
public class CRAZYCRYPT1 implements EncryptionMethod { public class CRAZYCRYPT1 extends UsernameSaltMethod {
private static final char[] CRYPTCHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; private static final char[] CRYPTCHARS =
protected final Charset charset = Charset.forName("UTF-8"); {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
private final Charset charset = Charset.forName("UTF-8");
private static String byteArrayToHexString(final byte... args) {
public static String byteArrayToHexString(final byte... args) {
final char[] chars = new char[args.length * 2]; final char[] chars = new char[args.length * 2];
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
chars[i * 2] = CRYPTCHARS[(args[i] >> 4) & 0xF]; chars[i * 2] = CRYPTCHARS[(args[i] >> 4) & 0xF];
@ -22,21 +28,11 @@ public class CRAZYCRYPT1 implements EncryptionMethod {
} }
@Override @Override
public String computeHash(String password, String salt, String name) public EncryptedPassword computeHash(String password, String name) {
throws NoSuchAlgorithmException {
final String text = "ÜÄaeut//&/=I " + password + "7421€547" + name + "__+IÄIH§%NK " + password; final String text = "ÜÄaeut//&/=I " + password + "7421€547" + name + "__+IÄIH§%NK " + password;
try { final MessageDigest md = HashUtils.getDigest(MessageDigestAlgorithm.SHA512);
final MessageDigest md = MessageDigest.getInstance("SHA-512"); md.update(text.getBytes(charset), 0, text.length());
md.update(text.getBytes(charset), 0, text.length()); return new EncryptedPassword(byteArrayToHexString(md.digest()));
return byteArrayToHexString(md.digest());
} catch (final NoSuchAlgorithmException e) {
return null;
}
} }
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
return hash.equals(computeHash(password, null, playerName));
}
} }

View File

@ -1,18 +1,17 @@
package fr.xephi.authme.security.crypts; 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.PBKDF2Engine;
import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters; import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays; import java.util.Arrays;
/** @Recommendation(Usage.DOES_NOT_WORK)
*/ public class CryptPBKDF2 extends HexSaltedMethod {
public class CryptPBKDF2 implements EncryptionMethod {
@Override @Override
public String computeHash(String password, String salt, String name) public String computeHash(String password, String salt, String name) {
throws NoSuchAlgorithmException {
String result = "pbkdf2_sha256$10000$" + salt + "$"; String result = "pbkdf2_sha256$10000$" + salt + "$";
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 10000); PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 10000);
PBKDF2Engine engine = new PBKDF2Engine(params); PBKDF2Engine engine = new PBKDF2Engine(params);
@ -21,9 +20,8 @@ public class CryptPBKDF2 implements EncryptionMethod {
} }
@Override @Override
public boolean comparePassword(String hash, String password, public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String unusedName) {
String playerName) throws NoSuchAlgorithmException { String[] line = encryptedPassword.getHash().split("\\$");
String[] line = hash.split("\\$");
String salt = line[2]; String salt = line[2];
String derivedKey = line[3]; String derivedKey = line[3];
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 10000, derivedKey.getBytes()); PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 10000, derivedKey.getBytes());
@ -31,4 +29,9 @@ public class CryptPBKDF2 implements EncryptionMethod {
return engine.verifyKey(password); return engine.verifyKey(password);
} }
@Override
public int getSaltLength() {
return 12;
}
} }

View File

@ -1,18 +1,16 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.crypts.description.AsciiRestricted;
import fr.xephi.authme.security.pbkdf2.PBKDF2Engine; import fr.xephi.authme.security.pbkdf2.PBKDF2Engine;
import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters; import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters;
import javax.xml.bind.DatatypeConverter; import javax.xml.bind.DatatypeConverter;
import java.security.NoSuchAlgorithmException;
/** @AsciiRestricted
*/ public class CryptPBKDF2Django extends HexSaltedMethod {
public class CryptPBKDF2Django implements EncryptionMethod {
@Override @Override
public String computeHash(String password, String salt, String name) public String computeHash(String password, String salt, String name) {
throws NoSuchAlgorithmException {
String result = "pbkdf2_sha256$15000$" + salt + "$"; String result = "pbkdf2_sha256$15000$" + salt + "$";
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 15000); PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 15000);
PBKDF2Engine engine = new PBKDF2Engine(params); PBKDF2Engine engine = new PBKDF2Engine(params);
@ -21,9 +19,8 @@ public class CryptPBKDF2Django implements EncryptionMethod {
} }
@Override @Override
public boolean comparePassword(String hash, String password, public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String unusedName) {
String playerName) throws NoSuchAlgorithmException { String[] line = encryptedPassword.getHash().split("\\$");
String[] line = hash.split("\\$");
String salt = line[2]; String salt = line[2];
byte[] derivedKey = DatatypeConverter.parseBase64Binary(line[3]); byte[] derivedKey = DatatypeConverter.parseBase64Binary(line[3]);
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 15000, derivedKey); PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 15000, derivedKey);
@ -31,4 +28,9 @@ public class CryptPBKDF2Django implements EncryptionMethod {
return engine.verifyKey(password); return engine.verifyKey(password);
} }
@Override
public int getSaltLength() {
return 12;
}
} }

View File

@ -1,32 +1,12 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import java.math.BigInteger; import static fr.xephi.authme.security.HashUtils.md5;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/** public class DOUBLEMD5 extends UnsaltedMethod {
*/
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 @Override
public String computeHash(String password, String salt, String name) public String computeHash(String password) {
throws NoSuchAlgorithmException { return md5(md5(password));
return getMD5(getMD5(password));
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
return hash.equals(computeHash(password, "", ""));
} }
} }

View File

@ -0,0 +1,48 @@
package fr.xephi.authme.security.crypts;
/**
* The result of a hash computation. See {@link #salt} for details.
*/
public class EncryptedPassword {
/** The generated hash. */
private final String hash;
/**
* The generated salt; may be null if no salt is used or if the salt is included
* in the hash output. The salt is only not null if {@link EncryptionMethod#hasSeparateSalt()}
* returns true for the associated encryption method.
* <p>
* When the field is not null, it must be stored into the salt column of the data source
* and retrieved again to compare a password with the hash.
*/
private final String salt;
/**
* Constructor.
*
* @param hash The computed hash
* @param salt The generated salt
*/
public EncryptedPassword(String hash, String salt) {
this.hash = hash;
this.salt = salt;
}
/**
* Constructor for a hash with no separate salt.
*
* @param hash The computed hash
*/
public EncryptedPassword(String hash) {
this(hash, null);
}
public String getHash() {
return hash;
}
public String getSalt() {
return salt;
}
}

View File

@ -1,35 +1,59 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import java.security.NoSuchAlgorithmException;
/** /**
* Public interface for custom password encryption methods. * Public interface for custom password encryption methods.
*/ */
public interface EncryptionMethod { public interface EncryptionMethod {
/** /**
* Hash the given password with the given salt for the given player. * Hash the given password for the given player name.
* *
* @param password The clear-text password to hash * @param password The password to hash
* @param salt The salt to add to the hash * @param name The name of the player (sometimes required to generate a salt with)
* @param name The player's name (sometimes required for storing the salt separately in the database)
* *
* @return The hashed password * @return The hash result for the password.
* @see EncryptedPassword
*/ */
String computeHash(String password, String salt, String name) EncryptedPassword computeHash(String password, String name);
throws NoSuchAlgorithmException;
/** /**
* Check whether a given hash matches the clear-text password. * Hash the given password with the given salt for the given player.
* *
* @param hash The hash to verify * @param password The password to hash
* @param password The clear-text password to verify the hash against * @param salt The salt to add to the hash
* @param playerName The player name to do the check for (sometimes required for retrieving * @param name The player's name (sometimes required to generate a salt with)
* the salt from the database) *
* @return The hashed password
* @see #hasSeparateSalt()
*/
String computeHash(String password, String salt, String name);
/**
* Check whether the given hash matches the clear-text password.
*
* @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 * @return True if the password matches, false otherwise
*/ */
boolean comparePassword(String hash, String password, String playerName) boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name);
throws NoSuchAlgorithmException;
/**
* Generate a new salt to hash a password with.
*
* @return The generated salt, null if the method does not use a random text-based salt
*/
String generateSalt();
/**
* Return whether the encryption method requires the salt to be stored separately and
* passed again to {@link #comparePassword(String, 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.
*
* @return True if the salt has to be stored and retrieved separately, false otherwise
*/
boolean hasSeparateSalt();
} }

View File

@ -0,0 +1,40 @@
package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.RandomString;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
/**
* Common type for encryption methods which use a random String of hexadecimal characters
* and store the salt with the hash itself.
*/
@Recommendation(Usage.ACCEPTABLE)
@HasSalt(SaltType.TEXT) // See saltLength() for length
public abstract class HexSaltedMethod implements EncryptionMethod {
public abstract int getSaltLength();
@Override
public abstract String computeHash(String password, String salt, String name);
@Override
public EncryptedPassword computeHash(String password, String name) {
String salt = generateSalt();
return new EncryptedPassword(computeHash(password, salt, null));
}
@Override
public abstract boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name);
@Override
public String generateSalt() {
return RandomString.generateHex(getSaltLength());
}
@Override
public boolean hasSeparateSalt() {
return false;
}
}

View File

@ -1,34 +1,25 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import fr.xephi.authme.AuthMe; import fr.xephi.authme.security.RandomString;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
import java.math.BigInteger; import static fr.xephi.authme.security.HashUtils.md5;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/** @Recommendation(Usage.DO_NOT_USE)
*/ @HasSalt(value = SaltType.TEXT, length = 5)
public class IPB3 implements EncryptionMethod { public class IPB3 extends SeparateSaltMethod {
private static String getMD5(String message) @Override
throws NoSuchAlgorithmException { public String computeHash(String password, String salt, String name) {
MessageDigest md5 = MessageDigest.getInstance("MD5"); return md5(md5(salt) + md5(password));
md5.reset();
md5.update(message.getBytes());
byte[] digest = md5.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
} }
@Override @Override
public String computeHash(String password, String salt, String name) public String generateSalt() {
throws NoSuchAlgorithmException { return RandomString.generateHex(5);
return getMD5(getMD5(salt) + getMD5(password));
} }
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
return hash.equals(computeHash(password, salt, playerName));
}
} }

View File

@ -1,32 +1,27 @@
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.Recommendation;
import java.security.NoSuchAlgorithmException; import fr.xephi.authme.security.crypts.description.Usage;
/** @Recommendation(Usage.RECOMMENDED)
*/ public class JOOMLA extends HexSaltedMethod {
public class JOOMLA implements EncryptionMethod {
private static String getMD5(String message) @Override
throws NoSuchAlgorithmException { public String computeHash(String password, String salt, String name) {
MessageDigest md5 = MessageDigest.getInstance("MD5"); return HashUtils.md5(password + salt) + ":" + salt;
md5.reset();
md5.update(message.getBytes());
byte[] digest = md5.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
} }
@Override @Override
public String computeHash(String password, String salt, String name) public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String unusedName) {
throws NoSuchAlgorithmException { String hash = encryptedPassword.getHash();
return getMD5(password + salt) + ":" + salt; String[] hashParts = hash.split(":");
return hashParts.length == 2 && hash.equals(computeHash(password, hashParts[1], null));
} }
@Override @Override
public boolean comparePassword(String hash, String password, public int getSaltLength() {
String playerName) throws NoSuchAlgorithmException { return 32;
String salt = hash.split(":")[1];
return hash.equals(getMD5(password + salt) + ":" + salt);
} }
} }

View File

@ -1,31 +1,16 @@
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;
/** public class MD5 extends UnsaltedMethod {
*/
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 @Override
public String computeHash(String password, String salt, String name) public String computeHash(String password) {
throws NoSuchAlgorithmException { return HashUtils.md5(password);
return getMD5(password);
} }
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
return hash.equals(computeHash(password, "", ""));
}
} }

View File

@ -1,33 +1,24 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import java.math.BigInteger; import static fr.xephi.authme.security.HashUtils.md5;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/** public class MD5VB extends HexSaltedMethod {
*/
public class MD5VB implements EncryptionMethod {
private static String getMD5(String message) @Override
throws NoSuchAlgorithmException { public String computeHash(String password, String salt, String name) {
MessageDigest md5 = MessageDigest.getInstance("MD5"); return "$MD5vb$" + salt + "$" + md5(md5(password) + salt);
md5.reset();
md5.update(message.getBytes());
byte[] digest = md5.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
} }
@Override @Override
public String computeHash(String password, String salt, String name) public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) {
throws NoSuchAlgorithmException { String hash = encryptedPassword.getHash();
return "$MD5vb$" + salt + "$" + getMD5(getMD5(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], name));
}
@Override
public int getSaltLength() {
return 16;
} }
} }

View File

@ -1,34 +1,19 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import fr.xephi.authme.AuthMe; import fr.xephi.authme.security.RandomString;
import java.math.BigInteger; import static fr.xephi.authme.security.HashUtils.md5;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/** public class MYBB extends SeparateSaltMethod {
*/
public class MYBB implements EncryptionMethod {
private static String getMD5(String message) @Override
throws NoSuchAlgorithmException { public String computeHash(String password, String salt, String name) {
MessageDigest md5 = MessageDigest.getInstance("MD5"); return md5(md5(salt) + md5(password));
md5.reset();
md5.update(message.getBytes());
byte[] digest = md5.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
} }
@Override @Override
public String computeHash(String password, String salt, String name) public String generateSalt() {
throws NoSuchAlgorithmException { return RandomString.generateHex(8);
return getMD5(getMD5(salt) + getMD5(password));
} }
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
return hash.equals(computeHash(password, salt, playerName));
}
} }

View File

@ -4,25 +4,26 @@
*/ */
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.HashUtils;
import fr.xephi.authme.security.MessageDigestAlgorithm;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/** /**
* @author stefano * @author stefano
*/ */
public class PHPBB implements EncryptionMethod { public class PHPBB extends HexSaltedMethod {
private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
public static String md5(String data) { private static String md5(String data) {
try { try {
byte[] bytes = data.getBytes("ISO-8859-1"); byte[] bytes = data.getBytes("ISO-8859-1");
MessageDigest md5er = MessageDigest.getInstance("MD5"); MessageDigest md5er = HashUtils.getDigest(MessageDigestAlgorithm.MD5);
byte[] hash = md5er.digest(bytes); byte[] hash = md5er.digest(bytes);
return bytes2hex(hash); return bytes2hex(hash);
} catch (GeneralSecurityException | UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@ -58,7 +59,7 @@ public class PHPBB implements EncryptionMethod {
return buf.toString(); return buf.toString();
} }
public String phpbb_hash(String password, String salt) { private String phpbb_hash(String password, String salt) {
String random_state = salt; String random_state = salt;
StringBuilder random = new StringBuilder(); StringBuilder random = new StringBuilder();
int count = 6; int count = 6;
@ -109,7 +110,7 @@ public class PHPBB implements EncryptionMethod {
return output.toString(); return output.toString();
} }
String _hash_crypt_private(String password, String setting) { private String _hash_crypt_private(String password, String setting) {
String output = "*"; String output = "*";
if (!setting.substring(0, 3).equals("$H$")) if (!setting.substring(0, 3).equals("$H$"))
return output; return output;
@ -130,21 +131,26 @@ public class PHPBB implements EncryptionMethod {
return output; return output;
} }
public boolean phpbb_check_hash(String password, String hash) { private boolean phpbb_check_hash(String password, String hash) {
if (hash.length() == 34) if (hash.length() == 34) {
return _hash_crypt_private(password, hash).equals(hash); return _hash_crypt_private(password, hash).equals(hash);
else return md5(password).equals(hash); }
return md5(password).equals(hash);
} }
@Override @Override
public String computeHash(String password, String salt, String name) public String computeHash(String password, String salt, String name) {
throws NoSuchAlgorithmException {
return phpbb_hash(password, salt); return phpbb_hash(password, salt);
} }
@Override @Override
public boolean comparePassword(String hash, String password, public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) {
String playerName) throws NoSuchAlgorithmException { return phpbb_check_hash(password, encryptedPassword.getHash());
return phpbb_check_hash(password, hash);
} }
@Override
public int getSaltLength() {
return 16;
}
} }

View File

@ -1,36 +1,27 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import fr.xephi.authme.AuthMe; import fr.xephi.authme.security.HashUtils;
import fr.xephi.authme.security.RandomString;
import fr.xephi.authme.security.crypts.description.AsciiRestricted;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
import javax.crypto.Mac; import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
/** @Recommendation(Usage.DO_NOT_USE)
*/ @AsciiRestricted
public class PHPFUSION implements EncryptionMethod { public class PHPFUSION extends SeparateSaltMethod {
private static String getSHA1(String message)
throws NoSuchAlgorithmException {
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
sha1.reset();
sha1.update(message.getBytes());
byte[] digest = sha1.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
}
@Override @Override
public String computeHash(String password, String salt, String name) public String computeHash(String password, String salt, String name) {
throws NoSuchAlgorithmException {
String digest = null;
String algo = "HmacSHA256"; String algo = "HmacSHA256";
String keyString = getSHA1(salt); String keyString = HashUtils.sha1(salt);
try { try {
SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"), algo); SecretKeySpec key = new SecretKeySpec(keyString.getBytes("UTF-8"), algo);
Mac mac = Mac.getInstance(algo); Mac mac = Mac.getInstance(algo);
mac.init(key); mac.init(key);
byte[] bytes = mac.doFinal(password.getBytes("ASCII")); byte[] bytes = mac.doFinal(password.getBytes("ASCII"));
@ -42,19 +33,16 @@ public class PHPFUSION implements EncryptionMethod {
} }
hash.append(hex); hash.append(hex);
} }
digest = hash.toString(); return hash.toString();
} catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException e) { } catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException e) {
//ingore throw new UnsupportedOperationException("Cannot create PHPFUSION hash for " + name, e);
} }
return digest;
} }
@Override @Override
public boolean comparePassword(String hash, String password, public String generateSalt() {
String playerName) throws NoSuchAlgorithmException { return RandomString.generateHex(12);
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
return hash.equals(computeHash(password, salt, ""));
} }
} }

View File

@ -1,21 +1,11 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import java.security.NoSuchAlgorithmException; @Deprecated
public class PLAINTEXT extends UnsaltedMethod {
/**
*/
public class PLAINTEXT implements EncryptionMethod {
@Override @Override
public String computeHash(String password, String salt, String name) public String computeHash(String password) {
throws NoSuchAlgorithmException {
return password; return password;
} }
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
return hash.equals(password);
}
} }

View File

@ -1,35 +1,16 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import java.security.MessageDigest; import fr.xephi.authme.security.HashUtils;
import java.security.NoSuchAlgorithmException;
/** public class ROYALAUTH extends UnsaltedMethod {
*/
public class ROYALAUTH implements EncryptionMethod {
@Override @Override
public String computeHash(String password, String salt, String name) public String computeHash(String password) {
throws NoSuchAlgorithmException { for (int i = 0; i < 25; i++) {
for (int i = 0; i < 25; i++) // TODO ljacqu 20151228: HashUtils#sha512 gets a new message digest each time...
password = hash(password, salt); password = HashUtils.sha512(password);
}
return password; return password;
} }
public String hash(String password, String salt)
throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(password.getBytes());
byte byteData[] = md.digest();
StringBuilder sb = new StringBuilder();
for (byte aByteData : byteData)
sb.append(Integer.toString((aByteData & 0xff) + 0x100, 16).substring(1));
return sb.toString();
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
return hash.equalsIgnoreCase(computeHash(password, "", ""));
}
} }

View File

@ -1,34 +1,26 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import fr.xephi.authme.AuthMe; import fr.xephi.authme.security.RandomString;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
import fr.xephi.authme.settings.Settings;
import java.math.BigInteger; import static fr.xephi.authme.security.HashUtils.md5;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/** @Recommendation(Usage.ACCEPTABLE) // presuming that length is something sensible (>= 8)
*/ @HasSalt(value = SaltType.TEXT) // length defined by Settings.saltLength
public class SALTED2MD5 implements EncryptionMethod { public class SALTED2MD5 extends SeparateSaltMethod {
private static String getMD5(String message) @Override
throws NoSuchAlgorithmException { public String computeHash(String password, String salt, String name) {
MessageDigest md5 = MessageDigest.getInstance("MD5"); return md5(md5(password) + salt);
md5.reset();
md5.update(message.getBytes());
byte[] digest = md5.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
} }
@Override @Override
public String computeHash(String password, String salt, String name) public String generateSalt() {
throws NoSuchAlgorithmException { return RandomString.generateHex(Settings.saltLength);
return getMD5(getMD5(password) + salt);
} }
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
return hash.equals(getMD5(getMD5(password) + salt));
}
} }

View File

@ -1,34 +1,20 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import fr.xephi.authme.AuthMe; import fr.xephi.authme.security.HashUtils;
import fr.xephi.authme.security.RandomString;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
import java.math.BigInteger; @Recommendation(Usage.RECOMMENDED)
import java.security.MessageDigest; public class SALTEDSHA512 extends SeparateSaltMethod {
import java.security.NoSuchAlgorithmException;
/** @Override
*/ public String computeHash(String password, String salt, String name) {
public class SALTEDSHA512 implements EncryptionMethod { return HashUtils.sha512(password + salt);
private static String getSHA512(String message)
throws NoSuchAlgorithmException {
MessageDigest sha512 = MessageDigest.getInstance("SHA-512");
sha512.reset();
sha512.update(message.getBytes());
byte[] digest = sha512.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
} }
@Override @Override
public String computeHash(String password, String salt, String name) public String generateSalt() {
throws NoSuchAlgorithmException { return RandomString.generateHex(32);
return getSHA512(password + salt);
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
return hash.equals(computeHash(password, salt, ""));
} }
} }

View File

@ -1,32 +1,12 @@
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 java.security.NoSuchAlgorithmException;
/** public class SHA1 extends UnsaltedMethod {
*/
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 @Override
public String computeHash(String password, String salt, String name) public String computeHash(String password) {
throws NoSuchAlgorithmException { return HashUtils.sha1(password);
return getSHA1(password);
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
return hash.equals(computeHash(password, "", ""));
} }
} }

View File

@ -1,33 +1,28 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import java.math.BigInteger; import fr.xephi.authme.security.crypts.description.Recommendation;
import java.security.MessageDigest; import fr.xephi.authme.security.crypts.description.Usage;
import java.security.NoSuchAlgorithmException;
/** import static fr.xephi.authme.security.HashUtils.sha256;
*/
public class SHA256 implements EncryptionMethod {
private static String getSHA256(String message) @Recommendation(Usage.RECOMMENDED)
throws NoSuchAlgorithmException { public class SHA256 extends HexSaltedMethod {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
sha256.reset(); @Override
sha256.update(message.getBytes()); public String computeHash(String password, String salt, String name) {
byte[] digest = sha256.digest(); return "$SHA$" + salt + "$" + sha256(sha256(password) + salt);
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
} }
@Override @Override
public String computeHash(String password, String salt, String name) public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String playerName) {
throws NoSuchAlgorithmException { String hash = encryptedPassword.getHash();
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], ""));
}
@Override
public int getSaltLength() {
return 16;
} }
} }

View File

@ -1,31 +1,12 @@
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 java.security.NoSuchAlgorithmException;
/** public class SHA512 extends UnsaltedMethod {
*/
public class SHA512 implements EncryptionMethod {
private static String getSHA512(String message)
throws NoSuchAlgorithmException {
MessageDigest sha512 = MessageDigest.getInstance("SHA-512");
sha512.reset();
sha512.update(message.getBytes());
byte[] digest = sha512.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
}
@Override @Override
public String computeHash(String password, String salt, String name) public String computeHash(String password) {
throws NoSuchAlgorithmException { return HashUtils.sha512(password);
return getSHA512(password);
} }
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
return hash.equals(computeHash(password, "", ""));
}
} }

View File

@ -1,31 +1,11 @@
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 java.security.NoSuchAlgorithmException;
/** public class SMF extends UsernameSaltMethod {
*/
public class SMF implements EncryptionMethod {
private static String getSHA1(String message) public EncryptedPassword computeHash(String password, String name) {
throws NoSuchAlgorithmException { return new EncryptedPassword(HashUtils.sha1(name.toLowerCase() + password));
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)
throws NoSuchAlgorithmException {
return getSHA1(name.toLowerCase() + password);
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
return hash.equals(computeHash(password, null, playerName));
}
} }

View File

@ -0,0 +1,30 @@
package fr.xephi.authme.security.crypts;
/**
* Common supertype for encryption methods which store their salt separately from the hash.
*/
public abstract class SeparateSaltMethod implements EncryptionMethod {
@Override
public abstract String computeHash(String password, String salt, String name);
@Override
public abstract String generateSalt();
@Override
public EncryptedPassword computeHash(String password, String name) {
String salt = generateSalt();
return new EncryptedPassword(computeHash(password, salt, name), salt);
}
@Override
public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) {
return encryptedPassword.getHash().equals(computeHash(password, encryptedPassword.getSalt(), null));
}
@Override
public boolean hasSeparateSalt() {
return true;
}
}

View File

@ -0,0 +1,41 @@
package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
/**
* Common type for encryption methods which do not use any salt whatsoever.
*/
@Recommendation(Usage.DO_NOT_USE)
@HasSalt(SaltType.NONE)
public abstract class UnsaltedMethod implements EncryptionMethod {
public abstract String computeHash(String password);
@Override
public EncryptedPassword computeHash(String password, String name) {
return new EncryptedPassword(computeHash(password));
}
@Override
public String computeHash(String password, String salt, String name) {
return computeHash(password);
}
@Override
public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) {
return encryptedPassword.getHash().equals(computeHash(password));
}
@Override
public String generateSalt() {
return null;
}
@Override
public boolean hasSeparateSalt() {
return false;
}
}

View File

@ -0,0 +1,39 @@
package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
/**
* Common supertype of encryption methods that use a player's username
* (or something based on it) as embedded salt.
*/
@Recommendation(Usage.DO_NOT_USE)
@HasSalt(SaltType.USERNAME)
public abstract class UsernameSaltMethod implements EncryptionMethod {
@Override
public abstract EncryptedPassword computeHash(String password, String name);
@Override
public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) {
return encryptedPassword.getHash().equals(computeHash(password, name).getHash());
}
@Override
public String computeHash(String password, String salt, String name) {
return computeHash(password, name).getHash();
}
@Override
public String generateSalt() {
return null;
}
@Override
public boolean hasSeparateSalt() {
return false;
}
}

View File

@ -1,34 +1,19 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import fr.xephi.authme.AuthMe; import fr.xephi.authme.security.RandomString;
import java.math.BigInteger; import static fr.xephi.authme.security.HashUtils.sha1;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/** public class WBB3 extends SeparateSaltMethod {
*/
public class WBB3 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 sha1(salt.concat(sha1(salt.concat(sha1(password)))));
sha1.reset();
sha1.update(message.getBytes());
byte[] digest = sha1.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
} }
@Override @Override
public String computeHash(String password, String salt, String name) public String generateSalt() {
throws NoSuchAlgorithmException { return RandomString.generateHex(40);
return getSHA1(salt.concat(getSHA1(salt.concat(getSHA1(password)))));
} }
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
return hash.equals(computeHash(password, salt, ""));
}
} }

View File

@ -1,21 +1,34 @@
package fr.xephi.authme.security.crypts; 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 WBB4 extends HexSaltedMethod {
public class WBB4 implements EncryptionMethod {
@Override @Override
public String computeHash(String password, String salt, String name) public String computeHash(String password, String salt, String name) {
throws NoSuchAlgorithmException {
return BCRYPT.getDoubleHash(password, salt); return BCRYPT.getDoubleHash(password, salt);
} }
@Override @Override
public boolean comparePassword(String hash, String password, public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String playerName) {
String playerName) throws NoSuchAlgorithmException { return BCRYPT.checkpw(password, encryptedPassword.getHash(), 2);
return BCRYPT.checkpw(password, hash, 2); }
@Override
public String generateSalt() {
return BCRYPT.gensalt(8);
}
/**
* Note that {@link #generateSalt()} is overridden for this class.
*
* @return The salt length
*/
@Override
public int getSaltLength() {
return 8;
} }
} }

View File

@ -59,12 +59,9 @@ package fr.xephi.authme.security.crypts;
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
import java.security.NoSuchAlgorithmException;
import java.util.Arrays; import java.util.Arrays;
/** public class WHIRLPOOL extends UnsaltedMethod {
*/
public class WHIRLPOOL implements EncryptionMethod {
/** /**
* The message digest size (in bits) * The message digest size (in bits)
@ -382,9 +379,7 @@ public class WHIRLPOOL implements EncryptionMethod {
} }
} }
@Override public String computeHash(String password) {
public String computeHash(String password, String salt, String name)
throws NoSuchAlgorithmException {
byte[] digest = new byte[DIGESTBYTES]; byte[] digest = new byte[DIGESTBYTES];
NESSIEinit(); NESSIEinit();
NESSIEadd(password); NESSIEadd(password);
@ -392,9 +387,4 @@ public class WHIRLPOOL implements EncryptionMethod {
return display(digest); return display(digest);
} }
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
return hash.equals(computeHash(password, "", ""));
}
} }

View File

@ -1,14 +1,22 @@
package fr.xephi.authme.security.crypts; 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.io.UnsupportedEncodingException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Arrays; import java.util.Arrays;
/** // TODO #391: Wordpress algorithm fails sometimes. Fix it and change the Recommendation to "ACCEPTABLE" if appropriate
*/ @Recommendation(Usage.DO_NOT_USE)
public class WORDPRESS implements EncryptionMethod { @HasSalt(value = SaltType.TEXT, length = 9)
// Note ljacqu 20151228: Wordpress is actually a salted algorithm but salt generation is handled internally
// and isn't exposed to the outside, so we treat it as an unsalted implementation
public class WORDPRESS extends UnsaltedMethod {
private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private final SecureRandom randomGen = new SecureRandom(); private final SecureRandom randomGen = new SecureRandom();
@ -102,16 +110,15 @@ public class WORDPRESS implements EncryptionMethod {
} }
@Override @Override
public String computeHash(String password, String salt, String name) public String computeHash(String password) {
throws NoSuchAlgorithmException {
byte random[] = new byte[6]; byte random[] = new byte[6];
this.randomGen.nextBytes(random); randomGen.nextBytes(random);
return crypt(password, gensaltPrivate(stringToUtf8(new String(random)))); return crypt(password, gensaltPrivate(stringToUtf8(new String(random))));
} }
@Override @Override
public boolean comparePassword(String hash, String password, public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) {
String playerName) throws NoSuchAlgorithmException { String hash = encryptedPassword.getHash();
String comparedHash = crypt(password, hash); String comparedHash = crypt(password, hash);
return comparedHash.equals(hash); return comparedHash.equals(hash);
} }

View File

@ -1,12 +1,12 @@
package fr.xephi.authme.security.crypts; 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.RECOMMENDED)
*/ public class XAUTH extends HexSaltedMethod {
public class XAUTH implements EncryptionMethod {
public static String getWhirlpool(String message) { private static String getWhirlpool(String message) {
WHIRLPOOL w = new WHIRLPOOL(); WHIRLPOOL w = new WHIRLPOOL();
byte[] digest = new byte[WHIRLPOOL.DIGESTBYTES]; byte[] digest = new byte[WHIRLPOOL.DIGESTBYTES];
w.NESSIEinit(); w.NESSIEinit();
@ -16,19 +16,23 @@ public class XAUTH implements EncryptionMethod {
} }
@Override @Override
public String computeHash(String password, String salt, String name) public String computeHash(String password, String salt, String name) {
throws NoSuchAlgorithmException {
String hash = getWhirlpool(salt + password).toLowerCase(); String hash = getWhirlpool(salt + password).toLowerCase();
int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length()); int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length());
return hash.substring(0, saltPos) + salt + hash.substring(saltPos); return hash.substring(0, saltPos) + salt + hash.substring(saltPos);
} }
@Override @Override
public boolean comparePassword(String hash, String password, public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String playerName) {
String playerName) throws NoSuchAlgorithmException { String hash = encryptedPassword.getHash();
int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length()); int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length());
String salt = hash.substring(saltPos, saltPos + 12); String saltFromHash = hash.substring(saltPos, saltPos + 12);
return hash.equals(computeHash(password, salt, "")); return hash.equals(computeHash(password, saltFromHash, null));
}
@Override
public int getSaltLength() {
return 12;
} }
} }

View File

@ -1,56 +0,0 @@
package fr.xephi.authme.security.crypts;
import fr.xephi.authme.AuthMe;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*/
public class XF implements EncryptionMethod {
@Override
public String computeHash(String password, String salt, String name)
throws NoSuchAlgorithmException {
return getSha256(getSha256(password) + regmatch("\"salt\";.:..:\"(.*)\";.:.:\"hashFunc\"", salt));
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
return hash.equals(regmatch("\"hash\";.:..:\"(.*)\";.:.:\"salt\"", salt));
}
private String getSha256(String password) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(password.getBytes());
byte byteData[] = md.digest();
StringBuilder sb = new StringBuilder();
for (byte element : byteData) {
sb.append(Integer.toString((element & 0xff) + 0x100, 16).substring(1));
}
StringBuilder hexString = new StringBuilder();
for (byte element : byteData) {
String hex = Integer.toHexString(0xff & element);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
private String regmatch(String pattern, String line) {
List<String> allMatches = new ArrayList<>();
Matcher m = Pattern.compile(pattern).matcher(line);
while (m.find()) {
allMatches.add(m.group(1));
}
return allMatches.get(0);
}
}

View File

@ -0,0 +1,15 @@
package fr.xephi.authme.security.crypts.description;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Denotes an encryption algorithm that is restricted to the ASCII charset.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AsciiRestricted {
}

View File

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

View File

@ -0,0 +1,18 @@
package fr.xephi.authme.security.crypts.description;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to mark a hash algorithm with the usage recommendation, see {@link Usage}.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Recommendation {
/** The recommendation for using the hash algorithm. */
Usage value();
}

View File

@ -0,0 +1,17 @@
package fr.xephi.authme.security.crypts.description;
/**
* The type of salt used by an encryption algorithm.
*/
public enum SaltType {
/** Random, newly generated text. */
TEXT,
/** Salt is based on the username, including variations and repetitions. */
USERNAME,
/** No salt. */
NONE
}

View File

@ -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. */
ACCEPTABLE,
/** 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
}

View File

@ -1,20 +1,18 @@
package fr.xephi.authme.task; package fr.xephi.authme.task;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import fr.xephi.authme.AuthMe; import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.auth.PlayerCache;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.output.Messages; import fr.xephi.authme.output.Messages;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.crypts.EncryptedPassword;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import java.security.NoSuchAlgorithmException;
public class ChangePasswordTask implements Runnable { public class ChangePasswordTask implements Runnable {
private final AuthMe plugin; private final AuthMe plugin;
@ -32,48 +30,40 @@ public class ChangePasswordTask implements Runnable {
@Override @Override
public void run() { public void run() {
Messages m = plugin.getMessages(); Messages m = plugin.getMessages();
try { PasswordSecurity passwordSecurity = plugin.getPasswordSecurity();
final String name = player.getName().toLowerCase();
String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, newPassword, name);
PlayerAuth auth = PlayerCache.getInstance().getAuth(name);
if (PasswordSecurity.comparePasswordWithHash(oldPassword, auth.getHash(), player.getName())) {
auth.setHash(hashNew);
if (PasswordSecurity.userSalt.containsKey(name) && PasswordSecurity.userSalt.get(name) != null) {
auth.setSalt(PasswordSecurity.userSalt.get(name));
} else {
auth.setSalt("");
}
if (!plugin.database.updatePassword(auth)) {
m.send(player, MessageKey.ERROR);
return;
}
plugin.database.updateSalt(auth);
PlayerCache.getInstance().updatePlayer(auth);
m.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS);
ConsoleLogger.info(player.getName() + " changed his password");
if (Settings.bungee)
{
final String hash = auth.getHash();
final String salt = auth.getSalt();
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable(){
@Override final String name = player.getName().toLowerCase();
public void run() { PlayerAuth auth = PlayerCache.getInstance().getAuth(name);
ByteArrayDataOutput out = ByteStreams.newDataOutput(); if (passwordSecurity.comparePassword(oldPassword, auth.getPassword(), player.getName())) {
out.writeUTF("Forward"); EncryptedPassword encryptedPassword = passwordSecurity.computeHash(newPassword, name);
out.writeUTF("ALL"); auth.setPassword(encryptedPassword);
out.writeUTF("AuthMe");
out.writeUTF("changepassword;" + name + ";" + hash + ";" + salt); if (!plugin.getDataSource().updatePassword(auth)) {
player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); m.send(player, MessageKey.ERROR);
} return;
});
}
} else {
m.send(player, MessageKey.WRONG_PASSWORD);
} }
} catch (NoSuchAlgorithmException ex) {
ConsoleLogger.showError(ex.getMessage()); PlayerCache.getInstance().updatePlayer(auth);
m.send(player, MessageKey.ERROR); m.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS);
ConsoleLogger.info(player.getName() + " changed his password");
if (Settings.bungee) {
final String hash = encryptedPassword.getHash();
final String salt = encryptedPassword.getSalt();
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable(){
@Override
public void run() {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Forward");
out.writeUTF("ALL");
out.writeUTF("AuthMe");
out.writeUTF("changepassword;" + name + ";" + hash + ";" + salt);
player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray());
}
});
}
} else {
m.send(player, MessageKey.WRONG_PASSWORD);
} }
} }
} }

View File

@ -143,13 +143,9 @@ public final class Utils {
return true; return true;
} }
if (!Settings.isForcedRegistrationEnabled) { if (!Settings.isForcedRegistrationEnabled && !plugin.getDataSource().isAuthAvailable(player.getName())) {
// TODO ljacqu 20151123: Use a getter to retrieve things from AuthMe return true;
if (!plugin.database.isAuthAvailable(player.getName())) {
return true;
}
} }
return false; return false;
} }
@ -159,15 +155,6 @@ public final class Utils {
&& (Settings.getUnrestrictedName.contains(player.getName().toLowerCase())); && (Settings.getUnrestrictedName.contains(player.getName().toLowerCase()));
} }
/**
* Method packCoords.
*
* @param x double
* @param y double
* @param z double
* @param w String
* @param pl Player
*/
public static void packCoords(double x, double y, double z, String w, final Player pl) { public static void packCoords(double x, double y, double z, String w, final Player pl) {
World theWorld; World theWorld;
if (w.equals("unavailableworld")) { if (w.equals("unavailableworld")) {

View File

@ -181,7 +181,7 @@ settings:
# Example unLoggedinGroup: NotLogged # Example unLoggedinGroup: NotLogged
unLoggedinGroup: unLoggedinGroup unLoggedinGroup: unLoggedinGroup
# possible values: MD5, SHA1, SHA256, WHIRLPOOL, XAUTH, MD5VB, PHPBB, # possible values: MD5, SHA1, SHA256, WHIRLPOOL, XAUTH, MD5VB, PHPBB,
# MYBB, IPB3, PHPFUSION, SMF, XENFORO, SALTED2MD5, JOOMLA, BCRYPT, WBB3, SHA512, # MYBB, IPB3, PHPFUSION, SMF, SALTED2MD5, JOOMLA, BCRYPT, WBB3, SHA512,
# DOUBLEMD5, PBKDF2, PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM(for developpers only) # DOUBLEMD5, PBKDF2, PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM(for developpers only)
passwordHash: SHA256 passwordHash: SHA256
# salt length for the SALTED2MD5 MD5(MD5(password)+salt) # salt length for the SALTED2MD5 MD5(MD5(password)+salt)

View File

@ -2,10 +2,12 @@ package fr.xephi.authme.command;
import fr.xephi.authme.AuthMe; import fr.xephi.authme.AuthMe;
import fr.xephi.authme.command.help.HelpProvider; import fr.xephi.authme.command.help.HelpProvider;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.output.Messages; import fr.xephi.authme.output.Messages;
import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.process.Management; import fr.xephi.authme.process.Management;
import fr.xephi.authme.security.PasswordSecurity;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.junit.Before; import org.junit.Before;
@ -32,6 +34,7 @@ public class CommandServiceTest {
private CommandMapper commandMapper; private CommandMapper commandMapper;
private HelpProvider helpProvider; private HelpProvider helpProvider;
private Messages messages; private Messages messages;
private PasswordSecurity passwordSecurity;
private CommandService commandService; private CommandService commandService;
@Before @Before
@ -40,7 +43,8 @@ public class CommandServiceTest {
commandMapper = mock(CommandMapper.class); commandMapper = mock(CommandMapper.class);
helpProvider = mock(HelpProvider.class); helpProvider = mock(HelpProvider.class);
messages = mock(Messages.class); messages = mock(Messages.class);
commandService = new CommandService(authMe, commandMapper, helpProvider, messages); passwordSecurity = mock(PasswordSecurity.class);
commandService = new CommandService(authMe, commandMapper, helpProvider, messages, passwordSecurity);
} }
@Test @Test
@ -110,9 +114,25 @@ public class CommandServiceTest {
} }
@Test @Test
@Ignore
public void shouldGetDataSource() { public void shouldGetDataSource() {
// TODO ljacqu 20151226: Cannot mock calls to fields // given
DataSource dataSource = mock(DataSource.class);
given(authMe.getDataSource()).willReturn(dataSource);
// when
DataSource result = commandService.getDataSource();
// then
assertThat(result, equalTo(dataSource));
}
@Test
public void shouldGetPasswordSecurity() {
// given/when
PasswordSecurity passwordSecurity = commandService.getPasswordSecurity();
// then
assertThat(passwordSecurity, equalTo(this.passwordSecurity));
} }
@Test @Test

View File

@ -0,0 +1,61 @@
package fr.xephi.authme.security;
import fr.xephi.authme.security.crypts.EncryptedPassword;
import fr.xephi.authme.security.crypts.EncryptionMethod;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.util.StringUtils;
import fr.xephi.authme.util.WrapperMock;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
* Integration test for {@link HashAlgorithm}.
*/
public class HashAlgorithmIntegrationTest {
@BeforeClass
public static void setUpWrapper() {
WrapperMock.createInstance();
Settings.bCryptLog2Rounds = 8;
Settings.saltLength = 16;
}
@Test
public void shouldHaveUniqueClassForEntries() {
// given
Set<Class<? extends EncryptionMethod>> classes = new HashSet<>();
// when / then
for (HashAlgorithm algorithm : HashAlgorithm.values()) {
if (!HashAlgorithm.CUSTOM.equals(algorithm)) {
if (classes.contains(algorithm.getClazz())) {
fail("Found class '" + algorithm.getClazz() + "' twice!");
}
classes.add(algorithm.getClazz());
}
}
}
@Test
public void shouldBeAbleToInstantiateEncryptionAlgorithms() throws InstantiationException, IllegalAccessException {
// given / when / then
for (HashAlgorithm algorithm : HashAlgorithm.values()) {
if (!HashAlgorithm.CUSTOM.equals(algorithm)) {
EncryptionMethod method = algorithm.getClazz().newInstance();
EncryptedPassword encryptedPassword = method.computeHash("pwd", "name");
assertThat("Salt should not be null if method.hasSeparateSalt(), and vice versa. Method: '"
+ method + "'", StringUtils.isEmpty(encryptedPassword.getSalt()), equalTo(!method.hasSeparateSalt()));
assertThat("Hash should not be empty for method '" + method + "'",
StringUtils.isEmpty(encryptedPassword.getHash()), equalTo(false));
}
}
}
}

View File

@ -0,0 +1,116 @@
package fr.xephi.authme.security;
import org.junit.Test;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
/**
* Test for {@link HashUtils}.
*/
public class HashUtilsTest {
/**
* List of passwords whose hash is provided to the class to test against.
*/
public static final String[] GIVEN_PASSWORDS = {"", "password", "PassWord1", "&^%te$t?Pw@_"};
@Test
public void shouldHashMd5() {
// given
String[] correctHashes = {
"d41d8cd98f00b204e9800998ecf8427e", // empty string
"5f4dcc3b5aa765d61d8327deb882cf99", // password
"f2126d405f46ed603ff5b2950f062c96", // PassWord1
"0833dcd2bc741f90c46bbac5498fd08f" // &^%te$t?Pw@_
};
// when
List<String> result = new ArrayList<>();
for (String password : GIVEN_PASSWORDS) {
result.add(HashUtils.md5(password));
}
// then
assertThat(result, contains(correctHashes));
}
@Test
public void shouldHashSha1() {
// given
String[] correctHashes = {
"da39a3ee5e6b4b0d3255bfef95601890afd80709", // empty string
"5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", // password
"285d0c707f9644b75e1a87a62f25d0efb56800f0", // PassWord1
"a42ef8e61e890af80461ca5dcded25cbfcf407a4" // &^%te$t?Pw@_
};
// when
List<String> result = new ArrayList<>();
for (String password : GIVEN_PASSWORDS) {
result.add(HashUtils.sha1(password));
}
// then
assertThat(result, contains(correctHashes));
}
@Test
public void shouldHashSha256() {
// given
String[] correctHashes = {
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", // empty string
"5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8", // password
"c04265d72b749debd67451c083785aa572742e3222e86884de16485fa14b55e7", // PassWord1
"005e3d7439d3e9a60a9d74aa1c763b36bfebec8e434ab6c5efab3df37eb2dae6" // &^%te$t?Pw@_
};
// when
List<String> result = new ArrayList<>();
for (String password : GIVEN_PASSWORDS) {
result.add(HashUtils.sha256(password));
}
// then
assertThat(result, contains(correctHashes));
}
@Test
public void shouldHashSha512() {
// given
String[] correctHashes = {
"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", // empty string
"b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86", // password
"ae9942149995a8171391625b36da134d5e288c721650d7c8d2d464fb49a49f3f551e4916ab1e097d9dd1201b01d69b1dccdefa3d2524a66092fb61b3df6e7e71", // PassWord1
"8c4f3df78db191142d819a72c16058b9e1ea41ae9b1649e1184eb89e30344c51c9c71039c483cf2f1b76b51480d8459d7eb3cfbaa24b07f2041d1551af4ead75" // &^%te$t?Pw@_
};
// when
List<String> result = new ArrayList<>();
for (String password : GIVEN_PASSWORDS) {
result.add(HashUtils.sha512(password));
}
// then
assertThat(result, contains(correctHashes));
}
@Test
public void shouldRetrieveMd5Instance() {
// given
MessageDigestAlgorithm algorithm = MessageDigestAlgorithm.MD5;
// when
MessageDigest digest = HashUtils.getDigest(algorithm);
// then
assertThat(digest.getAlgorithm(), equalTo("MD5"));
}
}

View File

@ -0,0 +1,224 @@
package fr.xephi.authme.security;
import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.PasswordEncryptionEvent;
import fr.xephi.authme.security.crypts.EncryptedPassword;
import fr.xephi.authme.security.crypts.EncryptionMethod;
import fr.xephi.authme.security.crypts.JOOMLA;
import fr.xephi.authme.security.crypts.PHPBB;
import org.bukkit.event.Event;
import org.bukkit.plugin.PluginManager;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Test for {@link PasswordSecurity}.
*/
public class PasswordSecurityTest {
private PluginManager pluginManager;
private DataSource dataSource;
private EncryptionMethod method;
private Class<?> caughtClassInEvent;
@Before
public void setUpMocks() {
pluginManager = mock(PluginManager.class);
dataSource = mock(DataSource.class);
method = mock(EncryptionMethod.class);
caughtClassInEvent = null;
// When the password encryption event is emitted, replace the encryption method with our mock.
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
Object[] arguments = invocation.getArguments();
if (arguments[0] instanceof PasswordEncryptionEvent) {
PasswordEncryptionEvent event = (PasswordEncryptionEvent) arguments[0];
caughtClassInEvent = event.getMethod() != null ? event.getMethod().getClass() : null;
event.setMethod(method);
}
return null;
}
}).when(pluginManager).callEvent(any(Event.class));
}
@Test
public void shouldReturnPasswordMatch() {
// given
EncryptedPassword password = new EncryptedPassword("$TEST$10$SOME_HASH", null);
String playerName = "Tester";
String clearTextPass = "myPassTest";
PlayerAuth auth = mock(PlayerAuth.class);
given(auth.getPassword()).willReturn(password);
given(dataSource.getAuth(playerName)).willReturn(auth);
given(method.comparePassword(clearTextPass, password, playerName)).willReturn(true);
PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.BCRYPT, pluginManager, false);
// when
boolean result = security.comparePassword(clearTextPass, playerName);
// then
assertThat(result, equalTo(true));
verify(dataSource).getAuth(playerName);
verify(pluginManager).callEvent(any(PasswordEncryptionEvent.class));
verify(method).comparePassword(clearTextPass, password, playerName);
}
@Test
public void shouldReturnPasswordMismatch() {
// given
EncryptedPassword password = new EncryptedPassword("$TEST$10$SOME_HASH", null);
String playerName = "My_PLayer";
String clearTextPass = "passw0Rd1";
PlayerAuth auth = mock(PlayerAuth.class);
given(auth.getPassword()).willReturn(password);
given(dataSource.getAuth(playerName)).willReturn(auth);
given(method.comparePassword(clearTextPass, password, playerName)).willReturn(false);
PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.CUSTOM, pluginManager, false);
// when
boolean result = security.comparePassword(clearTextPass, playerName);
// then
assertThat(result, equalTo(false));
verify(dataSource).getAuth(playerName);
verify(pluginManager).callEvent(any(PasswordEncryptionEvent.class));
verify(method).comparePassword(clearTextPass, password, playerName);
}
@Test
public void shouldReturnFalseIfPlayerDoesNotExist() {
// given
String playerName = "bobby";
String clearTextPass = "tables";
given(dataSource.getAuth(playerName)).willReturn(null);
PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.MD5, pluginManager, false);
// when
boolean result = security.comparePassword(clearTextPass, playerName);
// then
assertThat(result, equalTo(false));
verify(dataSource).getAuth(playerName);
verify(pluginManager, never()).callEvent(any(Event.class));
verify(method, never()).comparePassword(anyString(), any(EncryptedPassword.class), anyString());
}
@Test
public void shouldTryOtherMethodsForFailedPassword() {
// given
// BCRYPT2Y hash for "Test"
EncryptedPassword password =
new EncryptedPassword("$2y$10$2e6d2193f43501c926e25elvWlPmWczmrfrnbZV0dUZGITjYjnkkW");
String playerName = "somePlayer";
String clearTextPass = "Test";
// MD5 hash for "Test"
EncryptedPassword newPassword = new EncryptedPassword("0cbc6611f5540bd0809a388dc95a615b");
PlayerAuth auth = mock(PlayerAuth.class);
doCallRealMethod().when(auth).getPassword();
doCallRealMethod().when(auth).setPassword(any(EncryptedPassword.class));
auth.setPassword(password);
given(dataSource.getAuth(playerName)).willReturn(auth);
given(method.comparePassword(clearTextPass, password, playerName)).willReturn(false);
given(method.computeHash(clearTextPass, playerName)).willReturn(newPassword);
PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.MD5, pluginManager, true);
// when
boolean result = security.comparePassword(clearTextPass, playerName);
// then
assertThat(result, equalTo(true));
verify(dataSource, times(2)).getAuth(playerName);
verify(pluginManager, times(2)).callEvent(any(PasswordEncryptionEvent.class));
verify(method).comparePassword(clearTextPass, password, playerName);
verify(auth).setPassword(newPassword);
ArgumentCaptor<PlayerAuth> captor = ArgumentCaptor.forClass(PlayerAuth.class);
verify(dataSource).updatePassword(captor.capture());
assertThat(captor.getValue().getPassword(), equalTo(newPassword));
}
@Test
public void shouldHashPassword() {
// given
String password = "MyP@ssword";
String username = "theUserInTest";
EncryptedPassword encryptedPassword = new EncryptedPassword("$T$est#Hash", "__someSalt__");
given(method.computeHash(password, username)).willReturn(encryptedPassword);
PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.JOOMLA, pluginManager, true);
// when
EncryptedPassword result = security.computeHash(password, username);
// then
assertThat(result, equalTo(encryptedPassword));
ArgumentCaptor<PasswordEncryptionEvent> captor = ArgumentCaptor.forClass(PasswordEncryptionEvent.class);
verify(pluginManager).callEvent(captor.capture());
PasswordEncryptionEvent event = captor.getValue();
assertThat(JOOMLA.class.equals(caughtClassInEvent), equalTo(true));
assertThat(event.getPlayerName(), equalTo(username));
}
@Test
public void shouldHashPasswordWithGivenAlgorithm() {
// given
String password = "TopSecretPass#112525";
String username = "someone12";
EncryptedPassword encryptedPassword = new EncryptedPassword("~T!est#Hash", "__someSalt__");
given(method.computeHash(password, username)).willReturn(encryptedPassword);
PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.JOOMLA, pluginManager, true);
// when
EncryptedPassword result = security.computeHash(HashAlgorithm.PHPBB, password, username);
// then
assertThat(result, equalTo(encryptedPassword));
ArgumentCaptor<PasswordEncryptionEvent> captor = ArgumentCaptor.forClass(PasswordEncryptionEvent.class);
verify(pluginManager).callEvent(captor.capture());
PasswordEncryptionEvent event = captor.getValue();
assertThat(PHPBB.class.equals(caughtClassInEvent), equalTo(true));
assertThat(event.getPlayerName(), equalTo(username));
}
@Test
public void shouldSkipCheckIfMandatorySaltIsUnavailable() {
// given
String password = "?topSecretPass\\";
String username = "someone12";
EncryptedPassword encryptedPassword = new EncryptedPassword("~T!est#Hash");
given(method.computeHash(password, username)).willReturn(encryptedPassword);
given(method.hasSeparateSalt()).willReturn(true);
PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.XAUTH, pluginManager, true);
// when
boolean result = security.comparePassword(password, encryptedPassword, username);
// then
assertThat(result, equalTo(false));
verify(dataSource, never()).getAuth(anyString());
verify(pluginManager).callEvent(any(PasswordEncryptionEvent.class));
verify(method, never()).comparePassword(anyString(), any(EncryptedPassword.class), anyString());
}
}

View File

@ -0,0 +1,55 @@
package fr.xephi.authme.security;
import org.junit.Test;
import java.util.regex.Pattern;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
/**
* Test for {@link RandomString}.
*/
public class RandomStringTest {
@Test
public void shouldGenerateRandomStrings() {
// given
int[] lengths = {0, 1, 19, 142, 1872};
Pattern badChars = Pattern.compile(".*[^0-9a-z].*");
// when / then
for (int length : lengths) {
String result = RandomString.generate(length);
assertThat("Result '" + result + "' should have length " + length,
result.length(), equalTo(length));
assertThat("Result '" + result + "' should only have characters a-z, 0-9",
badChars.matcher(result).matches(), equalTo(false));
}
}
@Test
public void shouldGenerateRandomHexString() {
// given
int[] lengths = {0, 1, 21, 160, 1784};
Pattern badChars = Pattern.compile(".*[^0-9a-f].*");
// when / then
for (int length : lengths) {
String result = RandomString.generateHex(length);
assertThat("Result '" + result + "' should have length " + length,
result.length(), equalTo(length));
assertThat("Result '" + result + "' should only have characters a-f, 0-9",
badChars.matcher(result).matches(), equalTo(false));
}
}
@Test(expected = IllegalArgumentException.class)
public void shouldThrowForInvalidLength() {
// given/when
RandomString.generate(-3);
// then - throw exception
}
}

View File

@ -1,13 +1,16 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.PasswordSecurity; import com.google.common.collect.ImmutableList;
import fr.xephi.authme.security.crypts.description.AsciiRestricted;
import org.junit.Test; import org.junit.Test;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
@ -27,30 +30,48 @@ public abstract class AbstractEncryptionMethodTest {
* List of passwords that are hashed at runtime and then tested against; this verifies that hashes that are * List of passwords that are hashed at runtime and then tested against; this verifies that hashes that are
* generated are valid. * generated are valid.
*/ */
private static final String[] INTERNAL_PASSWORDS = {"test1234", "Ab_C73", "(!#&$~`_-Aa0", "Ûïé1&?+A"}; private static final List<String> INTERNAL_PASSWORDS =
ImmutableList.of("test1234", "Ab_C73", "(!#&$~`_-Aa0", "Ûïé1&?+A");
/** The encryption method to test. */ /** The encryption method to test. */
private EncryptionMethod method; private EncryptionMethod method;
/** Map with the hashes against which the entries in GIVEN_PASSWORDS are tested. */ /** Map with the hashes against which the entries in GIVEN_PASSWORDS are tested. */
private Map<String, String> hashes; private Map<String, EncryptedPassword> hashes;
/** /**
* Create a new test for the given encryption method. * Create a new test for the given encryption method.
* *
* @param method The encryption method to test * @param method The encryption method to test
* @param hash0 The pre-generated hash for the first {@link #GIVEN_PASSWORDS} * @param computedHashes The pre-generated hashes for the elements in {@link #GIVEN_PASSWORDS}
* @param hash1 The pre-generated hash for the second {@link #GIVEN_PASSWORDS}
* @param hash2 The pre-generated hash for the third {@link #GIVEN_PASSWORDS}
* @param hash3 The pre-generated hash for the fourth {@link #GIVEN_PASSWORDS}
*/ */
public AbstractEncryptionMethodTest(EncryptionMethod method, String hash0, String hash1, public AbstractEncryptionMethodTest(EncryptionMethod method, String... computedHashes) {
String hash2, String hash3) { if (method.hasSeparateSalt()) {
throw new UnsupportedOperationException("Test must be initialized with EncryptedPassword objects if "
+ "the salt is stored separately. Use the other constructor");
} else if (computedHashes.length != GIVEN_PASSWORDS.length) {
throw new UnsupportedOperationException("Expected " + GIVEN_PASSWORDS.length + " hashes");
}
this.method = method; this.method = method;
hashes = new HashMap<>(); hashes = new HashMap<>();
hashes.put(GIVEN_PASSWORDS[0], hash0); for (int i = 0; i < GIVEN_PASSWORDS.length; ++i) {
hashes.put(GIVEN_PASSWORDS[1], hash1); hashes.put(GIVEN_PASSWORDS[i], new EncryptedPassword(computedHashes[i]));
hashes.put(GIVEN_PASSWORDS[2], hash2); }
hashes.put(GIVEN_PASSWORDS[3], hash3); }
public AbstractEncryptionMethodTest(EncryptionMethod method, EncryptedPassword result0, EncryptedPassword result1,
EncryptedPassword result2, EncryptedPassword result3) {
if (!method.hasSeparateSalt()) {
throw new UnsupportedOperationException("Salt is not stored separately, so test should be initialized"
+ " with the password hashes only. Use the other constructor");
}
this.method = method;
hashes = new HashMap<>();
hashes.put(GIVEN_PASSWORDS[0], result0);
hashes.put(GIVEN_PASSWORDS[1], result1);
hashes.put(GIVEN_PASSWORDS[2], result2);
hashes.put(GIVEN_PASSWORDS[3], result3);
} }
@Test @Test
@ -75,31 +96,36 @@ public abstract class AbstractEncryptionMethodTest {
@Test @Test
public void testPasswordEquality() { public void testPasswordEquality() {
for (String password : INTERNAL_PASSWORDS) { List<String> internalPasswords = method.getClass().isAnnotationPresent(AsciiRestricted.class)
try { ? INTERNAL_PASSWORDS.subList(0, INTERNAL_PASSWORDS.size() - 1)
String hash = method.computeHash(password, getSalt(method), USERNAME); : INTERNAL_PASSWORDS;
assertTrue("Generated hash for '" + password + "' should match password (hash = '" + hash + "')",
method.comparePassword(hash, password, USERNAME)); for (String password : internalPasswords) {
if (!password.equals(password.toLowerCase())) { final String salt = method.generateSalt();
assertFalse("Lower-case of '" + password + "' should not match generated hash '" + hash + "'", final String hash = method.computeHash(password, salt, USERNAME);
method.comparePassword(hash, password.toLowerCase(), USERNAME)); EncryptedPassword encryptedPassword = new EncryptedPassword(hash, salt);
}
if (!password.equals(password.toUpperCase())) { // Check that the computeHash(password, salt, name) method has the same output for the returned salt
assertFalse("Upper-case of '" + password + "' should not match generated hash '" + hash + "'", if (testHashEqualityForSameSalt()) {
method.comparePassword(hash, password.toUpperCase(), USERNAME)); assertThat("Computing a hash with the same salt will generate the same hash",
} hash, equalTo(method.computeHash(password, salt, USERNAME)));
} catch (NoSuchAlgorithmException e) { }
throw new IllegalStateException("EncryptionMethod '" + method + "' threw exception", e);
assertTrue("Generated hash for '" + password + "' should match password (hash = '" + hash + "')",
method.comparePassword(password, encryptedPassword, USERNAME));
if (!password.equals(password.toLowerCase())) {
assertFalse("Lower-case of '" + password + "' should not match generated hash '" + hash + "'",
method.comparePassword(password.toLowerCase(), encryptedPassword, USERNAME));
}
if (!password.equals(password.toUpperCase())) {
assertFalse("Upper-case of '" + password + "' should not match generated hash '" + hash + "'",
method.comparePassword(password.toUpperCase(), encryptedPassword, USERNAME));
} }
} }
} }
private boolean doesGivenHashMatch(String password, EncryptionMethod method) { private boolean doesGivenHashMatch(String password, EncryptionMethod method) {
try { return method.comparePassword(password, hashes.get(password), USERNAME);
return method.comparePassword(hashes.get(password), password, USERNAME);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("EncryptionMethod '" + method + "' threw exception", e);
}
} }
// @org.junit.Test public void a() { AbstractEncryptionMethodTest.generateTest(); } // @org.junit.Test public void a() { AbstractEncryptionMethodTest.generateTest(); }
@ -116,43 +142,30 @@ public abstract class AbstractEncryptionMethodTest {
if (password.equals(GIVEN_PASSWORDS[GIVEN_PASSWORDS.length - 1])) { if (password.equals(GIVEN_PASSWORDS[GIVEN_PASSWORDS.length - 1])) {
delim = "); "; delim = "); ";
} }
try {
System.out.println("\t\t\"" + method.computeHash(password, getSalt(method), USERNAME) if (method.hasSeparateSalt()) {
EncryptedPassword encryptedPassword = method.computeHash(password, USERNAME);
System.out.println(String.format("\t\tnew EncryptedPassword(\"%s\", \"%s\")%s// %s",
encryptedPassword.getHash(), encryptedPassword.getSalt(), delim, password));
} else {
System.out.println("\t\t\"" + method.computeHash(password, USERNAME).getHash()
+ "\"" + delim + "// " + password); + "\"" + delim + "// " + password);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Could not generate hash", e);
} }
} }
System.out.println("\t}"); System.out.println("\t}");
System.out.println("\n}"); System.out.println("\n}");
} }
// TODO #358: Remove this method and use the new salt method on the interface /**
private static String getSalt(EncryptionMethod method) { * Return whether an encryption algorithm should be tested that it generates the same
try { * hash for the same salt. If {@code true}, we call {@link EncryptionMethod#computeHash(String, String)}
if (method instanceof BCRYPT) { * and verify that {@link EncryptionMethod#computeHash(String, String, String)} generates
return BCRYPT.gensalt(); * the same hash for the salt returned in the first call.
} else if (method instanceof MD5 || method instanceof WORDPRESS || method instanceof SMF *
|| method instanceof SHA512 || method instanceof SHA1 || method instanceof ROYALAUTH * @return Whether or not to test that the hash is the same for the same salt
|| method instanceof DOUBLEMD5) { */
return ""; protected boolean testHashEqualityForSameSalt() {
} else if (method instanceof JOOMLA || method instanceof SALTEDSHA512) { return true;
return PasswordSecurity.createSalt(32);
} else if (method instanceof SHA256 || method instanceof PHPBB || method instanceof WHIRLPOOL
|| method instanceof MD5VB || method instanceof BCRYPT2Y) {
return PasswordSecurity.createSalt(16);
} else if (method instanceof WBB3) {
return PasswordSecurity.createSalt(40);
} else if (method instanceof XAUTH || method instanceof CryptPBKDF2Django
|| method instanceof CryptPBKDF2) {
return PasswordSecurity.createSalt(12);
} else if (method instanceof WBB4) {
return BCRYPT.gensalt(8);
}
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
throw new IllegalStateException("Unknown EncryptionMethod for salt generation");
} }
} }

View File

@ -1,23 +1,16 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.PasswordSecurity;
import org.junit.Ignore;
import org.junit.Test;
import java.security.NoSuchAlgorithmException;
/** /**
* Test for {@link BCRYPT2Y}. * Test for {@link BCRYPT2Y}.
*/ */
@Ignore public class BCRYPT2YTest extends AbstractEncryptionMethodTest {
// TODO #369: Fix hash & add standard test
public class BCRYPT2YTest {
@Test public BCRYPT2YTest() {
public void shouldCreateHash() throws NoSuchAlgorithmException { super(new BCRYPT2Y(),
String salt = PasswordSecurity.createSalt(16); // As defined in PasswordSecurity "$2y$10$da641e404b982edf1c7c0uTU9BcKzfA2vWKV05q6r.dCvm/93wqVK", // password
EncryptionMethod method = new BCRYPT2Y(); "$2y$10$e52c48a76f5b86f5da899uiK/HYocyPsfQXESNbP278rIz08LKEP2", // PassWord1
System.out.println(method.computeHash("password", salt, "testPlayer")); "$2y$10$be6f11548dc5fb4088410ONdC0dXnJ04y1RHcJh5fVF3XK5d.qgqK", // &^%te$t?Pw@_
"$2y$10$a8097db1fa4423b93f1b2eF6rMAGFkSX178fpROf/OvCFtrDebp6K"); // âË_3(íù*
} }
} }

View File

@ -1,10 +1,20 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.util.WrapperMock;
import org.junit.Before;
/** /**
* Test for {@link BCRYPT}. * Test for {@link BCRYPT}.
*/ */
public class BcryptTest extends AbstractEncryptionMethodTest { public class BcryptTest extends AbstractEncryptionMethodTest {
@Before
public void setUpSettings() {
WrapperMock.createInstance();
Settings.bCryptLog2Rounds = 8;
}
public BcryptTest() { public BcryptTest() {
super(new BCRYPT(), super(new BCRYPT(),
"$2a$10$6iATmYgwJVc3YONhVcZFve3Cfb5GnwvKhJ20r.hMjmcNkIT9.Uh9K", // password "$2a$10$6iATmYgwJVc3YONhVcZFve3Cfb5GnwvKhJ20r.hMjmcNkIT9.Uh9K", // password

View File

@ -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(íù*
}
}

View File

@ -1,12 +1,8 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import org.junit.Ignore;
/** /**
* Test for {@link CryptPBKDF2Django}. * Test for {@link CryptPBKDF2Django}.
*/ */
@Ignore
// TODO ljacqu 20151220: testPasswordEquality fails - password matches hash for uppercase password...?
public class CryptPBKDF2DjangoTest extends AbstractEncryptionMethodTest { public class CryptPBKDF2DjangoTest extends AbstractEncryptionMethodTest {
public CryptPBKDF2DjangoTest() { public CryptPBKDF2DjangoTest() {

View File

@ -0,0 +1,16 @@
package fr.xephi.authme.security.crypts;
/**
* Test for {@link IPB3}.
*/
public class IPB3Test extends AbstractEncryptionMethodTest {
public IPB3Test() {
super(new IPB3(),
new EncryptedPassword("f8ecea1ce42b5babef369ff7692dbe3f", "1715b"), //password
new EncryptedPassword("40a93731a931352e0619cdf09b975040", "ba91c"), //PassWord1
new EncryptedPassword("a77ca982373946d5800430bd2947ba11", "a7725"), //&^%te$t?Pw@_
new EncryptedPassword("383d7b9e2b707d6e894ec7b30e3032c3", "fa9fd")); //âË_3(íù*
}
}

View File

@ -0,0 +1,16 @@
package fr.xephi.authme.security.crypts;
/**
* Test for {@link MYBB}.
*/
public class MYBBTest extends AbstractEncryptionMethodTest {
public MYBBTest() {
super(new MYBB(),
new EncryptedPassword("57c7a16d860833db5030738f5a465d2b", "acdc14e6"), //password
new EncryptedPassword("08fbdf721f2c42d9780b7d66df0ba830", "792fd7fb"), //PassWord1
new EncryptedPassword("d602f38fb59ad9e185d5604f5d4ddb36", "4b5534a4"), //&^%te$t?Pw@_
new EncryptedPassword("b3c39410d0ab8ae2a65c257820797fad", "e5a6cb14")); //âË_3(íù*
}
}

View File

@ -7,11 +7,10 @@ public class Md5Test extends AbstractEncryptionMethodTest {
public Md5Test() { public Md5Test() {
super(new MD5(), super(new MD5(),
"5f4dcc3b5aa765d61d8327deb882cf99", // password "5f4dcc3b5aa765d61d8327deb882cf99", // password
"f2126d405f46ed603ff5b2950f062c96", // PassWord1 "f2126d405f46ed603ff5b2950f062c96", // PassWord1
"0833dcd2bc741f90c46bbac5498fd08f", // &^%te$t?Pw@_ "0833dcd2bc741f90c46bbac5498fd08f", // &^%te$t?Pw@_
"d1accd961cb7b688c87278191c1dfed3" // âË_3(íù* "d1accd961cb7b688c87278191c1dfed3"); // âË_3(íù*
);
} }
} }

View File

@ -0,0 +1,16 @@
package fr.xephi.authme.security.crypts;
/**
* Test for {@link PHPFUSION}.
*/
public class PHPFUSIONTest extends AbstractEncryptionMethodTest {
public PHPFUSIONTest() {
super(new PHPFUSION(),
new EncryptedPassword("f7a606c4eb3fcfbc382906476e05b06f21234a77d1a4eacc0f93f503deb69e70", "6cd1c97c55cb"), // password
new EncryptedPassword("8a9b7bb706a3347e5f684a7cb905bfb26b9a0d099358064139ab3ed1a66aeb2b", "d6012370b73f"), // PassWord1
new EncryptedPassword("43f2f23f44c8f89e2dbf06050bc8c77dbcdf71a7b5d28c87ec657d474e63d62d", "f75400a209a4"), // &^%te$t?Pw@_
new EncryptedPassword("4e7f4eb7e3653d7460f1cf590def4153c6fcdf8b8e16fb95538fdf9e54a95245", "d552e0f5b23a")); // âË_3(íù*
}
}

View File

@ -0,0 +1,26 @@
package fr.xephi.authme.security.crypts;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.util.WrapperMock;
import org.junit.Before;
/**
* Test for {@link SALTED2MD5}.
*/
public class SALTED2MD5Test extends AbstractEncryptionMethodTest {
@Before
public void setUpAlgorithm() {
WrapperMock.createInstance();
Settings.saltLength = 8;
}
public SALTED2MD5Test() {
super(new SALTED2MD5(),
new EncryptedPassword("9f3d13dc01a6fe61fd669954174399f3", "9b5f5749"), // password
new EncryptedPassword("b28c32f624a4eb161d6adc9acb5bfc5b", "f750ba32"), // PassWord1
new EncryptedPassword("38dcb83cc68424afe3cda012700c2bb1", "eb2c3394"), // &^%te$t?Pw@_
new EncryptedPassword("ad25606eae5b760c8a2469d65578ac39", "04eee598")); // âË_3(íù*)
}
}

View File

@ -1,20 +1,16 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import org.junit.Ignore;
/** /**
* Test for {@link SALTEDSHA512}. * Test for {@link SALTEDSHA512}.
*/ */
@Ignore
// TODO ljacqu 20151220: Currently cannot test because of closely coupled database call inside of class
public class SALTEDSHA512Test extends AbstractEncryptionMethodTest { public class SALTEDSHA512Test extends AbstractEncryptionMethodTest {
public SALTEDSHA512Test() { public SALTEDSHA512Test() {
super(new SALTEDSHA512(), super(new SALTEDSHA512(),
"c8efe95e1ab02d9a0e7c7d11d4ac3cc068a8405b5810aac3a1b8b01927ab059563438131dc995156739daf74db40ffdc79b78f6aec9b2a468fe106b88c66c204", // password new EncryptedPassword("dea7a37cecf5384ae8e347fd1411efb51364b6ba1b328695de3b354612c1d7010807e8b7051c40f740e498490e1f133e2c2408327d13fbdd68e1b1f6d548e624", "29f8a3c52147f987fee7ba3e0fb311bd"), // password
"74c61af1bcbb3293cdc0959c7323d50be28c167eddc7a1b7eb029e38263c2cfb6eb090f41370a65249752aa316fa851091c2bd8420302e87d383529beea735b4", // PassWord1 new EncryptedPassword("7c06225aac574d2dc7c81a2ed306637adf025715f52083e05bdab014faaa234e24a97d0e69ea0108dfa77cc9228e58be319ee677e679b5d1ad168d40e50a42f6", "8ea37b85d020b98f60c0fe9b8ec9296c"), // PassWord1
"08eefcca4a17876441ebe61a02e8bc62cab7502dd87f8ec3b7f82edb2adace791b8dad31e74c5513cf99be502b732f5c5efffb239f4590d5c600d066a7037908", // &^%te$t?Pw@_ new EncryptedPassword("55711adbe03c9616f3505f0d57077fdd528c32243eb6f9840c1a6ff9e553940d6b89790750ebd52ebda63ca793fbe9980d54057af40836820c648750fe22d49c", "9f58079631ef21d32b4710694f1f461b"), // &^%te$t?Pw@_
"a122490c4c7c18ad665b5ac9617c948741468a787a2ba42c6fd2530ea1d7874681b8575ee9a8907c42ff65dac69e4ada2852789759c17d51865ca915b259a65a"); // âË_3(íù* new EncryptedPassword("29dc5be8702975ea4563ed3de5b145e2d2f1c37ae661bbe0d3e94d964402cf09d539d65f3b90ff6921ea3d40727f76fb38fb34d1e5c2d62238c4e0203efc372f", "048bb76168265d906f1fd1f81d0616a9")); // âË_3(íù*
} }
} }

View File

@ -1,20 +1,16 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import org.junit.Ignore;
/** /**
* Test for {@link WBB3}. * Test for {@link WBB3}.
*/ */
@Ignore
// TODO #364 ljacqu 20151220: Unignore test after fixing closely coupled DB dependency
public class WBB3Test extends AbstractEncryptionMethodTest { public class WBB3Test extends AbstractEncryptionMethodTest {
public WBB3Test() { public WBB3Test() {
super(new WBB3(), super(new WBB3(),
"ca426c4d20a82cd24c7bb07d94d69f3757e3d07d", // password new EncryptedPassword("8df818ef7d56075ab2744f74b98ad68a375ccac4", "b7415b355492ea60314f259a35733a3092c03e3f"), // password
"72d59d27674a3cace2600ff152ba8b46274e27e9", // PassWord1 new EncryptedPassword("106da5cf5df92cb845e12cf62cbdb5235b6dc693", "6110f19b2b52910dccf592a19c59126873f42e69"), // PassWord1
"23daf26602e52591156968a14c2a6592b5be4743", // &^%te$t?Pw@_ new EncryptedPassword("940a9fb7acec0178c6691e8b3c14bd7d789078b1", "f9dd501ff3d1bf74904f9e89649e378429af56e7"), // &^%te$t?Pw@_
"d3908efe4a15314066391dd8572883c70b16fd8a"); // âË_3(íù* new EncryptedPassword("0fa12e8d96c9e95f73aa91f3b76f8cdc815ec8a5", "736be8669f6159ddb2d5b47a3e6428cdb8b324de")); // âË_3(íù*
} }
} }

View File

@ -12,4 +12,10 @@ public class WORDPRESSTest extends AbstractEncryptionMethodTest {
"$P$BjzPjjzPjrAOyB1V0WFdpisgCTFx.N/", // &^%te$t?Pw@_ "$P$BjzPjjzPjrAOyB1V0WFdpisgCTFx.N/", // &^%te$t?Pw@_
"$P$BjzPjxxyjp2QdKcab/oTW8l/W0AgE21"); // âË_3(íù* "$P$BjzPjxxyjp2QdKcab/oTW8l/W0AgE21"); // âË_3(íù*
} }
@Override
protected boolean testHashEqualityForSameSalt() {
// We need to skip the test because Wordpress uses an "internal salt" that is not exposed to the outside
return false;
}
} }