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 users password and stored in our database. When user second time logs in, we have to verify entered password is correct. What we can do is, we can hash the entered text into 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 user enters wrong password, System should not let the user know it’s Invalid password , instead of it should tell Invalid username or password . Then if hacker tries to login into your system, he doesn’t know whether username or password got wrong.
2.2. Hash Functions
SHA1, MD5 are popular hashing algorithms. SHA1 got inspired from MD algorithms and created SHA algorithms. From these two algorithms, better to use SHA1, Because it’s high 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 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 has 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 8 characters lengthy MD5 password. Rainbow table is same as Lookup table. In here hash cracking speed is slower, compared to 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 same list of passwords and try out in your system. Let’s say two users are having same password in your system. When we hash these passwords, It’s same password hash. In Salting, we are adding a random number along with hashed password. So two users never get same salted password hash. And mind you don’t use same salt with different user passwords, don’t repeat the salting. If new user registers into your system or else change password, generate a new salt. Use a new salt for every user password. If hacker is smart enough, He 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. I used to create 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;
}
I 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 my 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.
5. References
-
Importance Of Developers Salting User Passwords
-
Salted Password Hashing - Doing it Right
-
Generating a Key from a Password
-
Rfc2898DeriveBytes Class