Protecting credential database against exploitation#
Salting in user registration#
graph TD
st1(Start user registration) -->
a[Ask user password for registration] -->
b[Generate a random salt] -->
c[Combine password + salt] -->
d[Hash password + salt] -->
e[Store in database] -->
end1(end)
Salting in user login#
graph TD
st2(Start user login) -->
f[Ask user password for login] -->
g[Get user's salt and hash from the database] -->
h[Calculate hash of user-provided pass + salt] -->
check{{hash_user == hash_database}} -->
j[Allow login] -->
end2(end)
check -->|false| f
Salting example code#
The following code is from the system login interface project:
1using System.ComponentModel.DataAnnotations;
2using System.Linq;
3using System.Security.Cryptography;
4using System.Threading.Tasks;
5using Microsoft.EntityFrameworkCore;
6
7namespace SystemLogin;
8
9public class AccountService(AppDbContext db, PasswordHasher hasher)
10{
11 public async Task NewAccountAsync(string username, string password, bool isAdmin = false)
12 {
13 var (salt, saltedPasswordHash) = hasher.Hash(password);
14 db.Add(new Account
15 {
16 Username = username,
17 Salt = salt,
18 SaltedPasswordHash = saltedPasswordHash,
19 isAdmin = isAdmin
20 });
21 await db.SaveChangesAsync();
22 }
23
24 public Task<bool> UsernameExistsAsync(string username)
25 {
26 return db.Accounts.AnyAsync(a => a.Username == username);
27 }
28
29 public async Task<bool> CredentialsCorrectAsync(string username, string password)
30 {
31 var account = await db.Accounts.FirstAsync(a => a.Username == username);
32 return hasher.PasswordCorrect(password, account.Salt, account.SaltedPasswordHash);
33 }
34
35 public Task<bool> UserIsAdminAsync(string username)
36 {
37 return db.Accounts.Where(a => a.Username == username).Select(a => a.isAdmin).FirstAsync();
38 }
39
40 public Task<Account> GetAccountAsync(string username)
41 {
42 return db.Accounts.FirstAsync(a => a.Username == username);
43 }
44}
45
46public class PasswordHasher(
47 int saltLength = 128 / 8,
48 int hashIterations = 600_000
49 // salt and iterations according to https://en.wikipedia.org/wiki/PBKDF2
50)
51{
52 public bool PasswordCorrect(string password, byte[] salt, byte[] saltedPasswordHash)
53 {
54 return CryptographicOperations.FixedTimeEquals(Hash(salt, password), saltedPasswordHash);
55 }
56
57 private byte[] Hash(byte[] salt, string password)
58 {
59 return Rfc2898DeriveBytes.Pbkdf2(
60 password,
61 salt,
62 hashIterations,
63 HashAlgorithmName.SHA256,
64 256 / 8 // Due to SHA256
65 );
66 }
67
68 public (byte[] Salt, byte[] Hash) Hash(string password)
69 {
70 var salt = RandomNumberGenerator.GetBytes(saltLength);
71 return (salt, Hash(salt, password));
72 }
73}
74
75public class AppDbContext(string dbPath = "../../../database.sqlite") : DbContext
76{
77 public DbSet<Account> Accounts { get; set; }
78
79 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
80 {
81 optionsBuilder.UseSqlite($"Data Source={dbPath}");
82 }
83}
84
85public class Account
86{
87 [Key] public string Username { get; set; }
88
89 public byte[] Salt { get; set; }
90 public byte[] SaltedPasswordHash { get; set; }
91 public bool isAdmin { get; set; }
92}
Activity 62 (Analyzing password salting in code)
The code above salts the password.
Where does salting take place?
There are two different
Hashfunctions. One of them returns salt and salted password hash, and the other one only salted password hash. What is the reason?