Some break-ins happened in your neighborhood. So, you decided to turn your house into a fortress. You fortified the walls, installed alarm systems on all the windows and bought the most expensive lock you could find for the front door. But, since you always lose your keys, you decide to leave the keys to your house under the welcome mat. How long do you think until you get robbed? Not very long, because you didn’t have any password policy.
If you have an application, you need to force your users to choose strong passwords. It is not just their problem if they use a weak password, it’s also yours. When their account gets hacked, they complain about your website. Your website gets bad press. Even if you made everything else secure, your users leave the keys under the welcome mat and make account compromise easy for hackers.
Now that you understand how important this is, let us discuss how your password policy should be.
What is a password policy?
The password policy is the set of rules that user passwords need to follow. Their job is to force users into choosing strong passwords that are hard to compromise. We will explore the recommended rules for a strict password policy in this article.
Force a character set
A character set (charset) is the set of characters from which the user can choose to build his password. To understand the importance of a big charset, let us do a thought exercise.
A user chose a password of 2 characters, and the 2 characters he chose are digits. You are a hacker who needs to find the password, and are allowed unlimited tries. How would you proceed?
To make sure to find the password, you would try every possible combination as follows:
00
01
..
09
10
11
..
..
99
One of these combinations must be the password we are looking for. And it would take us, at most, 10×10=100 tries to find it.
Now, we change the problem a bit. The user chose a 2 character password. But, this time, he chose the 2 characters from digits and lowercase letters. We again have unlimited tries to find the password. The combinations we would try look like the following:
00
01
..
09
0a
0b
..
0z
10
..
1z
..
..
zz
Here again, one of these combinations must be the password we are looking for. And it would take us, at most, (10+26) x (10+26) = 1296 tries to find it. That’s 12x more times when we only had digits to test.
The number of combinations needed only gets bigger when we increase the charset used. Thus, the password policy should force the user to use all possible printable characters.
We can split the printable characters into 4 groups:
- Digits: 0, 1, 2 …, 9
- Lowercase letters: a, b, c, …, z
- Uppercase letters: A, B, C, …, Z
- Special characters: ,;:/.?%$&”’#{([-|\_^@)]}+=…
So, when implementing the password policy, force the user into choosing at least one character from each charset. His password should contain at least one digit, one lowercase letter, one uppercase letter and one special character.
Minimum size
In the previous exercise, we calculated the complexity of cracking a certain website. We used the number of tries needed as a measure for the strength of the password.
The number of tries is a function of the size of the charset (S), and the number of characters in the password (n):
Number of tries = Sn
So, the bigger the size of the password, the harder it is to find the password. But, even with unlimited tries, past a certain password size, it becomes impractical to even try. It would take years and even millions of years to find a password if it is complex enough. Our password policy could thus benefit from a very big minimal password size.
But, the challenge is with users who still memorize their passwords. They cannot choose a very long password because then they wouldn’t be able to memorize it. So, there is a balance to be struck between security (big minimal password size) and convenience (small minimal password size).
NIST recommends setting the minimum password size to 8 characters for web applications. Coupled with a 94 charset size as we discussed earlier, this would constitute a strong password policy.
To guess a password of this strength, an attacker would need to perform an average of ( 948 ) / 2 login attempts. Let’s assume an average login request processing time of 300ms. A bruteforce attack of this size would take about 28993956 years to finish. This alone should discourage any attacker. And, with a proper anti-bruteforce protection on the login form, this attack is impossible.
Make it unlimited in size
As we saw earlier, the bigger the password the more secure it is. Some users out there are very security aware, and almost paranoid. So, don’t punish their discipline by limiting the size of the passwords you accept on your platform.
Since you always hash the user’s password (secure password storage, right?), you will always store the same length hash in the database anyways. So, a user choosing a very long cleartext password will not affect your storage capacity.
Don’t allow leaked passwords
We treated most password related rules already. But there is one case we still need to address.
What happens when a user chooses a password that respects the rules we defined earlier. But, that same password was used on another website that got hacked. That password got leaked, and hackers know about it. They will use that same password on your website to see if any of your users use it.
We call this attack credential stuffing. When usernames and passwords are leaked from a website, hackers will attempt to log into other websites with those leaked credentials. This will work for users who use the same password on many websites.
To protect your users against this attack, prohibit the use of leaked passwords on your website. You can use the haveibeenpwned API to see if a certain password is present in a password leak.
You don’t want to disclose your user’s passwords to a third party, like Haveibeenpwned. That’s why the API offers a search by range. Here is how it works:
- You hash the user’s password using the hashing algorithm SHA-1.
- You send only the first 5 characters of that hash to the Haveibeenpwned API.
- The API will respond with a list of password hashes starting with the 5 characters you sent.
- You check if the full hash is present in that list.
- If that is the case, then the password is present in the leaks and you should not accept it;
- If it is not present in the list, then the password is not present in any leak, and you can accept it.
This way, you check if the password is in leaks, without disclosing the password to the external API.
Implementing a password policy
Following is a full implementation of the function checkPasswordPolicy(password). It checks if a password respects the password policy:
import org.apache.commons.codec.digest.DigestUtils; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; public class PasswordManager { public static boolean checkPasswordPolicy(String password) { // Check the nimimal password length if (password.length() < 8) return false; // Check that the password has at least one digit, one lowercase, one uppercase and one special char String pattern = "((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[*!@$%^&(){}[]:;<>,\.?/~_+-=|\]).{8,})"; if( ! password.matches(pattern)) return false; String sha1_hash = DigestUtils.sha1Hex(password).toUpperCase(); String hash_prefix = sha1_hash.substring(0, 5); String hash_suffix = sha1_hash.substring(5); // Check that the password is not present in a password leak OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("https://api.pwnedpasswords.com/range/" + hash_prefix) .build(); Response response = client.newCall(request).execute(); String list = response.body().string().toUpperCase(); if (list.contains(hash_suffix)) return false; // We are sure the password respects password policy return true; } }
Where should I check the password policy?
You should check the password policy in all the places where you allow the user to define a new password:
- User creation
- Password reset
- Password change
What else should I be looking for?
You need to make sure all other password related subjects are taken care of:
- Put in place an anti-bruteforce protection on your login form
- Put in place secure password storage strategies (hash, salt, pepper …)
- Make sure your user account management system is secure
Don’t let them ruin your reputation
if you allow users to choose one letter password they will define one letter passwords for their accounts. Accounts on your website will get compromised by hackers, and your application gains the reputation of the insecure application. Even if you know it’s the customer’s fault if he got compromised, the Internet doesn’t care. And you know what they say, the Internet remembers. Once your application takes a reputation hit, it is hard to recover. So, take control over your own application’s security. Protect your users from themselves.
Leave a Reply