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?