Hash It Like You Mean It — Proper password hashing in FreePascal
The title is a bit of a giveaway. Today we’ll be talking about hashing and salting, and more importantly we’re going to talk about how to do it right.
We’ll cover
- What hashing is
- A common way to hash passwords
- A better way to hash passwords
To do so we’ll take a common login concept from a naive implementation where we store passwords in clear-text to a an industry-standard security conscious solution using PBKDF2.
Hash functions take a string input and generates a new string of a fixed length using mathematical formulas. The length of the hashed string is always a fixed size, it has no relation to the length of the input.
As opposed to cryptography, hashes are one-way. Once a string has been hashed there’s no way to un-hash it to get the original string.
The only way to figure out what the original string was, is to pick a string at random, hash it, and see if the values match.
We use that to our advantage when storing passwords. We don’t actually need to know what the password is, just that the hashed values match!
To put this into a relatable form, let’s imagine a website with an online ordering system. To use it you must log in.
Let’s start with a super simple and naive login mechanism and work our way up, getting more and more secure as we go.
User authentication
You have a pizza shop, and you have a website where people can order your tasty pizza.
I go to your website to order my favorite pizza, the Hawaii. (Pineapple belongs on pizza, fight me).
Like all these kinds of sites, this one will let me log in to make it easy to order delivery, pay for my order and so on.
So I type in my username and password and click the big pepperoni-shaped “Log In” button.
The information is sent to the server and the server performs a SQL query to see if I gave it a valid combination of username and password.
select * from users where username="' + username + '" and password="' + password + '"
The results come back, yep, found a record, so the server responds to us with “Yeah, I know you, you’re Marcus Fernstrom, here, have a session token”
Yay pizza!
Prepared statement
The first thing to note about the above SQL statement is that it’s using concatenated strings. That’s a big no-no because of the SQL injection risk.
This article is about hashing, but using prepared statements is so fundamental to using SQL safely that it gets mentioned here, too.
Luckily, turning that SQL into a prepared statement is very easy.
In FreePascal it looks like this
Query.SQL.Text := 'select * from users where username=:username and password=:password';Query.PrepareQuery.Params.ParamByName('username').AsString := username;
Query.Params.ParamByName('password').AsString:= password;
We use placeholders to tell the database where to insert the data, and then we pass that data along to the database to let it do its thing, including validating the data type and escaping the data.
That’s a good first step in securing your authentication logic, but this defense isn’t enough.
Hashing
If someone gets access to your database, they could read all your user's passwords, because they’re being stored in clear-text.
Clear-text means that it’s just regular text being stored without encryption or hashing.
Let’s add some actual security to these passwords to this by using hashing.
A hashed string will always have a set number of bits, no matter how long or short your input string is. That also makes it easy to store as you know exactly how many characters the hashed string will be.
SHA-256 will always be 256 bits. When you convert it to hexadecimal characters, it comes out to 64 characters.
For example, if we hash the string “coolpassword” with SHA-256 we get
FADB68F2A094C1DB2F6FA959EC350A9C51507A14AE7F2324C00B082F2493EFAA
The process when using hashing works like this.
When you sign up for an account, the password is hashed and stored in the “password” field.
When you log in the next time, whatever you type into the password field is hashed and then compared to what’s stored. That’s why we don’t need to store the password in clear-text.
This approach is much better than storing passwords in clear-text, but it’s still pretty bad..
There are two principal reasons it’s bad.
- Rainbow tables
- SHA-256 and even SHA-512 are fast to calculate
Rainbow tables
Since using the same hashing algorithm on the same string will always result in the same hash, that also means we could take a dictionary and pre-calculate as many hashes as we want.
With that list, you now have a key, you know what the hashed word is when you find a match.
This list of pre-calculated hash values is called a rainbow table, and hackers love them.
This kind of attack is used when a hacker has breached your site and copied your data, and the passwords are hashed.
To use it, they just provide a list of hashed passwords and the rainbow table, and the program will go through the list until it finds a match.
The rainbow table is very powerful because it lets you do all the calculations upfront and then apply it to hundreds, thousands, or millions or passwords that were poorly hashed without having to generate a hash for every password you want to try.
It takes time to create a hash from a string.
With algorithms like SHA-256 or SHA-512 it doesn’t take very much time but it does take some. That’s why pre-calculating the rainbow table is so worthwhile, it’s much faster to compare two values than generating a hash and then compare it.
But there IS some sunshine at the end of the hashing tunnel. There’s a simple way to null-and-void rainbow tables altogether!
And that is…
Salting
Salting a string means adding data to it before hashing it, so the output value differs from what it otherwise would have been.
It’s a simple and powerful technique because rainbow tables rely on the fact that what’s been hashed is “coolpassword” and not “mysaltedcoolpassword” which comes out as a different hash value!
Using a salt is very easy.
const saltedPassword = 'mySalt' + request.body.password;
But hold up. If someone hacks your server they could see the salt value right there, and just create a NEW rainbow table using the salt.
Yep, that’s right. That’s why you should use a dynamic salt value.
It’s easier than you think.
To log in you need to provide two pieces of information, a username and a password.
And because you have to use the right combination of username and password, you can use the username as a salt.
That way every user's password will have a different salt so the hacker has no use of rainbow tables. They’d have to generate every hash for every user separately.
And it’s very simple to implement
const saltedPass := request.form.username + request.form.password;
Slow it down
We’re not done yet though!
I mentioned that using algorithms like SHA-256 or SHA-512 are quite fast, and in this case fast is a bad thing.
It’s bad because if someone is using a tool like Hydra against your login page, the faster the algorithm is, the more attempts they can make per minute at cracking someone's password.
“Now hold up a minute”, you say, “it’s a web application, we all know web apps should be as fast as possible or users abandon the site”.
Welcome to the intersection of security vs convenience!
Yes, your application should be as fast as possible, but it doesn’t have to be as fast as possible everywhere!
We’re going to slow things down to make it more secure, but we will only slow down the login action itself, and only to the point where it takes about a second and a half to perform the hashing function.
A second and a half is not a big deal to a user, but to a hacker needing to try hundreds of thousands of passwords (or more)? It’s an eternity.
Instead of just using the SHA algorithms, we will use it with a hashing function specifically designed for passwords.
It’s called PBKDF2. You don’t have to remember this bit, it won’t be on the exam, but that’s short for Password Based Key Derivation Function 2
yeah let’s just stick with PBKDF2.
The difference is that the SHA algorithms are one-shot hashing of a string, while PBKDF2 takes
- A string to hash
- A salt
- The number of iterations
- The desired hash length
- What hashing algorithm to use
It uses all that to come up with the final hash.
Even if a hacker gets a hold of your source code and database, there’s no way to speed up the process other than having a really powerful machine perform the calculations.
Just how long it takes to create a hash depends on your server and the number of iterations you chose. You’ll have to experiment a bit here to find the right number of iterations.
Start with a low number of iterations like 1000. Run the login logic on the server and see how long it takes.
If it takes less than 1.5 seconds, just increase the number and try again until it takes roughly that long.
1.5 seconds seems awfully small to make a difference, doesn’t it?
Let’s do a little experiment! (You know, for science, not because it’s fun!)
I created a command-line program to test how many SHA-512 and how many PBKDF2 hashes I can generate in a second, the program also uses the iteration setting of PBKDF2 to let me test how many seconds it takes to run with a number of iterations to create a single hash.
Here’s the program, the only requirement is that you’ve installed HashLib
in Lazarus using the OPM (Online Package Manager).
If you want to install it manually, here’s the GitHub link
program hashtest;{$mode objfpc}{$H+}uses
Classes,
SysUtils,
HlpIHash,
HlpHashFactory,
HlpConverters,
HlpSHA2_512,
HlpIHashInfo;function GenerateSha(password: String):String;
var
hash: IHash;
begin
hash := TSHA2_512.Create();
Result := hash.ComputeString(password, TEncoding.UTF8).ToString();
end;function GeneratePBKDF(password, salt: String; iterations: Integer):string;
var
BytePassword, ByteSalt: TBytes;
PBKDF2_HMACInstance: IPBKDF2_HMAC;
begin
BytePassword := TConverters.ConvertStringToBytes(password, TEncoding.UTF8);
ByteSalt := TConverters.ConvertStringToBytes(salt, TEncoding.UTF8);
PBKDF2_HMACInstance := TKDF.TPBKDF2_HMAC.CreatePBKDF2_HMAC(THashFactory.TCrypto.CreateSHA2_512(), BytePassword, ByteSalt, iterations);
Result := TConverters.ConvertBytesToHexString(PBKDF2_HMACInstance.GetBytes(64), False)
end;var
shaCount, pbkdfCount, startTick, stopTick, iterations: Integer;
begin
shaCount := 0;
pbkdfCount := 0; WriteLn('Generating SHA-512 hashes');
startTick := GetTickCount64;
stopTick := startTick + 1500;
while GetTickCount64 < stopTick do begin
GenerateSha('password');
inc(shaCount);
end; WriteLn('Generating PBKDF2 hashes');
startTick := GetTickCount64;
stopTick := startTick + 1500;
while GetTickCount64 < stopTick do begin
GeneratePBKDF('password', 'salt', 1);
inc(pbkdfCount);
end; WriteLn(format(#10 + 'SHA : %d hashes generated in 1.5 second', [shaCount]));
WriteLn(format('PBKDF2 : %d hashes generated in 1.5 second', [pbkdfCount])); iterations := 450000;
WriteLn(format(#10 + 'Generating PBKDF2 with %d iterations', [iterations]));
startTick := GetTickCount64;
GeneratePBKDF('password', 'salt', iterations);
stopTick := GetTickCount64;
WriteLn(format('Took %d ms', [stopTick - startTick]));
end.
Go ahead and copy/paste that into your IDE. It looks a lot messier here on Medium.
It’s a pretty straight forward program. We have two functions that generates SHA-512 and PBKDF2 hashes.
In the program begin/end block we’re just using while loops to generate hashes like this.
while GetTickCount64 < stopTick do begin
GenerateSha('password');
inc(shaCount);
end;
We generate a hash, increment the shaCount variable and if we haven’t run for more than 1.5 seconds we do it again.
For PBKDF2 it works the same way
while GetTickCount64 < stopTick do begin
GeneratePBKDF('password', 'salt', 1);
inc(pbkdfCount);
end;
We’re also including a salt and iterations is set to 1.
And lastly we generate a PBKDF hash with 450000 iterations, and output the time it took.
iterations := 450000;
WriteLn(format(#10 + 'Generating PBKDF2 with %d iterations', [iterations]));
startTick := GetTickCount64;
GeneratePBKDF('password', 'salt', iterations);
stopTick := GetTickCount64;
WriteLn(format('Took %d ms', [stopTick - startTick]));
And when you run the program, you get something like this.
On this particular laptop I generated 314727 SHA-512 hashes in 1.5 second.
With PBKDF2 I could only generate 175329 hashes in 1.5 second.
But when I use the iteration setting with 450000, it took exactly 1.5 second to generate a single hash!
That’s powerful because even if someone was to get a hold of your data, your salt, your logic for the hash, they still have to use a very heavy algorithm to generate every hash they want to test.
So the process now goes something like this
// Prep for hashing
BytePassword := TConverters.ConvertStringToBytes(password, TEncoding.UTF8);
ByteSalt := TConverters.ConvertStringToBytes(username, TEncoding.UTF8);// Perform hashing
PBKDF2_HMACInstance := TKDF.TPBKDF2_HMAC.CreatePBKDF2_HMAC(THashFactory.TCrypto.CreateSHA2_512(), BytePassword, ByteSalt, 450000);// Convert to hex
hashedPassword := TConverters.ConvertBytesToHexString(PBKDF2_HMACInstance.GetBytes(64), False);// Perform database lookup
Query.SQL.Text := 'select userId where username = :username and password = :hashedPassword';Query.Prepare;
Query.Params.ParamByName('username').AsString := username;
Query.Params.ParamByName('hashedPassword').AsString := hashedPassword;
Note the prepared statement. See my article on SQL injection if you’re wondering why prepared statements are the bee's knees.
There you go, you now know how to safely hash passwords in your application.
I used FreePascal for this article but PBKDF2 is available in most languages. Here are links with more info for some of the popular languages out there
- JavaScript
- Java
- C/C++
- Python
- And many more.
If you like this article, consider giving it a clap and a share, it really helps me out.
If you have questions or ideas for articles, feel free to connect on LinkedIn