From 1877c29e689d66fc4db4d3910fc13b7bf126b774 Mon Sep 17 00:00:00 2001 From: csimonapastore Date: Tue, 17 Jun 2025 00:01:29 +0200 Subject: [PATCH] Fixing user password generation/validation - wip --- MainProject.Tests/JsonData/appsettings.json | 5 +-- .../JsonData/invalidCryptAppsettings.json | 5 +-- MainProject.Tests/TestsUtils/ModelsInit.cs | 3 +- ...yptoUtils_Tests.cs => CryptUtils_Tests.cs} | 19 +++++----- MainProject/Models/Database/SqlServer/User.cs | 5 ++- .../Models/Settings/EncryptionSettings.cs | 3 +- MainProject/Services/UserService.cs | 15 +++++--- .../Utils/{CryptoUtils.cs => CryptUtils.cs} | 35 ++++++++----------- MainProject/appsettings.json | 5 +-- 9 files changed, 50 insertions(+), 45 deletions(-) rename MainProject.Tests/Utils/{CryptoUtils_Tests.cs => CryptUtils_Tests.cs} (87%) rename MainProject/Utils/{CryptoUtils.cs => CryptUtils.cs} (65%) diff --git a/MainProject.Tests/JsonData/appsettings.json b/MainProject.Tests/JsonData/appsettings.json index a8fa93d..97b4013 100644 --- a/MainProject.Tests/JsonData/appsettings.json +++ b/MainProject.Tests/JsonData/appsettings.json @@ -35,8 +35,9 @@ "ExpiredAfterMinsOfInactivity": 15 }, "EncryptionSettings": { - "Salt": "S7VIidfXQf1tOQYX", - "Pepper": "" + "Secret": "S7VIidfXQf1tOQYX", + "Salt": "", + "Iterations": 10 } } diff --git a/MainProject.Tests/JsonData/invalidCryptAppsettings.json b/MainProject.Tests/JsonData/invalidCryptAppsettings.json index bb290e6..293760b 100644 --- a/MainProject.Tests/JsonData/invalidCryptAppsettings.json +++ b/MainProject.Tests/JsonData/invalidCryptAppsettings.json @@ -35,8 +35,9 @@ "ExpiredAfterMinsOfInactivity": 15 }, "EncryptionSettings": { - "Salt": "AAAAA", - "Pepper": "" + "Secret": "AAAAA", + "Salt": "", + "Iterations": 10 } } diff --git a/MainProject.Tests/TestsUtils/ModelsInit.cs b/MainProject.Tests/TestsUtils/ModelsInit.cs index 29541ff..d97f11b 100644 --- a/MainProject.Tests/TestsUtils/ModelsInit.cs +++ b/MainProject.Tests/TestsUtils/ModelsInit.cs @@ -13,8 +13,9 @@ public static class ModelsInit FirstName = "FirstName", LastName = "LastName", Email = "test-new@email.it", - PasswordHash = "PasswordHash", + PasswordPepper = "PasswordPepper", PasswordSalt = "PasswordSalt", + PasswordIterations = 0, Password = "Password", Role = CreateRole(), IsTestUser = true diff --git a/MainProject.Tests/Utils/CryptoUtils_Tests.cs b/MainProject.Tests/Utils/CryptUtils_Tests.cs similarity index 87% rename from MainProject.Tests/Utils/CryptoUtils_Tests.cs rename to MainProject.Tests/Utils/CryptUtils_Tests.cs index c054658..d29e343 100644 --- a/MainProject.Tests/Utils/CryptoUtils_Tests.cs +++ b/MainProject.Tests/Utils/CryptUtils_Tests.cs @@ -102,11 +102,11 @@ public class CryptoUtils_Tests } [TestMethod] - public void GenerateSalt() + public void GeneratePepper() { try { - var salt = CryptUtils.GenerateSalt(); + var salt = CryptUtils.GeneratePepper(); Assert.IsTrue(!String.IsNullOrEmpty(salt)); } catch (Exception ex) @@ -122,14 +122,14 @@ public class CryptoUtils_Tests try { var password = "P4ssw0rd@1!"; - var salt = CryptUtils.GenerateSalt(); - Assert.IsTrue(!String.IsNullOrEmpty(salt)); + var pepper = CryptUtils.GeneratePepper(); + Assert.IsTrue(!String.IsNullOrEmpty(pepper)); WebApplicationBuilder builder = WebApplication.CreateBuilder(Array.Empty()); AppSettings appSettings = ProgramUtils.AddConfiguration(ref builder, System.AppDomain.CurrentDomain.BaseDirectory + "/JsonData"); - CryptUtils cryptoUtils = new CryptUtils(appSettings); - var encryptedPassword = cryptoUtils.GeneratePassword(password, salt, 0); - Assert.IsTrue(password != encryptedPassword); + var salt = appSettings?.EncryptionSettings?.Salt ?? String.Empty; + var encryptedPassword = CryptUtils.GeneratePassword(password, salt, 0, pepper); + Assert.AreNotEqual(encryptedPassword, password); } catch (Exception ex) { @@ -147,10 +147,7 @@ public class CryptoUtils_Tests var salt = "Afi7PQYgEL2sPbNyVzduvg=="; var hashedPassword = "2lMeySZ9ciH1KtSg1Z7oSJRmJEjHMeDvdaNRcJcGutM="; - WebApplicationBuilder builder = WebApplication.CreateBuilder(Array.Empty()); - AppSettings appSettings = ProgramUtils.AddConfiguration(ref builder, System.AppDomain.CurrentDomain.BaseDirectory + "/JsonData"); - CryptUtils cryptoUtils = new CryptUtils(appSettings); - var verified = cryptoUtils.VerifyPassword(password, salt, 0, hashedPassword); + var verified = CryptUtils.VerifyPassword(hashedPassword, password, salt, 0); Assert.IsTrue(verified); } catch (Exception ex) diff --git a/MainProject/Models/Database/SqlServer/User.cs b/MainProject/Models/Database/SqlServer/User.cs index 3bb8f97..8d5fff6 100644 --- a/MainProject/Models/Database/SqlServer/User.cs +++ b/MainProject/Models/Database/SqlServer/User.cs @@ -13,7 +13,10 @@ public class User : Base [MaxLength(200)] public required string Email { get; set; } public required string PasswordSalt { get; set; } - public required string PasswordHash { get; set; } +#nullable enable + public string? PasswordPepper { get; set; } +#nullable disable + public required int PasswordIterations { get; set; } public required Role Role { get; set; } public required bool IsTestUser { get; set; } diff --git a/MainProject/Models/Settings/EncryptionSettings.cs b/MainProject/Models/Settings/EncryptionSettings.cs index 8d40260..a7a0106 100644 --- a/MainProject/Models/Settings/EncryptionSettings.cs +++ b/MainProject/Models/Settings/EncryptionSettings.cs @@ -3,7 +3,8 @@ namespace BasicDotnetTemplate.MainProject.Models.Settings; public class EncryptionSettings { #nullable enable + public string? Secret { get; set; } public string? Salt { get; set; } - public string? Pepper { get; set; } + public int? Iterations { get; set; } #nullable disable } \ No newline at end of file diff --git a/MainProject/Services/UserService.cs b/MainProject/Services/UserService.cs index 3fe1314..99a6a09 100644 --- a/MainProject/Services/UserService.cs +++ b/MainProject/Services/UserService.cs @@ -4,6 +4,7 @@ using BasicDotnetTemplate.MainProject.Core.Database; using BasicDotnetTemplate.MainProject.Models.Api.Common.Exceptions; using BasicDotnetTemplate.MainProject.Models.Api.Data.User; using BasicDotnetTemplate.MainProject.Models.Database.SqlServer; +using BasicDotnetTemplate.MainProject.Utils; using Microsoft.EntityFrameworkCore; namespace BasicDotnetTemplate.MainProject.Services; @@ -21,12 +22,15 @@ public interface IUserService public class UserService : BaseService, IUserService { private readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + private readonly CryptUtils _cryptUtils; public UserService( IHttpContextAccessor httpContextAccessor, IConfiguration configuration, SqlServerContext sqlServerContext ) : base(httpContextAccessor, configuration, sqlServerContext) - { } + { + this._cryptUtils = new(_appSettings); + } private IQueryable GetUsersQueryable() { @@ -51,8 +55,9 @@ public class UserService : BaseService, IUserService FirstName = data.FirstName, LastName = data.LastName, Email = data.Email, - PasswordSalt = "", - PasswordHash = "", + PasswordSalt = _appSettings.EncryptionSettings?.Salt ?? String.Empty, + PasswordPepper = CryptUtils.GeneratePepper(), + PasswordIterations = _appSettings.EncryptionSettings?.Iterations ?? 10, Password = "", Role = role, IsTestUser = false @@ -77,7 +82,9 @@ public class UserService : BaseService, IUserService User? user = await this.GetUserByEmailQueryable(email).FirstOrDefaultAsync(); if (user != null) { - var encryptedPassword = user.PasswordHash; + var valid = CryptUtils.VerifyPassword(user.Password, password, user.PasswordSalt, user.PasswordIterations, user.PasswordPepper); + if (!valid) + user = null; } return user; diff --git a/MainProject/Utils/CryptoUtils.cs b/MainProject/Utils/CryptUtils.cs similarity index 65% rename from MainProject/Utils/CryptoUtils.cs rename to MainProject/Utils/CryptUtils.cs index cb847fc..75b9862 100644 --- a/MainProject/Utils/CryptoUtils.cs +++ b/MainProject/Utils/CryptUtils.cs @@ -4,24 +4,17 @@ using System.Text; using BasicDotnetTemplate.MainProject.Models.Settings; namespace BasicDotnetTemplate.MainProject.Utils; -public class CryptUtils +public class CryptUtils(AppSettings appSettings) { - private readonly string _secretKey; - private readonly string _pepper; + private readonly string _secret = appSettings.EncryptionSettings?.Secret ?? String.Empty; private const int _M = 16; private const int _N = 32; - public CryptUtils(AppSettings appSettings) - { - _secretKey = appSettings.EncryptionSettings?.Salt ?? String.Empty; - _pepper = appSettings.EncryptionSettings?.Pepper ?? String.Empty; - } - public string Decrypt(string encryptedData) { var decrypted = String.Empty; - if (String.IsNullOrEmpty(this._secretKey) || this._secretKey.Length < _M) + if (String.IsNullOrEmpty(this._secret) || this._secret.Length < _M) { throw new ArgumentException("Unable to proceed with decryption due to invalid settings"); } @@ -35,7 +28,7 @@ public class CryptUtils using (var aes = Aes.Create()) { - aes.Key = Encoding.UTF8.GetBytes(this._secretKey); + aes.Key = Encoding.UTF8.GetBytes(this._secret); aes.IV = Encoding.UTF8.GetBytes(iv); using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV)) @@ -57,21 +50,21 @@ public class CryptUtils return decrypted; } - public static string GenerateSalt() + public static string GeneratePepper() { using var rng = RandomNumberGenerator.Create(); - var byteSalt = new byte[16]; - rng.GetBytes(byteSalt); - var salt = Convert.ToBase64String(byteSalt); - return salt; + var bytePepper = new byte[16]; + rng.GetBytes(bytePepper); + var pepper = Convert.ToBase64String(bytePepper); + return pepper; } - public string GeneratePassword(string password, string salt, int iteration) + public static string GeneratePassword(string password, string salt, int iterations, string? pepper = "") { string hashedPassword = password; - for(var i = 0; i <= iteration; i++) + for (var i = 0; i <= iterations; i++) { - var passwordSaltPepper = $"{hashedPassword}{salt}{this._pepper}"; + var passwordSaltPepper = $"{hashedPassword}{salt}{pepper}"; var byteValue = Encoding.UTF8.GetBytes(passwordSaltPepper); var byteHash = SHA256.HashData(byteValue); hashedPassword = Convert.ToBase64String(byteHash); @@ -80,9 +73,9 @@ public class CryptUtils return hashedPassword; } - public bool VerifyPassword(string password, string salt, int iteration, string userPassword) + public static bool VerifyPassword(string userPassword, string password, string salt, int iterations, string? pepper = "") { - string hashedPassword = this.GeneratePassword(password, salt, iteration); + string hashedPassword = GeneratePassword(password, salt, iterations, pepper); return hashedPassword.Equals(userPassword, StringComparison.OrdinalIgnoreCase); } diff --git a/MainProject/appsettings.json b/MainProject/appsettings.json index 8893bf2..7063e68 100644 --- a/MainProject/appsettings.json +++ b/MainProject/appsettings.json @@ -35,8 +35,9 @@ "ExpiredAfterMinsOfInactivity": 15 }, "EncryptionSettings": { - "Salt": "S7VIidfXQf1tOQYX", - "Pepper": "" + "Secret": "S7VIidfXQf1tOQYX", + "Salt": "", + "Iterations": 10 }, "PermissionsSettings": { "FilePath": "Config/permissions.json"