One thing I have often seen is badly stored passwords. Just because the password is stored in a database on your server, does not make it secure. So what do badly stored passwords look like?
Secure passwords stored badly are no longer secure. The passwords in the previous screenshot are the actual user passwords. Entering the first password, ^tj_Y4$g1!8LkD
at the login screen will give the user access to the system. Passwords should be stored securely in the database. In fact, you need to employ salted password hashing. You should be able to encrypt the user's password, but never decrypt it.
So how do you decrypt the password to match it to the password the user enters at the login screen? Well, you don't. You always hash the password the user enters at the login screen. If it matches the hash of their real password stored in the database, you give them access to the system.
The SQL tables in this recipe are for illustration only and are not written to by the code in the recipe. The database can be found in the _database scripts
folder that accompanies the source code for this book.
Chapter12
:Class1.cs
, which we renamed Recipes.cs
in order to distinguish the code properly. You can, however, rename your class whatever you like if that makes more sense to you.Chapter12
library project:namespace Chapter12 { public class Recipes { } }
using
statement to your class:using System.Security.Cryptography;
RegisterUser()
and ValidateLogin()
. Both methods take as parameters the username
and password
variables:public static class Recipes { public static string saltValue { get; set; } public static string hashValue { get; set; } public static void RegisterUser(string password, string username) { } public static void ValidateLogin(string password, string username) { } }
RegisterUser()
method, here we do a number of things. To list the steps in the method:RNGCryptoServiceProvider
SHA256
.This is a very secure way to handle user passwords in your application:
public static void RegisterUser(string password, string username) { // Create a truly random salt using RNGCryptoServiceProvider. RNGCryptoServiceProvider csprng = new RNGCryptoServiceProvider(); byte[] salt = new byte[32]; csprng.GetBytes(salt); // Get the salt value saltValue = Convert.ToBase64String(salt); // Salt the password byte[] saltedPassword = Encoding.UTF8.GetBytes(saltValue + password); // Hash the salted password using SHA256 SHA256Managed hashstring = new SHA256Managed(); byte[] hash = hashstring.ComputeHash(saltedPassword); // Save both the salt and the hash in the user's database record. saltValue = Convert.ToBase64String(salt); hashValue = Convert.ToBase64String(hash); }
ValidateLogin()
method. Here we take the username and validate that first. If the user entered the username incorrectly, do not tell them so. This would alert someone trying to compromise the system that they have the wrong username and that as soon as they get a wrong password notification, they know that the username is correct. The steps in this method are as follows:Note that we never decrypt the password from the database. If you have code decrypting user passwords and matching that to the password entered, you need to reconsider and rewrite your password logic. A system should never be able to decrypt user passwords.
public static void ValidateLogin(string password, string username) { // Read the user's salt value from the database string saltValueFromDB = saltValue; // Read the user's hash value from the database string hashValueFromDB = hashValue; byte[] saltedPassword = Encoding.UTF8.GetBytes(saltValueFromDB + password); // Hash the salted password using SHA256 SHA256Managed hashstring = new SHA256Managed(); byte[] hash = hashstring.ComputeHash(saltedPassword); string hashToCompare = Convert.ToBase64String(hash); if (hashValueFromDB.Equals(hashToCompare)) Console.WriteLine("User Validated."); else Console.WriteLine("Login credentials incorrect. User not validated."); }
Chapter12
class in your CodeSamples
project:using static
to your Program.cs
file:using static Chapter12.Recipes;
RegisterUser()
method and pass it the username
and password
variable. After that, call the ValidateLogin()
method and see whether the password matches the hash. This would obviously not happen at the same time in a real production system:string username = "dirk.strauss"; string password = "^tj_Y4$g1!8LkD"; RegisterUser(password, username); ValidateLogin(password, username); Console.ReadLine();
password
variable to something else. This will mimic a user entering an incorrect password:string username = "dirk.strauss"; string password = "^tj_Y4$g1!8LkD"; RegisterUser(password, username); password = "WrongPassword"; ValidateLogin(password, username); Console.ReadLine();
Nowhere in the code did we decrypt the password. In fact, the password is never stored anywhere. We always worked with the hash of the password. Here are the important points to take away from this recipe:
Random
class in C# to generate your salt. Always use the RNGCryptoServiceProvider
class.'l3tm31n'
for a password is good enough), and you have a very good password encryption routine.When we look at the user access table, the correct way to store user credentials would look something like this:
The salt and hash are stored alongside the username, and are secure because they can't be decrypted to expose the actual password.
If you sign up for a service on the Internet and they send you a confirmation either via email or text message and display your password in this message in plain text, then you should seriously consider closing your account. If a system can read your password and send it to you in plain text, then so can anybody else. Never use the same password for all your logins.