Code Focused

Hashing Passwords for Fun and Security

Hashwords can use a pinch of salt for added security. There's a .NET class for that.

I kind of miss the old days of passwords. Spy One says, "The rock falls with a bang," and Spy Two responds with, "Snow can be brushed off your shoes." Why the Internet didn't adopt this standard is beyond me. And look how we suffer, with hackers releasing millions of consumer passwords.

The solution, as you've no doubt heard, is to create a securely scrambled hash of each password before storing them in your user database. Effective hashing systems are hard to create, but fortunately, they already exist in the .NET Framework, ready for you to use. Today I'll introduce you to the "PBKDF2-SHA1" hashing process, expressed through the .NET Rfc2898DeriveBytes class. This already looks promising, because even these names appear to be scrambled.

When you hash a password, you run the text of the password through a process that scrambles it into something consistent yet unrecognizable. It doesn't matter what the result looks like, but it must have a few key features:

  1. The same initial password must always produce the same scrambled output.
  2. Different passwords, even with minor differences or transpositions, must produce dramatically different results.
  3. The process must be irreversible.

The first feature, where a specific password always generates the same hashed result, is essential so that you can compare a user's initial password with one provided and hashed at a later point. But this raises security flags, because if Alice and Bob both use "password" for their passwords, their database records will contain the same hash. A hacker who figures out Alice's password now has free access to Bob's account.

To overcome such security lapses, salt is added to the password text before the scramble. The salt can be any text, but it must be different for each initial password. If Alice's salt is "ABC" and Bob's is "XYZ," their hashes will be different, because "ABCpassword" and "XYZpassword" will produce unique hashes.

Creating a random collection of salt-bytes is the first step in generating a hashed password. In .NET, the RNGCryptoServiceProvider class is a great tool for generating random, secure chunks of salt:

// ----- All article code assumes the following:
//       using System;
//       using System.Text;
//       using System.Security.Cryptography;
RNGCryptoServiceProvider saltCellar = new RNGCryptoServiceProvider();
byte[] salt = new byte[24];
saltCellar.GetBytes(salt);

The size of the salt can be whatever you want, but I find 24 bytes to be a nice non-round number. With this salt in hand, it's time to produce the actual hash by passing the salt and the new user-supplied password through the Rfc2898DeriveBytes class features:

Rfc2898DeriveBytes hashTool = new Rfc2898DeriveBytes(password, salt);
hashTool.IterationCount = 1000;
byte[] hash = hashTool.GetBytes(24);

Hashing is an iterative process, and more iterations produce increasingly scrambled results. Iterating 1,000 times generates nice results, but you can choose another value if you want. Whatever you choose, you must keep track of both the salt and the iteration count. You'll need both values later when it comes time to verify a user's password. I like to bundle up the hashed password, the salt and the iteration count into a database-friendly string:

string databaseStoredPassword = "1000:" +
  Convert.ToBase64String(salt) + ":" +
  Convert.ToBase64String(hash);

That's just a few lines of code to generate and store a secure password hash, which is great. The process of validating a password provided by a returning user, or even by a hacker, is just as easy, and uses the stored iteration count, salt, and hashed password values:

string[] hashParts = databaseStoredPassword.Split(':');
int iterations = Int32.Parse(hashParts[0]);
byte[] originalSalt = Convert.FromBase64String(hashParts[1]);
byte[] originalHash = Convert.FromBase64String(hashParts[2]);

Now that you have the original salt and iteration count, you can use those values and the proposed password to generate a new hash, one that will be compared to the database-stored hash:

Rfc2898DeriveBytes hashTool = new Rfc2898DeriveBytes(testPassword, originalSalt);
hashTool.IterationCount = iterations;
byte[] testHash = hashTool.GetBytes(originalHash.Length);

The only thing left to do is to compare the original and proposed hashes. While you can walk down the byte arrays and compare each element until you find a mismatch, it turns out that this is a lousy idea, as it opens up the process to so-called timing attacks. It might take only a millisecond to determine that the first hashed character is incorrect, but ten milliseconds to find a failure farther into the text, and a hacker could monitor these timing variations. To prevent such attacks, every character in the password must be tested every time:

uint differences = (uint)originalHash.Length ^ (uint)testHash.Length;
for (int position = 0; position < Math.Min(originalHash.Length,
  testHash.Length); position++)
  differences |= (uint)(originalHash[position] ^ testHash[position]);
bool passwordMatches = (differences == 0);

Generating salted, hashed passwords will go a long way toward securing your user accounts. It isn't a panacea, because it doesn't address the human factor in password selection. But by converting Bob's and Alice's admittedly simple passwords into something scrambled and complex, it will prevent access to a password table from being the weakest link in the security chain.

About the Author

Tim Patrick has spent more than thirty years as a software architect and developer. His two most recent books on .NET development -- Start-to-Finish Visual C# 2015, and Start-to-Finish Visual Basic 2015 -- are available from http://owanipress.com. He blogs regularly at http://wellreadman.com.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube