diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index 99cb3620..e048b58d 100755 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -919,16 +919,18 @@ namespace TShockAPI try { var user = new User(); - + string echoPassword = ""; if (args.Parameters.Count == 1) { user.Name = args.Player.Name; - user.Password = args.Parameters[0]; + echoPassword = args.Parameters[0]; + user.CreateBCryptHash(args.Parameters[0]); } else if (args.Parameters.Count == 2 && TShock.Config.AllowRegisterAnyUsername) { user.Name = args.Parameters[0]; - user.Password = args.Parameters[1]; + echoPassword = args.Parameters[1]; + user.CreateBCryptHash(args.Parameters[1]); } else { @@ -942,7 +944,7 @@ namespace TShockAPI if (TShock.Users.GetUserByName(user.Name) == null && user.Name != TSServerPlayer.AccountName) // Cheap way of checking for existance of a user { args.Player.SendSuccessMessage("Account \"{0}\" has been registered.", user.Name); - args.Player.SendSuccessMessage("Your password is {0}.", user.Password); + args.Player.SendSuccessMessage("Your password is {0}.", echoPassword); TShock.Users.AddUser(user); TShock.Log.ConsoleInfo("{0} registered an account: \"{1}\".", args.Player.Name, user.Name); } @@ -976,7 +978,7 @@ namespace TShockAPI var user = new User(); user.Name = args.Parameters[1]; - user.Password = args.Parameters[2]; + user.CreateBCryptHash(args.Parameters[2]); user.Group = args.Parameters[3]; try diff --git a/TShockAPI/DB/UserManager.cs b/TShockAPI/DB/UserManager.cs index 5b920ba1..a9b134d1 100755 --- a/TShockAPI/DB/UserManager.cs +++ b/TShockAPI/DB/UserManager.cs @@ -21,9 +21,11 @@ using System.CodeDom.Compiler; using System.Data; using System.Collections.Generic; using System.Linq; +using System.Text; using MySql.Data.MySqlClient; using System.Text.RegularExpressions; using BCrypt.Net; +using System.Security.Cryptography; namespace TShockAPI.DB { @@ -309,7 +311,7 @@ namespace TShockAPI.DB { public int ID { get; set; } public string Name { get; set; } - public string Password { get; set; } + public string Password { get; internal set; } public string UUID { get; set; } public string Group { get; set; } public string Registered { get; set; } @@ -354,7 +356,7 @@ namespace TShockAPI.DB return true; } } catch (SaltParseException) { - if (TShock.Utils.HashPassword(password).ToUpper() == this.Password.ToUpper()) { + if (hashPassword(password).ToUpper() == this.Password.ToUpper()) { // The password is not stored using BCrypt; upgrade it to BCrypt immediately upgradePasswordToBCrypt(password); return true; @@ -406,6 +408,61 @@ namespace TShockAPI.DB } } } + + public void CreateBCryptHash(string password) { + try { + this.Password = BCrypt.Net.BCrypt.HashPassword(password, TShock.Config.WorkFactor); + } catch (ArgumentOutOfRangeException) { + TShock.Log.ConsoleError("Invalid BCrypt work factor! Creating new hash using default work factor."); + this.Password = BCrypt.Net.BCrypt.HashPassword(password); + } + } + + /// + /// A dictionary of hashing algortihms and an implementation object. + /// + internal readonly Dictionary> HashTypes = new Dictionary> + { + {"sha512", () => new SHA512Managed()}, + {"sha256", () => new SHA256Managed()}, + {"md5", () => new MD5Cng()}, + {"sha512-xp", () => SHA512.Create()}, + {"sha256-xp", () => SHA256.Create()}, + {"md5-xp", () => MD5.Create()}, + }; + + /// + /// Returns a Sha256 string for a given string + /// + /// bytes to hash + /// string sha256 + internal string hashPassword(byte[] bytes) + { + if (bytes == null) + throw new NullReferenceException("bytes"); + Func func; + if (!HashTypes.TryGetValue(TShock.Config.HashAlgorithm.ToLower(), out func)) + throw new NotSupportedException("Hashing algorithm {0} is not supported".SFormat(TShock.Config.HashAlgorithm.ToLower())); + + using (var hash = func()) + { + var ret = hash.ComputeHash(bytes); + return ret.Aggregate("", (s, b) => s + b.ToString("X2")); + } + } + + /// + /// Returns a hashed password string for a given string + /// + /// string to hash + /// string sha256 + internal string hashPassword(string password) + { + if (string.IsNullOrEmpty(password) || password == "non-existant password") + return null; + return hashPassword(Encoding.UTF8.GetBytes(password)); + } + } [Serializable] diff --git a/TShockAPI/Utils.cs b/TShockAPI/Utils.cs index f6033796..bdb82b9c 100644 --- a/TShockAPI/Utils.cs +++ b/TShockAPI/Utils.cs @@ -27,6 +27,7 @@ using System.Text; using System.Text.RegularExpressions; using Terraria; using TShockAPI.DB; +using BCrypt.Net; namespace TShockAPI { @@ -719,14 +720,16 @@ namespace TShockAPI ply.SendErrorMessage("Use \"my query\" for items with spaces"); } - /// - /// Default hashing algorithm. - /// - public string HashAlgo = "sha512"; + /// + /// Default hashing algorithm. + /// + [Obsolete("This is no longer necessary, please use TShock.Config.HashAlgorithm instead.")] + public string HashAlgo = "sha512"; - /// - /// A dictionary of hashing algortihms and an implementation object. - /// + /// + /// A dictionary of hashing algortihms and an implementation object. + /// + [Obsolete("This is no longer necessary, after switching to User.VerifyPassword(password) instead.")] public readonly Dictionary> HashTypes = new Dictionary> { {"sha512", () => new SHA512Managed()}, @@ -742,6 +745,7 @@ namespace TShockAPI /// /// bytes to hash /// string sha256 + [Obsolete("Please use User.VerifyPassword(password) instead. Warning: This will upgrade passwords to BCrypt. Already converted passwords will not hash correctly using this method.")] public string HashPassword(byte[] bytes) { if (bytes == null) @@ -762,6 +766,7 @@ namespace TShockAPI /// /// string to hash /// string sha256 + [Obsolete("Please use User.VerifyPassword(password) instead. Warning: This will upgrade passwords to BCrypt. Already converted passwords will not hash correctly using this method.")] public string HashPassword(string password) { if (string.IsNullOrEmpty(password) || password == "non-existant password")