Source code for the login system I wrote for the system. As the project were closed source and there were other members I can only post my own code and not the entire project.
LoginSignupSystem.cs
The code for handling login and signup attempts
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Security.Cryptography;
using System.Security;
using System.Net;
using System.Data.SQLite;
using System.Runtime.InteropServices;
namespace Project_Table_Infect
{
/// <summary>
/// Enum containing different signup results.
/// </summary>
public enum SignupState { Succesfull, UserExists, PasswordsDoNotMatch, InvalidUsername}
/// <summary>
/// Class for handling login
/// </summary>
public static class LoginSignupSystem
{
private const string CONNECTIONSTRING = @"Data Source=Project_Table_Infect_DB;version=3;New=true;Compress=true";
private readonly static SQLiteConnection connection = new SQLiteConnection(CONNECTIONSTRING);
private const int HASHITERATIONS = 60000;
/// <summary>
/// Tries to login.
/// </summary>
/// <param name="usernameInput">Username to login with.</param>
/// <param name="pwInput">The password to login with.</param>
/// <returns>Returns if the login were sucessful</returns>
public static bool Login(string usernameInput, SecureString pwInput)
{
string username = usernameInput;
SecureString pw = pwInput;
bool result = false;
try
{
connection.Open();
if (!CheckIfUserExists(username)) return false;
SQLiteCommand liteCommand = connection.CreateCommand();
liteCommand.CommandText = @"SELECT * FROM Users WHERE Username = @username COLLATE NOCASE;";
liteCommand.Parameters.AddWithValue("@username", username);
SQLiteDataReader liteDataReader = liteCommand.ExecuteReader();
string pwHash ="";
string salt = "";
while (liteDataReader.Read())
{
pwHash = liteDataReader.GetString(2);
salt = liteDataReader.GetString(3);
}
result = pwHash == HashPassword(pw, salt);
}
finally
{
if (pw != null) pw.Dispose();
connection.Close();
}
return result;
}
public static SignupState Signup(string usernameInput, SecureString pwInput)
{
string username = usernameInput;
string passwordHash = "";
SecureString pw = pwInput;
try
{
//Open db connection
connection.Open();
//Generate salt
byte[] saltByteArray = new byte[64];
new RNGCryptoServiceProvider().GetBytes(saltByteArray);
string salt = Encoding.Unicode.GetString(saltByteArray);
//checks username
if (!ValidateUsername(username)) return SignupState.InvalidUsername;
if (CheckIfUserExists(username)) return SignupState.UserExists;
//Hashes the password
passwordHash = HashPassword(pw, salt);
//Sets up sql command
SQLiteCommand liteCommand = connection.CreateCommand();
string commandString = @"INSERT INTO Users (Username, Password, Salt) VALUES(@username,@passwordHash,@salt);";
liteCommand.CommandText = commandString;
//Sets parameters
liteCommand.Parameters.AddWithValue("@username", username);
liteCommand.Parameters.AddWithValue("@passwordHash", passwordHash);
liteCommand.Parameters.AddWithValue("@salt", salt);
//Execute SQL command
liteCommand.ExecuteNonQuery();
liteCommand.Dispose();
}
finally
{
if (pw != null) pw.Dispose();
connection.Close();
}
return SignupState.Succesfull;
}
/// <summary>
/// Runs the signup procedure with password validation.
/// </summary>
/// <param name="usernameInput">The username to sign up with</param>
/// <param name="pw1Input">First password input</param>
/// <param name="pw2Input">Second password input for validation</param>
/// <returns>Retruns the state of the signup, wheter is were succesful or not</returns>
public static SignupState SignupWithPasswordValidation(string usernameInput, SecureString pw1Input, SecureString pw2Input)
{
string username = usernameInput;
string passwordHash = "";
SecureString pw1 = pw1Input;
SecureString pw2 = pw2Input;
try
{
//Open db connection
connection.Open();
//Generate salt
byte[] saltByteArray = new byte[64];
new RNGCryptoServiceProvider().GetBytes(saltByteArray);
string salt = Encoding.Unicode.GetString(saltByteArray);
//checks username
if (!ValidateUsername(username)) return SignupState.InvalidUsername;
if (CheckIfUserExists(username)) return SignupState.UserExists;
//Checks password
if (!CompareSecureStrings(pw1, pw2)) return SignupState.PasswordsDoNotMatch;
//Hashes the password
passwordHash = HashPassword(pw1, salt);
//Sets up sql command
SQLiteCommand liteCommand = connection.CreateCommand();
string commandString = @"INSERT INTO Users (Username, Password, Salt) VALUES(@username,@passwordHash,@salt);";
liteCommand.CommandText = commandString;
//Sets parameters
liteCommand.Parameters.AddWithValue("@username", username);
liteCommand.Parameters.AddWithValue("@passwordHash", passwordHash);
liteCommand.Parameters.AddWithValue("@salt", salt);
//Execute SQL command
liteCommand.ExecuteNonQuery();
liteCommand.Dispose();
}
finally
{
if (pw1 != null) pw1.Dispose();
if (pw2 != null) pw2.Dispose();
connection.Close();
}
return SignupState.Succesfull;
}
/// <summary>
/// Hashes the password with HMACSHA12
/// </summary>
/// <param name="password">The password</param>
/// <param name="salt">The salt to use</param>
/// <returns>Returns the password Hash</returns>
private static string HashPassword(SecureString password, string salt)
{
string passwordHash = "";
passwordHash = Encoding.Unicode.GetString(new Rfc2898DeriveBytes(new NetworkCredential("", password).Password, Encoding.Unicode.GetBytes(salt), HASHITERATIONS, HashAlgorithmName.SHA512).GetBytes(64));
return passwordHash;
}
/// <summary>
/// Checks if a user exists in the database
/// </summary>
/// <param name="username">The username to check</param>
/// <returns>Returns true if a user exists</returns>
private static bool CheckIfUserExists(string username)
{
bool opendConnection = false;
bool result = false;
if (connection != null && connection.State != System.Data.ConnectionState.Open)
{
connection.Open();
opendConnection = true;
}
SQLiteCommand liteCommand = connection.CreateCommand();
liteCommand.CommandText = $"SELECT EXISTS(SELECT 1 FROM Users WHERE Username = @username COLLATE NOCASE);";
liteCommand.Parameters.AddWithValue("@username", username);
SQLiteDataReader liteDataReader = liteCommand.ExecuteReader();
while (liteDataReader.Read())
{
if (liteDataReader.GetInt16(0) == 1) result = true;
}
if (opendConnection)
{
connection.Close();
}
return result;
}
/// <summary>
/// Validates a username to only use the following characters[a-zA-Z0-9], -, _
/// </summary>
/// <param name="stringToCheck">The username to check</param>
/// <returns></returns>
private static bool ValidateUsername(string stringToCheck)
{
return Regex.IsMatch(Regex.Escape(stringToCheck), @"(^(?![-_]))(^[a-zA-Z0-9_-]*$)");
}
/// <summary>
/// Checks if two secure strings are the same
/// </summary>
/// <param name="ssPw1">The first securestring</param>
/// <param name="ssPw2">The second securestring</param>
/// <returns>Returns true if the secure strings are the same</returns>
private static bool CompareSecureStrings(SecureString ssPw1, SecureString ssPw2)
{
IntPtr ptrPw1 = IntPtr.Zero;
IntPtr ptrPw2 = IntPtr.Zero;
try
{
ptrPw1 = Marshal.SecureStringToBSTR(ssPw1);
ptrPw2 = Marshal.SecureStringToBSTR(ssPw2);
bool result = true;
if (ssPw1.Length != ssPw2.Length)
{
return false;
}
else
{
int ssPw1Length = Marshal.ReadInt32(ptrPw1, -4);
for (int i = 0; i < ssPw1Length; i++)
{
byte bPw1 = Marshal.ReadByte(ptrPw1, i);
byte bPw2 = Marshal.ReadByte(ptrPw2, i);
if (bPw1 != bPw2) result = false;
}
return result;
}
}
finally
{
if (ptrPw1 != IntPtr.Zero) Marshal.ZeroFreeBSTR(ptrPw1);
if (ptrPw2 != IntPtr.Zero) Marshal.ZeroFreeBSTR(ptrPw2);
}
}
}
}