How Not To Store Passwords (and what to do)

      No Comments on How Not To Store Passwords (and what to do)

This post is a summary of a presentation I made before the Atlanta Foxpro Users Group on Tuesday, January 22nd 2013. You can download the code samples here:

Over the years and years of my career, I think I’ve made every mistake possible, at some point. We’re going to talk about how to improve our practices in storing user passwords, by looking at various approaches that start to approach better practices.

Remember that security is a process, not a destination.

No security
Here’s the old way of doing things. Storing the usernames and passwords in the same table, in clear text. If someone hacked your system, they’d have access to your system and it is possible that you’d never know what data was compromised. Don’t do this anymore. As of today, you know better.

Notice that you have the typical users that are using the absolute worst passwords ever conceived. This should send a shudder down your back. Another security procedure, is to not allow your users to use the most popular passwords.

Storing Passwords As Hashed Values

Thanks to Gilles Patrick, we have a native VFP access to the MD5 hashing process. Let us take our first small step. The difference between encryption and hashing, is that you can’t get the original values back from a hashed value. The hashed value just is a unique representation of a string no matter if that string is long or short.

MD5 Passwords In this adjustment to our original table, we have the hashed value of the password. We could then drop the password field. To log in users, we would find the user record, and see if the hashed value of the password entered on the login form, matches the hashed value stored in the table. If so, let the user in.

oMD5.tohash = c_User2.cpassword
REPLACE cmd5 WITH oMD5.Compute()

While this approach is a small bit better than storing passwords in the clear, it isn’t a good approach. The problem is that there is such a thing as rainbow tables. You can go out on the Intertubes and download a rainbow table of MD5 values and their corresponding passwords. This is the way to back into the passwords. Not only that, but you can test all of the passwords in a single pass. (More on this later.)

Hashing Passwords with Multiple Passes

So if storing passwords as hashed values is bad, can we improve things by making multiple passes? Yes, it’s better. In the example code, I hash the password, and then hash the hash value.

lcPassword = c_User3.cPassword
FOR lnX = 1 TO 3
  oMD5.tohash = lcPassword
  lcPassword = oMD5.Compute()
REPLACE cmd5x3 WITH lcPassword

Multiple Hashing Passes

Notice that this approach makes it more difficult to use a rainbow table. You’d need a rainbow table that has values that have the exact same number of iterative loops. Also note, that you can always INCREASE the number of loops at any time and still retain the efficacy of the stored passwords. You can increase the number of loops from 3 to 6 or 60, just by replacing the passwords with the hashed value of nNewLoopValue – nOldLoopValue. (Example: 60 – 3)

This approach still isn’t satisfactory. You can still test against the entire database in one pass. It’s possible that there are rainbow tables that have your iteration values. We can still do better.

Storing Passwords With Salted Hashes

A ‘salt’ is introducing a value in addition to the original password. This salt value is unknown to the user. In fact, the user does not need to know the salt. It helps protect the passwords on the back end of the database. So, if a user has the awful password of “Password123”, then the salted value could be “AFUG” + “Password123” or “Password123” + “AFUG”.

lcPassword = lcSalt + c_User4.cPassword
FOR lnX = 1 TO 3
  oMD5.tohash = lcSalt + lcPassword
  lcPassword = oMD5.Compute()
REPLACE cmd5Salt WITH lcPassword

Salted Hashed Passwords

Note that if you feel that your database was compromised, you could change your salt, zero out all passwords thus forcing all users to enter a new password, hopefully re-securing your user table and system. At this point, you can start to feel more confident that you are safer from rainbow tables, however hackers can still attack the entire user table in one pass.

Unique salts per user, passwords stored in external table

What if we used a different salt per user? … plus a system wide salt? With the multiple passes?

lcPassword = c_User5.cSalt + c_User5.cPassword
FOR lnX = 1 TO 3
  oMD5.tohash = c_User5.cSalt + lcPassword
  lcPassword = oMD5.Compute()
INSERT INTO c_User5Password ( cMD5Salted, tLastUsed ) VALUES ( lcPassword, DATETIME() )

In addition to the above code, we don’t need to store the hashed value in the user table! If the hashed value is unique, there is no need to store it with the user values. We can then test the userid, get the salt, loop through the multiple hashed values, and then see if the result is stored in the hashed value tables. If the value exists after that process, then we have a good match between the user and password.

Salted external hashed

Oh yeah, don’t use MD5!

I used MD5 all over the examples because we have access to MD5 natively as VFP code. However, the MD5 process is too FAST! Hackers can make thousands of MD5 computations per second and attack your database faster and faster as computers get faster. There are other hashing techniques that are SLOWER, not GPU optimizable, and thus are more resistant to hacking attempts. Some of these approaches are BCRYPT and SCRYPT. There are plugins that can be purchased and integrated into your application. Also note that PHP (and others) have slower hashing approaches available to them, and thus is callable from within a VFP application to a PHP page that gets the hashed value back. (over SSL of course!)


While this post just scratches the surface of security, as a programmer it is time to make sure that we are taking advantage of the leading best practices of our day. If you refactor an old VFP application, take the time to update it’s security password mechanism. Use better practices.

Leave a Reply