Helping users to create good passwords
This post is part of the series 'Password management'. Be sure to check out the rest of the blog posts of the series!
- How to store a password in a web application?
- How to implement Password reset feature in a web application?
- How to store a password on Windows?
- How to prompt for a password on Windows?
- Helping users to create good passwords (this post)
- Automatically log in a user on a website using the Credential Management API?
- How to avoid storing secrets in the source code?
I've written a lot about storing passwords in an application. This is the role of the developer to ensure there are no security issues in the storage of the passwords. However, this is the responsibility of the user to choose a password that won't be easy to break. I think you have all read this from xkcd:
xkcd: Password Strength (source))
Creating a good password is not an easy task. And creating one different password for every website is even harder. Password managers can help you with this task, but this is not directly the point of this post.
Let's see how to help the user creating a good password!
#Allow the user to use a strong password
✓ Allow the user to paste passwords. This will allow the user to use a password manager, which is a good practice as password managers allow the user to have a different and strong password for each service.
✓ Do not limit the password length to a low value. If a user wants to use a 30 character long password, let them do. They probably use a password manager or they have a very good memory!
✓ Do not limit the allowed characters. Anyway, on the server, you'll convert it to a byte array and hash it, so it shouldn't be a problem to accept any Unicode character.
#Display the strength/complexity of the password
The first thing you can do is to show the strength of the password they choose and advises to improve it. Multiple criteria are important such as the length, the variety of characters (letters, number, symbols), and the unicity of the password. Here's what it can look like:
Password strength indicator
There are lots of library for every language to compute the strength of a password. You can for instance use zxcvbn
(GitHub) from Dropbox which gives you a number from 0 to 4 for a given password. Or you can see the answers to this Stack Overflow question for other libraries.
#Check the password has not leaked
Have I Been Pwned
Ok, you have now chosen a strong password. Then, you need to check that this password is not on the list of leaked passwords. This means that it was used by you or another user on another service and the database of this service has leaked. Therefore, the password is not secure. The web service "Have I been pwned" contains a huge list of leaked passwords. You can query the API to know if your password is on the list. For security purposes, you don't need to send the password, just the 5 first characters of the SHA1. The API is free and you'll find the documentation on the website: https://haveibeenpwned.com/API/v2.
Security note: The password is not sent to the external website. Only the 5 first characters of the SHA1 are sent. Hashing a password is a one-way operation. You should not be able to find the password from the hash. In practice, this is possible using Rainbow tables or tools such as John the Ripper or hashcat. Have I been pwned only asks for the 5 first characters of the hash. A SHA1 is 160 bits long. The 5 first characters encode the first 20 bits of the SHA1. This means HIBP only has a 8th of the SHA1. With so little information you cannot find the actual password. If you are not familiar with password hashing, you can read my previous post about storing passwords in a web application.
Here's how to use it to validate a password in .NET:
private static async Task<bool> IsCompromisedAsync(string password)
{
var hash = ComputeSha1(password);
var hashPrefix = hash.Substring(0, 5);
var hashSuffix = hash.Substring(5);
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("User-Agent", "MySampleApp");
httpClient.DefaultRequestHeaders.Add("api-version", "2");
using (var response = await httpClient.GetAsync("https://api.pwnedpasswords.com/range/" + hashPrefix))
{
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
// The content contains a list of hashes that start with the prefix
// We now have to check if our hash is in the list
var lines = content.Split('\n');
return lines.Any(line => line.StartsWith(hashSuffix, StringComparison.OrdinalIgnoreCase));
}
}
}
private static string ComputeSha1(string value)
{
var bytes = Encoding.UTF8.GetBytes(value);
using (var sha1 = SHA1.Create())
{
var hash = sha1.ComputeHash(bytes);
return ToHexa(hash);
}
}
private static string ToHexa(byte[] bytes)
{
var Result = new StringBuilder(bytes.Length * 2);
const string HexChars = "0123456789ABCDEF";
foreach (byte b in bytes)
{
Result.Append(HexChars[b >> 4]);
Result.Append(HexChars[b & 0xF]);
}
return Result.ToString();
}
You can call IsCompromisedAsync
to validate a password is not compromised. Then, you can indicate to the user their password is not safe.
Your application now has a good user experience to create robust passwords!
Do you have a question or a suggestion about this post? Contact me!