diff --git a/README.md b/README.md index fb23fd75..f98ce8bb 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,6 @@ typing commands or using the inventory. It can also kick players with uncommonly
Team members: look at the team.txt file -
Credit for old version of the plugin to: d4rkwarriors, fabe1337 , Whoami2 and pomo4ka
+Credit for old version of the plugin to: d4rkwarriors, fabe1337, Whoami2 and pomo4ka
Thanks also to: AS1LV3RN1NJA, Hoeze and eprimex
diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index cae30a44..04ff2a32 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -43,6 +43,7 @@ import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.process.Management; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.OtherAccounts; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Spawn; @@ -101,12 +102,13 @@ public class AuthMe extends JavaPlugin { private Messages messages; private JsonCache playerBackup; private ModuleManager moduleManager; + private PasswordSecurity passwordSecurity; + private DataSource database; // Public Instances public NewAPI api; public SendMailSSL mail; public DataManager dataManager; - public DataSource database; public OtherAccounts otherAccounts; public Location essentialsSpawn; @@ -192,7 +194,6 @@ public class AuthMe extends JavaPlugin { /** * Method called when the server enables the plugin. - * @see org.bukkit.plugin.Plugin#onEnable() */ @Override public void onEnable() { @@ -209,12 +210,26 @@ public class AuthMe extends JavaPlugin { return; } - // Set up messages + // Set up messages & password security 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 permsMan = initializePermissionsManager(); - commandHandler = initializeCommandHandler(permsMan, messages); + commandHandler = initializeCommandHandler(permsMan, messages, passwordSecurity); // Set up the module manager setupModuleManager(); @@ -256,16 +271,7 @@ public class AuthMe extends JavaPlugin { // Do a backup on 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 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); Set- * This event is called when we need to compare or get an hash password, for set - * a custom EncryptionMethod - *
+ * This event is called when we need to compare or hash password and allows + * third-party listeners to change the encryption method. This is typically + * done with the {@link fr.xephi.authme.security.HashAlgorithm#CUSTOM} setting. * * @author Xephi59 - * @version $Revision: 1.0 $ - * @see fr.xephi.authme.security.crypts.EncryptionMethod */ public class PasswordEncryptionEvent extends Event { private static final HandlerList handlers = new HandlerList(); - private EncryptionMethod method = null; - private String playerName = ""; + private EncryptionMethod method; + private String playerName; - /** - * Constructor for PasswordEncryptionEvent. - * - * @param method EncryptionMethod - * @param playerName String - */ public PasswordEncryptionEvent(EncryptionMethod method, String playerName) { super(false); this.method = method; this.playerName = playerName; } - /** - * Method getHandlerList. - * - * @return HandlerList - */ public static HandlerList getHandlerList() { return handlers; } - /** - * Method getHandlers. - * - * @return HandlerList - */ @Override public HandlerList getHandlers() { return handlers; } - /** - * Method getMethod. - * - * @return EncryptionMethod - */ public EncryptionMethod getMethod() { return method; } - /** - * Method setMethod. - * - * @param method EncryptionMethod - */ public void setMethod(EncryptionMethod method) { this.method = method; } - /** - * Method getPlayerName. - * - * @return String - */ public String getPlayerName() { return playerName; } diff --git a/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java b/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java index f5d3e0ae..731de626 100644 --- a/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java +++ b/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java @@ -6,6 +6,8 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; 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.plugin.messaging.PluginMessageListener; @@ -24,15 +26,6 @@ public class BungeeCordMessage implements PluginMessageListener { 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 public void onPluginMessageReceived(String channel, Player player, byte[] message) { if (!channel.equals("BungeeCord")) { @@ -50,21 +43,22 @@ public class BungeeCordMessage implements PluginMessageListener { final String[] args = str.split(";"); final String act = args[0]; final String name = args[1]; + final DataSource dataSource = plugin.getDataSource(); plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { @Override public void run() { - PlayerAuth auth = plugin.database.getAuth(name); + PlayerAuth auth = dataSource.getAuth(name); if (auth == null) { return; } if ("login".equals(act)) { PlayerCache.getInstance().updatePlayer(auth); - plugin.database.setLogged(name); + dataSource.setLogged(name); ConsoleLogger.info("Player " + auth.getNickname() + " has logged in from one of your server!"); } else if ("logout".equals(act)) { PlayerCache.getInstance().removePlayer(name); - plugin.database.setUnlogged(name); + dataSource.setUnlogged(name); ConsoleLogger.info("Player " + auth.getNickname() + " has logged out from one of your server!"); } else if ("register".equals(act)) { @@ -72,11 +66,10 @@ public class BungeeCordMessage implements PluginMessageListener { + " has registered out from one of your server!"); } else if ("changepassword".equals(act)) { final String password = args[2]; - auth.setHash(password); - if (args.length == 4) - auth.setSalt(args[3]); + final String salt = args.length >= 4 ? args[3] : null; + auth.setPassword(new EncryptedPassword(password, salt)); PlayerCache.getInstance().updatePlayer(auth); - plugin.database.updatePassword(auth); + dataSource.updatePassword(auth); } } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index 701308ee..42a11b3f 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -89,7 +89,7 @@ public class AuthMePlayerListener implements Listener { plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { @Override public void run() { - if (plugin.database.isAuthAvailable(player.getName().toLowerCase())) { + if (plugin.getDataSource().isAuthAvailable(player.getName().toLowerCase())) { m.send(player, MessageKey.LOGIN_MESSAGE); } else { if (Settings.emailRegistration) { @@ -221,8 +221,9 @@ public class AuthMePlayerListener implements Listener { @EventHandler(priority = EventPriority.HIGHEST) public void onPreLogin(AsyncPlayerPreLoginEvent event) { - PlayerAuth auth = plugin.database.getAuth(event.getName()); - if (auth != null && auth.getRealName() != null && !auth.getRealName().isEmpty() && !auth.getRealName().equals("Player") && !auth.getRealName().equals(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())) { event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); event.setKickMessage("You should join using username: " + ChatColor.AQUA + auth.getRealName() + 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")) { auth.setRealName(event.getName()); - plugin.database.saveAuth(auth); + plugin.getDataSource().saveAuth(auth); } if (auth == null && Settings.enableProtection) { @@ -302,7 +303,7 @@ public class AuthMePlayerListener implements Listener { } final String name = player.getName().toLowerCase(); - boolean isAuthAvailable = plugin.database.isAuthAvailable(name); + boolean isAuthAvailable = plugin.getDataSource().isAuthAvailable(name); if (Settings.isKickNonRegisteredEnabled && !isAuthAvailable) { if (Settings.antiBotInAction) { @@ -475,9 +476,13 @@ public class AuthMePlayerListener implements Listener { Player player = event.getPlayer(); String name = player.getName().toLowerCase(); Location spawn = plugin.getSpawnLocation(player); - if (Settings.isSaveQuitLocationEnabled && plugin.database.isAuthAvailable(name)) { - PlayerAuth auth = new PlayerAuth(name, spawn.getX(), spawn.getY(), spawn.getZ(), spawn.getWorld().getName(), player.getName()); - plugin.database.updateQuitLoc(auth); + if (Settings.isSaveQuitLocationEnabled && plugin.getDataSource().isAuthAvailable(name)) { + PlayerAuth auth = PlayerAuth.builder() + .name(name) + .realName(player.getName()) + .location(spawn) + .build(); + plugin.getDataSource().updateQuitLoc(auth); } if (spawn != null && spawn.getWorld() != null) { event.setRespawnLocation(spawn); diff --git a/src/main/java/fr/xephi/authme/process/Management.java b/src/main/java/fr/xephi/authme/process/Management.java index 58ce248f..6d262308 100644 --- a/src/main/java/fr/xephi/authme/process/Management.java +++ b/src/main/java/fr/xephi/authme/process/Management.java @@ -33,7 +33,7 @@ public class Management { @Override 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 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 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 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 public void run() { - new AsynchronousQuit(player, plugin, plugin.database, isKick).process(); + new AsynchronousQuit(player, plugin, plugin.getDataSource(), isKick).process(); } }); diff --git a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java index 214b8215..7208f60a 100644 --- a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java +++ b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java @@ -40,7 +40,7 @@ public class AsyncChangeEmail { if (Settings.getmaxRegPerEmail > 0) { 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); return; } @@ -68,7 +68,7 @@ public class AsyncChangeEmail { } String old = auth.getEmail(); auth.setEmail(newEmail); - if (!plugin.database.updateEmail(auth)) { + if (!plugin.getDataSource().updateEmail(auth)) { m.send(player, MessageKey.ERROR); auth.setEmail(old); return; @@ -81,7 +81,7 @@ public class AsyncChangeEmail { } m.send(player, MessageKey.EMAIL_CHANGED_SUCCESS); } else { - if (plugin.database.isAuthAvailable(playerName)) { + if (plugin.getDataSource().isAuthAvailable(playerName)) { m.send(player, MessageKey.LOGIN_MESSAGE); } else { if (Settings.emailRegistration) { diff --git a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java index fe47be51..8297224b 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -8,7 +8,6 @@ import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent; import fr.xephi.authme.permission.PlayerPermission; -import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.RandomString; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; @@ -26,12 +25,11 @@ import java.util.List; */ public class AsynchronousLogin { - private static final RandomString rdm = new RandomString(Settings.captchaLength); - protected final Player player; - protected final String name; - protected final String realName; - protected final String password; - protected final boolean forceLogin; + private final Player player; + private final String name; + private final String realName; + private final String password; + private final boolean forceLogin; private final AuthMe plugin; private final DataSource database; private final Messages m; @@ -70,7 +68,7 @@ public class AsynchronousLogin { plugin.captcha.putIfAbsent(name, i); } 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)); return true; } @@ -120,8 +118,7 @@ public class AsynchronousLogin { 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 m.send(player, MessageKey.USERNAME_ALREADY_ONLINE_ERROR); return null; @@ -138,19 +135,19 @@ public class AsynchronousLogin { if (pAuth == null || needsCaptcha()) return; - String hash = pAuth.getHash(); String email = pAuth.getEmail(); - boolean passwordVerified = true; - if (!forceLogin) - try { - passwordVerified = PasswordSecurity.comparePasswordWithHash(password, hash, realName); - } catch (Exception ex) { - ConsoleLogger.showError(ex.getMessage()); - m.send(player, MessageKey.ERROR); - return; - } + boolean passwordVerified = forceLogin || plugin.getPasswordSecurity() + .comparePassword(password, pAuth.getPassword(), realName); + 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); if (Settings.useCaptcha) { diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java index fb102ec9..3f2cbd0e 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -8,7 +8,7 @@ import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; 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 org.bukkit.entity.Player; @@ -44,7 +44,10 @@ public class AsyncRegister { } else if (!Settings.isRegistrationEnabled) { m.send(player, MessageKey.REGISTRATION_DISABLED); 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); return false; } 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 && !plugin.getPermissionsManager().hasPermission(player, PlayerPermission.ALLOW_MULTIPLE_ACCOUNTS) && database.getAllAuthsByEmail(email).size() >= Settings.getmaxRegPerEmail) { m.send(player, MessageKey.MAX_REGISTER_EXCEEDED); return; } - final String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); - final String salt = PasswordSecurity.userSalt.get(name); + final EncryptedPassword encryptedPassword = plugin.getPasswordSecurity().computeHash(password, name); PlayerAuth auth = PlayerAuth.builder() .name(name) .realName(player.getName()) - .hash(hashNew) + .password(encryptedPassword) .ip(ip) - .locWorld(player.getLocation().getWorld().getName()) - .locX(player.getLocation().getX()) - .locY(player.getLocation().getY()) - .locZ(player.getLocation().getZ()) + .location(player.getLocation()) .email(email) - .salt(salt != null ? salt : "") .build(); if (!database.saveAuth(auth)) { @@ -122,18 +120,13 @@ public class AsyncRegister { } private void passwordRegister() throws Exception { - final String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); - final String salt = PasswordSecurity.userSalt.get(name); + final EncryptedPassword encryptedPassword = plugin.getPasswordSecurity().computeHash(password, name); PlayerAuth auth = PlayerAuth.builder() .name(name) .realName(player.getName()) - .hash(hashNew) + .password(encryptedPassword) .ip(ip) - .locWorld(player.getLocation().getWorld().getName()) - .locX(player.getLocation().getX()) - .locY(player.getLocation().getY()) - .locZ(player.getLocation().getZ()) - .salt(salt != null ? salt : "") + .location(player.getLocation()) .build(); if (!database.saveAuth(auth)) { diff --git a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java index 53c54b3e..58c17694 100644 --- a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java +++ b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java @@ -2,11 +2,11 @@ package fr.xephi.authme.process.unregister; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.backup.JsonCache; import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.cache.limbo.LimboPlayer; -import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.settings.Settings; @@ -20,16 +20,12 @@ import org.bukkit.potion.PotionEffectType; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitTask; -import java.security.NoSuchAlgorithmException; - -/** - */ public class AsynchronousUnregister { - protected final Player player; - protected final String name; - protected final String password; - protected final boolean force; + private final Player player; + private final String name; + private final String password; + private final boolean force; private final AuthMe plugin; private final Messages m; private final JsonCache playerCache; @@ -52,65 +48,59 @@ public class AsynchronousUnregister { this.playerCache = new JsonCache(); } - /** - * Method getIp. - * - * @return String - */ protected String getIp() { return plugin.getIP(player); } public void process() { - try { - if (force || PasswordSecurity.comparePasswordWithHash(password, PlayerCache.getInstance().getAuth(name).getHash(), player.getName())) { - if (!plugin.database.removeAuth(name)) { - m.send(player, MessageKey.ERROR); - return; + PlayerAuth cachedAuth = PlayerCache.getInstance().getAuth(name); + if (force || plugin.getPasswordSecurity().comparePassword( + password, cachedAuth.getPassword(), player.getName())) { + if (!plugin.getDataSource().removeAuth(name)) { + 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; - if (Settings.isForcedRegistrationEnabled) { - Utils.teleportToSpawn(player); - player.saveData(); - PlayerCache.getInstance().removePlayer(player.getName().toLowerCase()); - if (!Settings.getRegisteredGroup.isEmpty()) { - Utils.setGroup(player, GroupType.UNREGISTERED); - } - 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)); + 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"); - Utils.teleportToSpawn(player); - } else { - m.send(player, MessageKey.WRONG_PASSWORD); + return; } - } 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); } } } diff --git a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java index 315fa9fd..24a702dd 100644 --- a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java +++ b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java @@ -3,40 +3,42 @@ package fr.xephi.authme.security; 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 { - 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), - WBB3(fr.xephi.authme.security.crypts.WBB3.class), - WBB4(fr.xephi.authme.security.crypts.WBB4.class), - SHA512(fr.xephi.authme.security.crypts.SHA512.class), + BCRYPT2Y(fr.xephi.authme.security.crypts.BCRYPT2Y.class), + CRAZYCRYPT1(fr.xephi.authme.security.crypts.CRAZYCRYPT1.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), 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), - CRAZYCRYPT1(fr.xephi.authme.security.crypts.CRAZYCRYPT1.class), - BCRYPT2Y(fr.xephi.authme.security.crypts.BCRYPT2Y.class), + SALTED2MD5(fr.xephi.authme.security.crypts.SALTED2MD5.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); - final Class extends EncryptionMethod> clazz; + private final Class extends EncryptionMethod> clazz; /** * Constructor for HashAlgorithm. diff --git a/src/main/java/fr/xephi/authme/security/HashUtils.java b/src/main/java/fr/xephi/authme/security/HashUtils.java new file mode 100644 index 00000000..c4fb4edf --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/HashUtils.java @@ -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)); + } + +} diff --git a/src/main/java/fr/xephi/authme/security/MessageDigestAlgorithm.java b/src/main/java/fr/xephi/authme/security/MessageDigestAlgorithm.java new file mode 100644 index 00000000..51ff2a5d --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/MessageDigestAlgorithm.java @@ -0,0 +1,30 @@ +package fr.xephi.authme.security; + +import java.security.MessageDigest; + +/** + * The Java-supported names to get a {@link MessageDigest} instance with. + * + * @see + * Crypto Spec Appendix A: Standard Names + */ +public enum MessageDigestAlgorithm { + + MD5("MD5"), + + SHA1("SHA-1"), + + SHA256("SHA-256"), + + SHA512("SHA-512"); + + private final String key; + + MessageDigestAlgorithm(String key) { + this.key = key; + } + + public String getKey() { + return key; + } +} diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index 1ffa423d..ba1d2e02 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -1,182 +1,126 @@ package fr.xephi.authme.security; -import fr.xephi.authme.AuthMe; 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.BCRYPT; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.security.crypts.EncryptionMethod; -import fr.xephi.authme.settings.Settings; -import org.bukkit.Bukkit; - -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.HashMap; +import org.bukkit.plugin.PluginManager; /** + * Manager class for password-related operations. */ public class PasswordSecurity { - public static final HashMap
+ * 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;
+ }
+
+}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java
index f925c10d..9381113c 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java
@@ -1,35 +1,59 @@
package fr.xephi.authme.security.crypts;
-import java.security.NoSuchAlgorithmException;
-
/**
* Public interface for custom password encryption methods.
*/
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 salt The salt to add to the hash
- * @param name The player's name (sometimes required for storing the salt separately in the database)
+ * @param password The password to hash
+ * @param name The name of the player (sometimes required to generate a salt with)
*
- * @return The hashed password
+ * @return The hash result for the password.
+ * @see EncryptedPassword
*/
- String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException;
+ EncryptedPassword computeHash(String password, String name);
/**
- * 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 clear-text password to verify the hash against
- * @param playerName The player name to do the check for (sometimes required for retrieving
- * the salt from the database)
+ * @param password The password to hash
+ * @param salt The salt to add to the hash
+ * @param name The player's name (sometimes required to generate a salt with)
+ *
+ * @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
*/
- boolean comparePassword(String hash, String password, String playerName)
- throws NoSuchAlgorithmException;
+ boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name);
+
+ /**
+ * 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();
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java b/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java
new file mode 100644
index 00000000..2427e431
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java
@@ -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;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java
index 93abdaf1..85789b88 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java
@@ -1,34 +1,25 @@
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 java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import static fr.xephi.authme.security.HashUtils.md5;
-/**
- */
-public class IPB3 implements EncryptionMethod {
+@Recommendation(Usage.DO_NOT_USE)
+@HasSalt(value = SaltType.TEXT, length = 5)
+public class IPB3 extends SeparateSaltMethod {
- private static String getMD5(String message)
- throws NoSuchAlgorithmException {
- MessageDigest md5 = MessageDigest.getInstance("MD5");
- md5.reset();
- md5.update(message.getBytes());
- byte[] digest = md5.digest();
- return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
+ @Override
+ public String computeHash(String password, String salt, String name) {
+ return md5(md5(salt) + md5(password));
}
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
- return getMD5(getMD5(salt) + getMD5(password));
+ public String generateSalt() {
+ return RandomString.generateHex(5);
}
- @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));
- }
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java
index 41f6fe35..4e42958f 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java
@@ -1,32 +1,27 @@
package fr.xephi.authme.security.crypts;
-import java.math.BigInteger;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import fr.xephi.authme.security.HashUtils;
+import fr.xephi.authme.security.crypts.description.Recommendation;
+import fr.xephi.authme.security.crypts.description.Usage;
-/**
- */
-public class JOOMLA implements EncryptionMethod {
+@Recommendation(Usage.RECOMMENDED)
+public class JOOMLA extends HexSaltedMethod {
- private static String getMD5(String message)
- throws NoSuchAlgorithmException {
- MessageDigest md5 = MessageDigest.getInstance("MD5");
- md5.reset();
- md5.update(message.getBytes());
- byte[] digest = md5.digest();
- return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
+ @Override
+ public String computeHash(String password, String salt, String name) {
+ return HashUtils.md5(password + salt) + ":" + salt;
}
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
- return getMD5(password + salt) + ":" + salt;
+ public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String unusedName) {
+ String hash = encryptedPassword.getHash();
+ String[] hashParts = hash.split(":");
+ return hashParts.length == 2 && hash.equals(computeHash(password, hashParts[1], null));
}
@Override
- public boolean comparePassword(String hash, String password,
- String playerName) throws NoSuchAlgorithmException {
- String salt = hash.split(":")[1];
- return hash.equals(getMD5(password + salt) + ":" + salt);
+ public int getSaltLength() {
+ return 32;
}
+
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5.java b/src/main/java/fr/xephi/authme/security/crypts/MD5.java
index 8e20dbf4..22164a53 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/MD5.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/MD5.java
@@ -1,31 +1,16 @@
package fr.xephi.authme.security.crypts;
-import java.math.BigInteger;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import fr.xephi.authme.security.HashUtils;
+import fr.xephi.authme.security.crypts.description.HasSalt;
+import fr.xephi.authme.security.crypts.description.Recommendation;
+import fr.xephi.authme.security.crypts.description.SaltType;
+import fr.xephi.authme.security.crypts.description.Usage;
-/**
- */
-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));
- }
+public class MD5 extends UnsaltedMethod {
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
- return getMD5(password);
+ public String computeHash(String password) {
+ return HashUtils.md5(password);
}
- @Override
- public boolean comparePassword(String hash, String password,
- String playerName) throws NoSuchAlgorithmException {
- return hash.equals(computeHash(password, "", ""));
- }
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java
index e54f8ad3..39a08b7f 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java
@@ -1,33 +1,24 @@
package fr.xephi.authme.security.crypts;
-import java.math.BigInteger;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import static fr.xephi.authme.security.HashUtils.md5;
-/**
- */
-public class MD5VB implements EncryptionMethod {
+public class MD5VB extends HexSaltedMethod {
- private static String getMD5(String message)
- throws NoSuchAlgorithmException {
- MessageDigest md5 = MessageDigest.getInstance("MD5");
- md5.reset();
- md5.update(message.getBytes());
- byte[] digest = md5.digest();
- return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
+ @Override
+ public String computeHash(String password, String salt, String name) {
+ return "$MD5vb$" + salt + "$" + md5(md5(password) + salt);
}
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
- return "$MD5vb$" + salt + "$" + getMD5(getMD5(password) + salt);
- }
-
- @Override
- public boolean comparePassword(String hash, String password,
- String playerName) throws NoSuchAlgorithmException {
+ public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) {
+ String hash = encryptedPassword.getHash();
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;
}
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java
index 7232dd18..97418640 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java
@@ -1,34 +1,19 @@
package fr.xephi.authme.security.crypts;
-import fr.xephi.authme.AuthMe;
+import fr.xephi.authme.security.RandomString;
-import java.math.BigInteger;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import static fr.xephi.authme.security.HashUtils.md5;
-/**
- */
-public class MYBB implements EncryptionMethod {
+public class MYBB extends SeparateSaltMethod {
- private static String getMD5(String message)
- throws NoSuchAlgorithmException {
- MessageDigest md5 = MessageDigest.getInstance("MD5");
- md5.reset();
- md5.update(message.getBytes());
- byte[] digest = md5.digest();
- return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
+ @Override
+ public String computeHash(String password, String salt, String name) {
+ return md5(md5(salt) + md5(password));
}
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
- return getMD5(getMD5(salt) + getMD5(password));
+ public String generateSalt() {
+ return RandomString.generateHex(8);
}
- @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));
- }
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java
index ff2e256e..c7d85b6a 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java
@@ -4,25 +4,26 @@
*/
package fr.xephi.authme.security.crypts;
+import fr.xephi.authme.security.HashUtils;
+import fr.xephi.authme.security.MessageDigestAlgorithm;
+
import java.io.UnsupportedEncodingException;
-import java.security.GeneralSecurityException;
import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
/**
* @author stefano
*/
-public class PHPBB implements EncryptionMethod {
+public class PHPBB extends HexSaltedMethod {
private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
- public static String md5(String data) {
+ private static String md5(String data) {
try {
byte[] bytes = data.getBytes("ISO-8859-1");
- MessageDigest md5er = MessageDigest.getInstance("MD5");
+ MessageDigest md5er = HashUtils.getDigest(MessageDigestAlgorithm.MD5);
byte[] hash = md5er.digest(bytes);
return bytes2hex(hash);
- } catch (GeneralSecurityException | UnsupportedEncodingException e) {
+ } catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
@@ -58,7 +59,7 @@ public class PHPBB implements EncryptionMethod {
return buf.toString();
}
- public String phpbb_hash(String password, String salt) {
+ private String phpbb_hash(String password, String salt) {
String random_state = salt;
StringBuilder random = new StringBuilder();
int count = 6;
@@ -109,7 +110,7 @@ public class PHPBB implements EncryptionMethod {
return output.toString();
}
- String _hash_crypt_private(String password, String setting) {
+ private String _hash_crypt_private(String password, String setting) {
String output = "*";
if (!setting.substring(0, 3).equals("$H$"))
return output;
@@ -130,21 +131,26 @@ public class PHPBB implements EncryptionMethod {
return output;
}
- public boolean phpbb_check_hash(String password, String hash) {
- if (hash.length() == 34)
+ private boolean phpbb_check_hash(String password, String hash) {
+ if (hash.length() == 34) {
return _hash_crypt_private(password, hash).equals(hash);
- else return md5(password).equals(hash);
+ }
+ return md5(password).equals(hash);
}
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
+ public String computeHash(String password, String salt, String name) {
return phpbb_hash(password, salt);
}
@Override
- public boolean comparePassword(String hash, String password,
- String playerName) throws NoSuchAlgorithmException {
- return phpbb_check_hash(password, hash);
+ public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) {
+ return phpbb_check_hash(password, encryptedPassword.getHash());
}
+
+ @Override
+ public int getSaltLength() {
+ return 16;
+ }
+
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java
index 4b8646c0..2950fe67 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java
@@ -1,36 +1,27 @@
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.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
-import java.math.BigInteger;
import java.security.InvalidKeyException;
-import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-/**
- */
-public class PHPFUSION 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));
- }
+@Recommendation(Usage.DO_NOT_USE)
+@AsciiRestricted
+public class PHPFUSION extends SeparateSaltMethod {
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
- String digest = null;
+ public String computeHash(String password, String salt, String name) {
String algo = "HmacSHA256";
- String keyString = getSHA1(salt);
+ String keyString = HashUtils.sha1(salt);
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.init(key);
byte[] bytes = mac.doFinal(password.getBytes("ASCII"));
@@ -42,19 +33,16 @@ public class PHPFUSION implements EncryptionMethod {
}
hash.append(hex);
}
- digest = hash.toString();
+ return hash.toString();
} catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException e) {
- //ingore
+ throw new UnsupportedOperationException("Cannot create PHPFUSION hash for " + name, e);
}
-
- return digest;
}
@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, ""));
+ public String generateSalt() {
+ return RandomString.generateHex(12);
}
+
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java b/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java
index 8dec71ad..dc2cb3b4 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java
@@ -1,21 +1,11 @@
package fr.xephi.authme.security.crypts;
-import java.security.NoSuchAlgorithmException;
-
-/**
- */
-public class PLAINTEXT implements EncryptionMethod {
+@Deprecated
+public class PLAINTEXT extends UnsaltedMethod {
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
+ public String computeHash(String password) {
return password;
}
- @Override
- public boolean comparePassword(String hash, String password,
- String playerName) throws NoSuchAlgorithmException {
- return hash.equals(password);
- }
-
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java
index 41e658ec..db089e36 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java
@@ -1,35 +1,16 @@
package fr.xephi.authme.security.crypts;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import fr.xephi.authme.security.HashUtils;
-/**
- */
-public class ROYALAUTH implements EncryptionMethod {
+public class ROYALAUTH extends UnsaltedMethod {
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
- for (int i = 0; i < 25; i++)
- password = hash(password, salt);
+ public String computeHash(String password) {
+ for (int i = 0; i < 25; i++) {
+ // TODO ljacqu 20151228: HashUtils#sha512 gets a new message digest each time...
+ password = HashUtils.sha512(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, "", ""));
- }
-
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java
index 1de37d50..613bcd3f 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java
@@ -1,34 +1,26 @@
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 java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import static fr.xephi.authme.security.HashUtils.md5;
-/**
- */
-public class SALTED2MD5 implements EncryptionMethod {
+@Recommendation(Usage.ACCEPTABLE) // presuming that length is something sensible (>= 8)
+@HasSalt(value = SaltType.TEXT) // length defined by Settings.saltLength
+public class SALTED2MD5 extends SeparateSaltMethod {
- private static String getMD5(String message)
- throws NoSuchAlgorithmException {
- MessageDigest md5 = MessageDigest.getInstance("MD5");
- md5.reset();
- md5.update(message.getBytes());
- byte[] digest = md5.digest();
- return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
+ @Override
+ public String computeHash(String password, String salt, String name) {
+ return md5(md5(password) + salt);
}
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
- return getMD5(getMD5(password) + salt);
+ public String generateSalt() {
+ return RandomString.generateHex(Settings.saltLength);
}
- @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));
- }
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java
index e336a13f..d9952ee4 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java
@@ -1,34 +1,20 @@
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;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+@Recommendation(Usage.RECOMMENDED)
+public class SALTEDSHA512 extends SeparateSaltMethod {
-/**
- */
-public class SALTEDSHA512 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
+ public String computeHash(String password, String salt, String name) {
+ return HashUtils.sha512(password + salt);
}
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
- 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, ""));
+ public String generateSalt() {
+ return RandomString.generateHex(32);
}
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java
index f41c01e9..080910ec 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java
@@ -1,32 +1,12 @@
package fr.xephi.authme.security.crypts;
-import java.math.BigInteger;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import fr.xephi.authme.security.HashUtils;
-/**
- */
-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));
- }
+public class SHA1 extends UnsaltedMethod {
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
- return getSHA1(password);
- }
-
- @Override
- public boolean comparePassword(String hash, String password,
- String playerName) throws NoSuchAlgorithmException {
- return hash.equals(computeHash(password, "", ""));
+ public String computeHash(String password) {
+ return HashUtils.sha1(password);
}
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java
index 834b208f..efe2c8dd 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java
@@ -1,33 +1,28 @@
package fr.xephi.authme.security.crypts;
-import java.math.BigInteger;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import fr.xephi.authme.security.crypts.description.Recommendation;
+import fr.xephi.authme.security.crypts.description.Usage;
-/**
- */
-public class SHA256 implements EncryptionMethod {
+import static fr.xephi.authme.security.HashUtils.sha256;
- private static String getSHA256(String message)
- throws NoSuchAlgorithmException {
- MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
- sha256.reset();
- sha256.update(message.getBytes());
- byte[] digest = sha256.digest();
- return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
+@Recommendation(Usage.RECOMMENDED)
+public class SHA256 extends HexSaltedMethod {
+
+ @Override
+ public String computeHash(String password, String salt, String name) {
+ return "$SHA$" + salt + "$" + sha256(sha256(password) + salt);
}
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
- return "$SHA$" + salt + "$" + getSHA256(getSHA256(password) + salt);
- }
-
- @Override
- public boolean comparePassword(String hash, String password,
- String playerName) throws NoSuchAlgorithmException {
+ public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String playerName) {
+ String hash = encryptedPassword.getHash();
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;
}
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java
index 888406a1..81f1e026 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java
@@ -1,31 +1,12 @@
package fr.xephi.authme.security.crypts;
-import java.math.BigInteger;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import fr.xephi.authme.security.HashUtils;
-/**
- */
-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));
- }
+public class SHA512 extends UnsaltedMethod {
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
- return getSHA512(password);
+ public String computeHash(String password) {
+ return HashUtils.sha512(password);
}
- @Override
- public boolean comparePassword(String hash, String password,
- String playerName) throws NoSuchAlgorithmException {
- return hash.equals(computeHash(password, "", ""));
- }
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/SMF.java b/src/main/java/fr/xephi/authme/security/crypts/SMF.java
index d2d4f74d..c1d33661 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/SMF.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/SMF.java
@@ -1,31 +1,11 @@
package fr.xephi.authme.security.crypts;
-import java.math.BigInteger;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import fr.xephi.authme.security.HashUtils;
-/**
- */
-public class SMF implements EncryptionMethod {
+public class SMF extends UsernameSaltMethod {
- 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));
+ public EncryptedPassword computeHash(String password, String name) {
+ return new EncryptedPassword(HashUtils.sha1(name.toLowerCase() + password));
}
- @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));
- }
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java
new file mode 100644
index 00000000..c7402fcc
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java
@@ -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;
+ }
+
+}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java b/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java
new file mode 100644
index 00000000..6900b80f
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java
@@ -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;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java
new file mode 100644
index 00000000..c7b46b11
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java
@@ -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;
+ }
+
+}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java b/src/main/java/fr/xephi/authme/security/crypts/WBB3.java
index a3fe0d5e..6cc12300 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/WBB3.java
@@ -1,34 +1,19 @@
package fr.xephi.authme.security.crypts;
-import fr.xephi.authme.AuthMe;
+import fr.xephi.authme.security.RandomString;
-import java.math.BigInteger;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import static fr.xephi.authme.security.HashUtils.sha1;
-/**
- */
-public class WBB3 implements EncryptionMethod {
+public class WBB3 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
+ public String computeHash(String password, String salt, String name) {
+ return sha1(salt.concat(sha1(salt.concat(sha1(password)))));
}
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
- return getSHA1(salt.concat(getSHA1(salt.concat(getSHA1(password)))));
+ public String generateSalt() {
+ return RandomString.generateHex(40);
}
- @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, ""));
- }
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java
index 05272886..9f67d499 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java
@@ -1,21 +1,34 @@
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;
-/**
- */
-public class WBB4 implements EncryptionMethod {
+@Recommendation(Usage.DOES_NOT_WORK)
+public class WBB4 extends HexSaltedMethod {
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
+ public String computeHash(String password, String salt, String name) {
return BCRYPT.getDoubleHash(password, salt);
}
@Override
- public boolean comparePassword(String hash, String password,
- String playerName) throws NoSuchAlgorithmException {
- return BCRYPT.checkpw(password, hash, 2);
+ public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String playerName) {
+ return BCRYPT.checkpw(password, encryptedPassword.getHash(), 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;
}
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java
index 0ce0c2e5..522fb45b 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java
@@ -59,12 +59,9 @@ package fr.xephi.authme.security.crypts;
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
-/**
- */
-public class WHIRLPOOL implements EncryptionMethod {
+public class WHIRLPOOL extends UnsaltedMethod {
/**
* The message digest size (in bits)
@@ -382,9 +379,7 @@ public class WHIRLPOOL implements EncryptionMethod {
}
}
- @Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
+ public String computeHash(String password) {
byte[] digest = new byte[DIGESTBYTES];
NESSIEinit();
NESSIEadd(password);
@@ -392,9 +387,4 @@ public class WHIRLPOOL implements EncryptionMethod {
return display(digest);
}
- @Override
- public boolean comparePassword(String hash, String password,
- String playerName) throws NoSuchAlgorithmException {
- return hash.equals(computeHash(password, "", ""));
- }
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java
index 3147ddbb..5b198056 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java
@@ -1,14 +1,22 @@
package fr.xephi.authme.security.crypts;
+import fr.xephi.authme.security.crypts.description.HasSalt;
+import fr.xephi.authme.security.crypts.description.Recommendation;
+import fr.xephi.authme.security.crypts.description.SaltType;
+import fr.xephi.authme.security.crypts.description.Usage;
+
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
-/**
- */
-public class WORDPRESS implements EncryptionMethod {
+// TODO #391: Wordpress algorithm fails sometimes. Fix it and change the Recommendation to "ACCEPTABLE" if appropriate
+@Recommendation(Usage.DO_NOT_USE)
+@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 final SecureRandom randomGen = new SecureRandom();
@@ -102,16 +110,15 @@ public class WORDPRESS implements EncryptionMethod {
}
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
+ public String computeHash(String password) {
byte random[] = new byte[6];
- this.randomGen.nextBytes(random);
+ randomGen.nextBytes(random);
return crypt(password, gensaltPrivate(stringToUtf8(new String(random))));
}
@Override
- public boolean comparePassword(String hash, String password,
- String playerName) throws NoSuchAlgorithmException {
+ public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) {
+ String hash = encryptedPassword.getHash();
String comparedHash = crypt(password, hash);
return comparedHash.equals(hash);
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java
index aa287ddd..ff5236bd 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java
@@ -1,12 +1,12 @@
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;
-/**
- */
-public class XAUTH implements EncryptionMethod {
+@Recommendation(Usage.RECOMMENDED)
+public class XAUTH extends HexSaltedMethod {
- public static String getWhirlpool(String message) {
+ private static String getWhirlpool(String message) {
WHIRLPOOL w = new WHIRLPOOL();
byte[] digest = new byte[WHIRLPOOL.DIGESTBYTES];
w.NESSIEinit();
@@ -16,19 +16,23 @@ public class XAUTH implements EncryptionMethod {
}
@Override
- public String computeHash(String password, String salt, String name)
- throws NoSuchAlgorithmException {
+ public String computeHash(String password, String salt, String name) {
String hash = getWhirlpool(salt + password).toLowerCase();
int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length());
return hash.substring(0, saltPos) + salt + hash.substring(saltPos);
}
@Override
- public boolean comparePassword(String hash, String password,
- String playerName) throws NoSuchAlgorithmException {
+ public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String playerName) {
+ String hash = encryptedPassword.getHash();
int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length());
- String salt = hash.substring(saltPos, saltPos + 12);
- return hash.equals(computeHash(password, salt, ""));
+ String saltFromHash = hash.substring(saltPos, saltPos + 12);
+ return hash.equals(computeHash(password, saltFromHash, null));
+ }
+
+ @Override
+ public int getSaltLength() {
+ return 12;
}
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/XF.java b/src/main/java/fr/xephi/authme/security/crypts/XF.java
deleted file mode 100644
index 00a23c8e..00000000
--- a/src/main/java/fr/xephi/authme/security/crypts/XF.java
+++ /dev/null
@@ -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