diff --git a/src/NzbDrone.Core/Authentication/User.cs b/src/NzbDrone.Core/Authentication/User.cs index 794d4824a..63c67bd5f 100644 --- a/src/NzbDrone.Core/Authentication/User.cs +++ b/src/NzbDrone.Core/Authentication/User.cs @@ -1,4 +1,4 @@ -using System; +using System; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Authentication @@ -8,5 +8,7 @@ namespace NzbDrone.Core.Authentication public Guid Identifier { get; set; } public string Username { get; set; } public string Password { get; set; } + public string Salt { get; set; } + public int Iterations { get; set; } } } diff --git a/src/NzbDrone.Core/Authentication/UserService.cs b/src/NzbDrone.Core/Authentication/UserService.cs index 4e4bc9aed..7e75d8645 100644 --- a/src/NzbDrone.Core/Authentication/UserService.cs +++ b/src/NzbDrone.Core/Authentication/UserService.cs @@ -1,6 +1,8 @@ -using System; +using System; using System.Linq; +using System.Security.Cryptography; using System.Xml.Linq; +using Microsoft.AspNetCore.Cryptography.KeyDerivation; using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; @@ -25,6 +27,10 @@ namespace NzbDrone.Core.Authentication private readonly IAppFolderInfo _appFolderInfo; private readonly IDiskProvider _diskProvider; + private static readonly int ITERATIONS = 10000; + private static readonly int SALT_SIZE = 128 / 8; + private static readonly int NUMBER_OF_BYTES = 256 / 8; + public UserService(IUserRepository repo, IAppFolderInfo appFolderInfo, IDiskProvider diskProvider) { _repo = repo; @@ -34,12 +40,15 @@ namespace NzbDrone.Core.Authentication public User Add(string username, string password) { - return _repo.Insert(new User - { - Identifier = Guid.NewGuid(), - Username = username.ToLowerInvariant(), - Password = password.SHA256Hash() - }); + var user = new User + { + Identifier = Guid.NewGuid(), + Username = username.ToLowerInvariant() + }; + + SetUserHashedPassword(user, password); + + return _repo.Insert(user); } public User Update(User user) @@ -58,7 +67,7 @@ namespace NzbDrone.Core.Authentication if (user.Password != password) { - user.Password = password.SHA256Hash(); + SetUserHashedPassword(user, password); } user.Username = username.ToLowerInvariant(); @@ -85,7 +94,20 @@ namespace NzbDrone.Core.Authentication return null; } - if (user.Password == password.SHA256Hash()) + if (user.Salt.IsNullOrWhiteSpace()) + { + // If password matches stored SHA256 hash, update to salted hash and verify. + if (user.Password == password.SHA256Hash()) + { + SetUserHashedPassword(user, password); + + return Update(user); + } + + return null; + } + + if (VerifyHashedPassword(user, password)) { return user; } @@ -98,6 +120,43 @@ namespace NzbDrone.Core.Authentication return _repo.FindUser(identifier); } + private User SetUserHashedPassword(User user, string password) + { + var salt = GenerateSalt(); + + user.Iterations = ITERATIONS; + user.Salt = Convert.ToBase64String(salt); + user.Password = GetHashedPassword(password, salt, ITERATIONS); + + return user; + } + + private byte[] GenerateSalt() + { + var salt = new byte[SALT_SIZE]; + RandomNumberGenerator.Create().GetBytes(salt); + + return salt; + } + + private string GetHashedPassword(string password, byte[] salt, int iterations) + { + return Convert.ToBase64String(KeyDerivation.Pbkdf2( + password: password, + salt: salt, + prf: KeyDerivationPrf.HMACSHA512, + iterationCount: iterations, + numBytesRequested: NUMBER_OF_BYTES)); + } + + private bool VerifyHashedPassword(User user, string password) + { + var salt = Convert.FromBase64String(user.Salt); + var hashedPassword = GetHashedPassword(password, salt, user.Iterations); + + return user.Password == hashedPassword; + } + public void Handle(ApplicationStartedEvent message) { if (_repo.All().Any()) diff --git a/src/NzbDrone.Core/Datastore/Migration/174_add_salt_to_users.cs b/src/NzbDrone.Core/Datastore/Migration/174_add_salt_to_users.cs new file mode 100644 index 000000000..e65a6056c --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/174_add_salt_to_users.cs @@ -0,0 +1,16 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(174)] + public class add_salt_to_users : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Users") + .AddColumn("Salt").AsString().Nullable() + .AddColumn("Iterations").AsInt32().Nullable(); + } + } +} diff --git a/src/NzbDrone.Core/Sonarr.Core.csproj b/src/NzbDrone.Core/Sonarr.Core.csproj index a763944ee..e02afe810 100644 --- a/src/NzbDrone.Core/Sonarr.Core.csproj +++ b/src/NzbDrone.Core/Sonarr.Core.csproj @@ -5,6 +5,7 @@ +