C#: How to generate a unique key or password using salting + hashing
1. Introduction
When an application develops, we need to protect user passwords from security attacks. When your application operates on a license key, we need to validate the license.
In today's world providing security to your system is bit crucial. We have to face malicious scripts, security threats and unwanted attacks in out there. Let’s see how we can prevent those kind of attacks.
2. Password Hashing
In hashing, we are going to convert password string into a byte array. Hashing is a one way function, we can’t reverse it. We can see many hashing algorithms available.
Let’s say we hashed the user's password and stored in our database. When a user logs in the second time, we have to verify the entered password is correct. What we can do is, we can hash the entered text into the password field and compare it with the hashed value from the database. Since Hashing is a one-way function, we can’t decrypt and retrieve original text.
2.1. Invalid username or password
When a user enters the wrong password, the system should not let the user know it’s an invalid password. Instead, it should tell invalid username or password. Then if the hacker tries to login into your system, he doesn’t know whether username or password got wrong.
2.2. Hash Functions
SHA1, MD5 is popular hashing algorithms. SHA1 got inspired by MD algorithms and created SHA algorithms. From these two algorithms, better to use SHA1. Because it’s more highly secure than MD5. MD5 is going to convert a string into 128 bits. But SHA1 convert a string into 160 bits. In MD5 less no of operations required to crack the message, when compared to SHA1. But MD is faster than SHA1. However, it’s better to use a SHA1 algorithm instead of MD5, since MD5 can be broken easily.
2.3. Hashes can be cracked easily
Hackers can guess passwords and simply hash them with a hashing algorithm and try it in your system. It takes few seconds to generate a hash. Within a limited amount of time, your passwords can be cracked easily. These type of guessing password and hashing is called as Dictionary attacks and Brute force attacks.
In dictionary attacks, it uses a file with some words. These words can be extracted from a database or else from set of paragraphs. Most hackers guess passwords from common terms like, hello, hellow, he11o3, etc. They take a string and try to replace letters in it, like hello, he33o, heiio, etc. Then these words are hashed and use against the password hash.
Brute Force attacks going to try out every possible of character combinations to a certain string length. It’s a very expensive computational process, But eventually it’s going to find out your password! That’s why we need lengthier passwords, So it will take long time to crack your passwords.
By hashing your passwords, we can’t prevent Dictionary attacks or Brute force attacks but we can minimize them.
While Dictionary attacks and Brute force attacks are time consuming, hackers have another kid in their block. It’s Lookup Table. In Lookup table, it’s going to precompute the hashes of passwords and used to store them in a dictionary file. So hundreds of guesses can be done in a second. It’s a very effective method to crack your passwords.
Rainbow Tables. Beware guys, it can crack any eight characters lengthy MD5 password. Rainbow table is same as Lookup table. In here hash cracking speed is slower, compared to a lookup table. But lookup table size is smaller, so more hashes can be stored in the same space. So Rainbow tables are more effective than Lookup tables.
2.4. Salted Password Hashing
If we use a salt with password hashing, it's impossible to crack your passwords through Rainbow tables and lookup tables. In lookup tables, hackers are going to hash the same list of passwords and try out in your system. Let’s say two users are having the same password on your system. When we hash these passwords, it’s same password hash. In Salting, we are adding a random number along with the hashed password. So two users never get same salted password hash. And mind you don’t use the same salt with different user passwords, don’t repeat the salting. If a new user registers into your system or else changes the password, generate a new salt. Use a new salt for every user password. If a hacker is smart enough, they may be able to crack a one or two user passwords, But not more than that. Even though many users have same password, Hacker will not be able to crack their passwords using a lookup table. Don’t use shorter salt, Then hacker can create a lookup table to generate every possible salt.
2.5. Let’s see how we can generate a unique key
public class AuthenticationValidator
{
KeyGeneratorContext context = new KeyGeneratorContext();
public bool GenerateSubscriptionKey(string userName, int companyCode)
{
bool isSuccess = false;
byte[] salt = GenerateSalt();
Company company = context.Companies.Where(c => c.Code == companyCode).FirstOrDefault();
User user = context.Users.Where(u => u.CompanyId == company.Id && u.UserName == userName).FirstOrDefault();
string[] hashKeys = GenerateHashKey(userName, companyCode).Split(':');
Rfc2898DeriveBytes value = new Rfc2898DeriveBytes(hashKeys[0] + hashKeys[1], salt);
byte[] key = value.GetBytes(64);
user.Subscription = key;
user.SaltValue = salt;
isSuccess = context.SaveChanges() > 0;
return isSuccess;
}
private static byte[] GenerateSalt ()
{
int saltLength = 32;
byte[] salt = new byte[saltLength];
using (var random = new RNGCryptoServiceProvider())
{
random.GetNonZeroBytes(salt);
}
return salt;
}
private string GenerateHashKey(string userName, int companyCode)
{
string key = string.Empty;
Company company = context.Companies.Where(c => c.Code == companyCode).FirstOrDefault();
User user = context.Users.Where(u => u.CompanyId == company.Id && u.UserName == userName).FirstOrDefault();
if (company != null && user != null)
key = company.Name + ":" + user.UserGuid;
return key;
}
In this example, we are passing username and company code to generate a license key. Using GenerateSalt method, we can generate a random number. Created a 32 bytes length salt number using RNGCryptoServiceProvider class. Using Rfc289DeriveVytes class we can generate a salted hashing value. Along with the generated license key, we used to store salted number as well.
2.6. Let’s see how we can validate the license key.
public bool ValidateSubscriptionKey(string userName, int companyCode)
{
bool isValid = false;
byte[] subscription = new byte[64];
byte[] saltValue = new byte[32];
Company company = context.Companies.Where(c => c.Code == companyCode).FirstOrDefault();
if (company != null)
{
User user = context.Users.Where(u => u.CompanyId == company.Id && u.UserName == userName).FirstOrDefault();
if (user != null)
{
subscription = user.Subscription;
saltValue = new byte[32];
}
string[] hashKeys = GenerateHashKey(userName, companyCode).Split(':');
Rfc2898DeriveBytes value = new Rfc2898DeriveBytes(hashKeys[0] + hashKeys[1], saltValue);
byte[] key = value.GetBytes(64);
bool result = subscription.SequenceEqual(key);
if (result && user.ExpiryDate >= DateTime.Now)
isValid = true;
}
return isValid;
}
Used 64 byte array as a license key and 32 byte array as a salted value. We can use SequenceEqual method to compare entered license key with stored license key as above.
3. Download
3.1. TechNet Gallery
- Source code can be downloaded from https://gallery.technet.microsoft.com/Generate-a-unique-key-or-5ee02098
3.2. GitHub
- You can find it in this github repo: https://github.com/hansamaligamage/LicenseKey
4. Conclusion
This article explains various mechanisms to secure valuable data. If you go through the code sample, it explains how to secure a license key using salted hashing mechanism.