In case you are just starting out and your source uses plaintext for account passwords, follow my guide for a quick cryptography addition.
Find where your project keeps accounts.cs or accountstable.cs (Assuming Trinity: AccountServer/API/Models/AccountServer) and create a new class named Password Hasher or something close to it.
Then use the sample code below:
// 1. Add this NuGet package (once)
Install-Package Konscious.Security.Cryptography.Argon2
// or if you use .csproj, just add:
// <PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.0" />
// 2. Replace your current password saving code with this:
using Konscious.Security.Cryptography;
using System.Security.Cryptography;
using System.Text;
public class AccountProtection
{
private const int SaltSize = 16; // 128-bit salt
private const int HashSize = 32; // 256-bit hash
private const int Iterations = 4; // 2025 recommended
private const int MemorySizeKB = 65536; // 64 MB
private const int Threads = 2;
public static string HashPassword(string password)
{
byte[] salt = RandomNumberGenerator.GetBytes(SaltSize);
var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password))
{
Salt = salt,
DegreeOfParallelism = Threads,
MemorySize = MemorySizeKB,
Iterations = Iterations
};
byte[] hash = argon2.GetBytes(HashSize);
// Format: $argon2id$v=19$m=65536,t=4,p=2$salt$hash
return "$argon2id$v=19$m=" + MemorySizeKB +
",t=" + Iterations +
",p=" + Threads +
"$" + Convert.ToBase64String(salt) +
"$" + Convert.ToBase64String(hash);
}
public static bool VerifyPassword(string password, string storedHash)
{
try
{
var parts = storedHash.Split('$');
if (parts.Length != 6 || parts[1] != "argon2id") return false;
int memory = int.Parse(parts[3].Substring(2));
int iterations = int.Parse(parts[4].Substring(2));
int parallelism = int.Parse(parts[5].Substring(2));
byte[] salt = Convert.FromBase64String(parts[6]);
byte[] hash = Convert.FromBase64String(parts[7]);
var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password))
{
Salt = salt,
DegreeOfParallelism = parallelism,
MemorySize = memory,
Iterations = iterations
};
byte[] testHash = argon2.GetBytes(hash.Length);
return CryptographicOperations.FixedTimeEquals(testHash, hash);
}
catch { return false; }
}
}
Also Where to Put It (2 lines to change in your AccountTable.cs or LoginHandler)
When player registers or changes password: account.Password = AccountProtection.HashPassword(newPassword);
When player logs in:if (AccountProtection.VerifyPassword(inputPassword, account.Password))
// login success
else
// wrong password
Keep in mind this is for new or servers that are not live!! If you have accounts playing already you will want to add a force password reset otherwise old accounts will NOT be able to login
(This is simple to do by creating a ForceReset.cshtml) and add something like this to ConquerController.cs:
[HttpGet("MigratePasswords")]
public async Task<string> MigratePasswords()
{
var oldAccounts = await _AuthDbContext.Accounts.Where(x => !x.Password.Contains("$")).ToListAsync();
foreach (var acc in oldAccounts)
{
// WARNING: This assumes old passwords are plaintext! If they were already hashed (MD5/SHA), you can't migrate → force reset instead.
acc.Password = PasswordHasher.Hash(acc.Password);
}
await _AuthDbContext.SaveChangesAsync();
return $"Migrated {oldAccounts.Count} accounts";
}