diff --git a/SimpleHttpServer/Login/LoginProvider.cs b/SimpleHttpServer/Login/LoginProvider.cs
index a91174a..e5b9f81 100644
--- a/SimpleHttpServer/Login/LoginProvider.cs
+++ b/SimpleHttpServer/Login/LoginProvider.cs
@@ -1,73 +1,81 @@
-using Konscious.Security.Cryptography;
-using Newtonsoft.Json;
+using Newtonsoft.Json;
+using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Text;
namespace SimpleHttpServer.Login;
internal struct SerialLoginData {
- public string salt;
+ public string passwordSalt;
+ public string extraDataSalt;
public string pwd;
- public string additionalData;
+ public string extraData;
- public LoginData toPlainData() {
+ public LoginData ToPlainData() {
return new LoginData {
- salt = Convert.FromBase64String(salt),
- password = Convert.FromBase64String(pwd)
+ passwordSalt = Convert.FromBase64String(passwordSalt),
+ extraDataSalt = Convert.FromBase64String(extraDataSalt)
};
}
}
internal struct LoginData {
- public byte[] salt;
- public byte[] password;
- public byte[] encryptedData;
+ public byte[] passwordSalt;
+ public byte[] extraDataSalt;
+ public byte[] passwordHash;
+ public byte[] encryptedExtraData;
- public SerialLoginData toSerial() {
+ public SerialLoginData ToSerial() {
return new SerialLoginData {
- salt = Convert.ToBase64String(salt),
- pwd = Convert.ToBase64String(password),
- additionalData = Convert.ToBase64String(encryptedData)
+ passwordSalt = Convert.ToBase64String(passwordSalt),
+ extraDataSalt = Convert.ToBase64String(extraDataSalt),
+ pwd = Convert.ToBase64String(passwordHash),
+ extraData = Convert.ToBase64String(encryptedExtraData)
};
}
}
internal struct LoginDataProviderConfig {
+ ///
+ /// Size of the password salt and the extradata salt. So each salt will be of size .
+ ///
public int SALT_SIZE = 32;
public int KEY_LENGTH = 256 / 8;
- public int A2_ITERATIONS = 5;
- public int A2_MEMORY_SIZE = 500_000;
- public int A2_PARALLELISM = 8;
- public int A2_HASH_LENGTH = 256 / 8;
- public int A2_MAX_CONCURRENT = 4;
public int PBKDF2_ITERATIONS = 600_000;
public LoginDataProviderConfig() { }
}
-public class LoginProvider {
+public class LoginProvider {
- private static readonly Func JsonSerialize = t => Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(t));
- private static readonly Func JsonDeserialize = b => JsonConvert.DeserializeObject(Encoding.UTF8.GetString(b))!;
+ private static readonly Func JsonSerialize = t => Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(t));
+ private static readonly Func JsonDeserialize = b => JsonConvert.DeserializeObject(Encoding.UTF8.GetString(b))!;
+
+ [ThreadStatic]
+ private static SHA256? _sha256PerThread;
+ private static SHA256 Sha256PerThread { get => _sha256PerThread ??= SHA256.Create(); }
private readonly LoginDataProviderConfig config;
private readonly ReaderWriterLockSlim ldLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
private readonly string ldPath;
- private readonly Dictionary loginData;
- private readonly SemaphoreSlim argon2Limit;
+ private readonly Dictionary loginDatas;
+
+ private Func DataSerializer = JsonSerialize;
+ private Func DataDeserializer = JsonDeserialize;
+ public void SetDataSerializers(Func serializer, Func deserializer) {
+ DataSerializer = serializer ?? JsonSerialize;
+ DataDeserializer = deserializer ?? JsonDeserialize;
+ }
- private Func DataSerializer = JsonSerialize;
- private Func DataDeserializer = JsonDeserialize;
public LoginProvider(string ldPath, string confPath) {
this.ldPath = ldPath;
- loginData = LoadLoginData(ldPath);
- config = LoadArgon2Config(confPath);
- argon2Limit = new SemaphoreSlim(config.A2_MAX_CONCURRENT);
+ loginDatas = LoadLoginDatas(ldPath);
+ config = LoadLoginProviderConfig(confPath);
}
- private static Dictionary LoadLoginData(string path) {
+ private static Dictionary LoadLoginDatas(string path) {
Dictionary tempData;
if (!File.Exists(path)) {
File.WriteAllText(path, "{}", Encoding.UTF8);
@@ -79,13 +87,26 @@ public class LoginProvider {
}
}
var ld = new Dictionary();
- foreach (var pair in tempData!) {
- ld.Add(pair.Key, pair.Value.toPlainData());
+ foreach (var pair in tempData) {
+ ld.Add(pair.Key, pair.Value.ToPlainData());
}
return ld;
}
- private static LoginDataProviderConfig LoadArgon2Config(string path) {
+ private void SaveLoginData() {
+ var serial = new Dictionary();
+ ldLock.EnterWriteLock();
+ try {
+ foreach (var pair in loginDatas) {
+ serial.Add(pair.Key, pair.Value.ToSerial());
+ }
+ } finally {
+ ldLock.ExitWriteLock();
+ }
+ File.WriteAllText(ldPath, JsonConvert.SerializeObject(serial));
+ }
+
+ private static LoginDataProviderConfig LoadLoginProviderConfig(string path) {
if (!File.Exists(path)) {
var conf = new LoginDataProviderConfig();
File.WriteAllText(path, JsonConvert.SerializeObject(conf));
@@ -94,39 +115,22 @@ public class LoginProvider {
return JsonConvert.DeserializeObject(File.ReadAllText(path));
}
- public void SetDataSerialization(Func serializer, Func deserializer) {
- DataSerializer = serializer ?? JsonSerialize;
- DataDeserializer = deserializer ?? JsonDeserialize;
- }
-
- private void StoreLoginData() {
- var serial = new Dictionary();
+ public bool AddUser(string username, string password, TExtraData additional) {
ldLock.EnterWriteLock();
try {
- foreach (var pair in loginData!) {
- serial.Add(pair.Key, pair.Value.toSerial());
- }
- } finally {
- ldLock.ExitWriteLock();
- }
- File.WriteAllText(ldPath, JsonConvert.SerializeObject(serial));
- }
-
- public bool AddUser(string username, string password, T additional) {
- ldLock.EnterWriteLock();
- try {
- if (loginData.ContainsKey(username)) {
+ if (loginDatas.ContainsKey(username)) {
return false;
}
- var salt = RandomNumberGenerator.GetBytes(config.SALT_SIZE);
- var pwdHash = HashPwd(password, salt);
+ var passwordSalt = RandomNumberGenerator.GetBytes(config.SALT_SIZE);
+ var extraDataSalt = RandomNumberGenerator.GetBytes(config.SALT_SIZE);
LoginData ld = new LoginData() {
- salt = salt,
- password = pwdHash,
- encryptedData = EncryptAdditionalData(password, salt, additional)
+ passwordSalt = passwordSalt,
+ extraDataSalt = extraDataSalt,
+ passwordHash = ComputeSaltedSha256Hash(password, passwordSalt),
+ encryptedExtraData = EncryptExtraData(password, extraDataSalt, additional),
};
- loginData.Add(username, ld);
- StoreLoginData();
+ loginDatas.Add(username, ld);
+ SaveLoginData();
} finally {
ldLock.ExitWriteLock();
}
@@ -136,9 +140,9 @@ public class LoginProvider {
public bool RemoveUser(string username) {
ldLock.EnterWriteLock();
try {
- var removed = loginData.Remove(username);
+ var removed = loginDatas.Remove(username);
if (removed) {
- StoreLoginData();
+ SaveLoginData();
}
return removed;
} finally {
@@ -146,64 +150,62 @@ public class LoginProvider {
}
}
- public bool ModifyUser(string username, string newPassword, T newAdditional) {
+ public bool ModifyUser(string username, string newPassword, TExtraData newExtraData) {
ldLock.EnterWriteLock();
try {
- if (!loginData.ContainsKey(username)) {
+ if (!loginDatas.ContainsKey(username)) {
return false;
}
- loginData.Remove(username, out var data);
- data.password = HashPwd(newPassword, data.salt);
- data.encryptedData = EncryptAdditionalData(newPassword, data.salt, newAdditional);
- loginData.Add(username, data);
- StoreLoginData();
+ loginDatas.Remove(username, out var data);
+ data.passwordHash = ComputeSaltedSha256Hash(newPassword, data.passwordSalt);
+ data.encryptedExtraData = EncryptExtraData(newPassword, data.extraDataSalt, newExtraData);
+ loginDatas.Add(username, data);
+ SaveLoginData();
} finally {
ldLock.ExitWriteLock();
}
return true;
}
- public (bool, T) Authenticate(string username, string password) {
+ public bool TryAuthenticate(string username, string password, [MaybeNullWhen(false)] out TExtraData extraData) {
LoginData data;
ldLock.EnterReadLock();
try {
- if (!loginData.TryGetValue(username, out data)) {
- return (false, default(T)!);
+ if (!loginDatas.TryGetValue(username, out data)) {
+ extraData = default;
+ return false;
}
} finally {
ldLock.ExitReadLock();
}
- var hash = HashPwd(password, data.salt);
- if (!hash.SequenceEqual(data.password)) {
- return (false, default(T)!);
+ var hash = ComputeSaltedSha256Hash(password, data.passwordSalt);
+ if (!hash.SequenceEqual(data.passwordHash)) {
+ extraData = default;
+ return false;
}
- return (true, DecryptAdditionalData(password, data.salt, data.encryptedData));
+ extraData = DecryptExtraData(password, data.extraDataSalt, data.encryptedExtraData);
+ return true;
}
- private byte[] HashPwd(string pwd, byte[] salt) {
- byte[] hash;
- argon2Limit.Wait();
- try {
- using (var argon2 = new Argon2id(Encoding.UTF8.GetBytes(pwd))) {
- argon2.Iterations = config.A2_ITERATIONS;
- argon2.MemorySize = config.A2_MEMORY_SIZE;
- argon2.DegreeOfParallelism = config.A2_PARALLELISM;
- argon2.Salt = salt;
- hash = argon2.GetBytes(config.A2_HASH_LENGTH);
- }
- // force collection to reduce sustained memory usage if many hashes are done in close time proximity to each other
- GC.Collect();
- } finally {
- argon2Limit.Release();
- }
- return hash;
+ ///
+ /// Threadsafe as the SHA256 instance () is per thread.
+ ///
+ ///
+ ///
+ ///
+ private static byte[] ComputeSaltedSha256Hash(string data, byte[] salt) {
+ var dataBytes = Encoding.UTF8.GetBytes(data);
+ var buf = new byte[data.Length + salt.Length];
+ Buffer.BlockCopy(dataBytes, 0, buf, 0, dataBytes.Length);
+ Buffer.BlockCopy(salt, 0, buf, dataBytes.Length, salt.Length);
+ return Sha256PerThread.ComputeHash(buf);
}
- private byte[] EncryptAdditionalData(string pwd, byte[] salt, T data) {
+ private byte[] EncryptExtraData(string pwd, byte[] salt, TExtraData extraData) {
var pbkdf2 = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(pwd), salt, config.PBKDF2_ITERATIONS, HashAlgorithmName.SHA256);
var key = pbkdf2.GetBytes(config.KEY_LENGTH / 8);
- var plainBytes = DataSerializer(data);
+ var plainBytes = DataSerializer(extraData);
using var aes = Aes.Create();
aes.KeySize = config.KEY_LENGTH;
aes.Key = key;
@@ -219,7 +221,7 @@ public class LoginProvider {
return encryptedBytes;
}
- private T DecryptAdditionalData(string pwd, byte[] salt, byte[] encryptedData) {
+ private TExtraData DecryptExtraData(string pwd, byte[] salt, byte[] encryptedData) {
var pbkdf2 = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(pwd), salt, config.PBKDF2_ITERATIONS, HashAlgorithmName.SHA256);
var key = pbkdf2.GetBytes(config.KEY_LENGTH / 8);