Bear with me while I rant about password security policies in websites.
Both of these password policies are bad. Example 1 is a better policy than Example 2, but they are both bad.
In the old days, long before Windows and the Web, systems still needed security. The controller of a company should have access to all financial data and the AP clerk should only have access to a small subset of the financial data related to accounts payable. Without security, the AP clerk and controller would have the same level of access and bad things could happen. So the designers of these systems created simple âUser IDâ + âPasswordâ login mechanisms. The controller had his login and the AP clerk had her login. In these days, processing power and memory were at a premium. The overhead of encryption was usually considered unnecessary. So they stored the User IDs and Passwords in unencrypted form in a database of some sort. Nobody could access the system except from one of their terminals, so this was adequately safe.
Now, letâs fast forward to today. Computers have a lot more processing power and a lot more memory. My primary servers are running multiple 6-core hyper-threaded processors with about 32 GB of memory per processor. This is not your typical home computer environment, but this is a fairly typical server environment. Even more so when youâre looking at database servers. This is an important point in terms of security. Notice, I donât even take into consideration storage because that is a moot point. Storage doesnât matter when it comes to data security (for the most part).
Some developers donât think too much about security. And I believe web developers are the worst offenders. Mostly because of how many web sites are designed insecurely every day. They may design their own login and store your password in a plain text format. If a hacker gains access to this database, they have instant access to all user passwords in this system. This is why you should never use the same password on multiple systems. If the developer thinks about it, they may encrypt the password before storing it. This makes the password somewhat harder to gain access to, but not hard enough. The server I discussed above would take a while to decrypt an encrypted password. However, you throw in a few high end video cards for GPU computing and the time it takes to decrypt a password drops frighteningly. In these cases, the passwords might as well not be encrypted at all. This encryption can be thought of like the lock on your front door, itâs only there to keep honest people honest. Do you want your bank storing your money behind a simple deadbolt or do you want it stored in a high end vault?
Thatâs why the above policies are bad. They scream out that the password is being stored in either plain text or encrypted format. The equivalent of either no lock or a simple pin and tumbler deadbolt. The real indicator is the upper limit on length with the secondary indicator being the limitation on characters that you can use.
In the first example â8 to 30â characters could simply be stating a âhardâ minimum and âsoftâ maximum. They may not be storing the password; they may just want to minimize network traffic by limiting the maximum length. This is fine, 30 characters is a relatively low maximum, but not horrible. What is horrible is the character limitation.
In the second example âsix to tenâ characters is a âhardâ minimum and a âhardâ maximum. This is not a method to save bandwidth. This combined with the character limitations really indicates storage of the password in a recoverable format.
Minimum requirements are good. They help keep stupid people from being too stupid. Iâm sure there are people who complain about the minimum requirements being too strict, I complain about the maximum requirements existing at all.
Ok, so maximum lengths indicate storage and thatâs bad. What about the character limitations? When you submit a form online, your browser will either URL encode or Base64 encode the values you type in. That means that you can type anything and the server will be able to receive it as it was typed. Even if you use characters like ?, %, &, <, or > which are all considered special characters in the Web. In the world of databases, characters like â, ;, %, and _ are all considered special. The % and _ characters are special in filters while â and ; are special in statements. If a developer does not escape special characters prior to sending them to a database server, you can end up with broken statements. This is one method hackers can use to gain access to a system. Starting a value with ; or â; can easily expose a field being sent directly to the database. Following the ; with a malicious statement can occasionally give the attacker access to the system. Thatâs bad. This is called SQL injection. Anyplace where there are character limitations on the input field (aside from format related restrictions like a phone number that should only contain numbers), you have to worry. If the dev was lazy, they may be missing processing somewhere and an attacker may gain access.
So storing passwords in a recoverable format is bad and sending unprocessed input is bad. Here are a few solutions.
First, when working with a database, use parameters. Somebody else spent a lot of time writing the code that processes parameters and formats their values in a safe way.
âUPDATE [User] SET [Password]=âMyNewPasswordâ WHERE [UserID]=âMyUserIDââ
@Password=âMyNewPasswordâ
âUPDATE [User] SET [Password]=@Password WHERE [UserID]=@UserIDâ
@PasswordHash=ComputeHash(âMyNewPasswordâ)
âUPDATE [User] SET [PasswordHash]=@PasswordHash WHERE [UserID]=@UserIDâ
Second, donât store the password in a recoverable format. You donât need it. The user can always reset later if they forgot it. You should never send a password thru email, thatâs just asking for trouble. What you should store is a hashed password. And not just a hash of the password, but a hash of the password with some value that only you (and your server) know. This second value is known as a âsaltâ and causes the hash to vary wildly from the known hashes. If someone figures out your âsaltâ value, they will be able to start cracking away at the passwords, but if they donât they will have a very hard time.
I canât go into details about hashing; itâs a complex mathematical process. Google âSHA-1â if youâre really interested in the process. Basically what hashing does is take any amount of data and converts it into a fixed size value. One of the more common routines is âSHA-1â which will convert any amount of data from zero bytes to hundreds of terabytes into a value consisting of twenty bytes. Those 20 bytes represent 160 bits of data. There are 2^160 (or more than 1,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000) possible values for these 20 bytes. The security is banked on this uniqueness. It is possible for two values to generate the same hash, but very unlikely. So instead of saying that your password is limited to 20 characters and storing the password in the database, you should be saying there is no upper limit on the password length and storing the 20-byte SHA-1 hash in the database. There are actually much more secure algorithms, but the SHA-1 hash is many times better than storing the password itself in any recoverable form. This SHA-1 hash would be similar to a bank's time-lock vault where they need the correct combination and key at the correct time of a specific date to open the lock on the vault.
Another advantage about hashes, you can expand them to hexadecimal to store in the database and know that they will never cause issues with SQL injection. Hereâs a brief example showing a lazy devâs code and the result:
âUPDATE [User] SET [Password]=ââ; DELETE FROM [User];â WHERE [UserID]=âMyUserIDââ
The dev didnât process the password and the first two characters of the supplied password will close the text value and end the first statement. This will set all passwords in the User table to ââ. Then the server starts processing the rest of the password as the second statement and attempts to delete all the userâs from the User table. Finally the server hits the rest of the original statement and throws an error. However, the damage has already been done.
@Password=âââ; DELETE FROM [User];â
âUPDATE [User] SET [Password]=@Password WHERE [UserID]=@UserIDâ
The dev processed the password and the parameter processing further protects the database from malicious intent. The userâs password is set to ââ;DELETE FROM [User];â if it fits. There should be no error, but the dev is storing the password and one misstep could cause problems.
@PasswordHash=ComputeHash(âââ;DELETE FROM [User];â)
âUPDATE [User] SET [PasswordHash]=@PasswordHash WHERE [UserID]=@UserIDâ
Not only did the dev process the password, he also hashed it into a harmless value. The userâs password is still set, but there is no chance of it ever causing problems in its stored format.
Hashes can still be used for enforcement of password history. Your hashing algorithm will always compute the same hash for the userâs password. When they try to change a password, you just compare the hashes (same as during a login event) to determine if they are reusing a password.
So, to sum up this incredibly long rant that started with my setting up an account with a bank, if you are a developer creating your own security system:
Donât store passwords in plain text.
Donât store passwords in a reversible encrypted format.
Donât store passwords in any recoverable format.
Donât limit the userâs ability to create insanely complex passwords.
Store a hashed value of the userâs password.
Add a salt value to the password before computing the hash.
Use more than one hash algorithm and combine the results to create a super hash.
Include the length of the password in the stored value.
See #1, #2, and #3 again.
If you can figure out the userâs password using the value you stored in your database, start over.
Items 7 & 8 take advantage of the fact that different algorithms will have different collisions and the collisions will likely have different lengths from the original password. Ok, I'm done ranting, what do you think?