Earlier this year I gave a talk on Password Security at Digital Croydon #5, and didn't want to simply put up the slides without any commentary, so I've written this article to accompany them. It is primarily for the benefit of those that attended - by which I mean it wont explain every slide or go into detail on everything the talk covered, but will explain the key points, provide links, etc.
Similarly, as mentioned in the talk, it didn't cover everything there is to know, and this article wont do that either - it's an introduction, not a comprehensive guide.
(At some point I do hope to write up more detailed articles, both on the points covered and on related subjects - if/when that happens I will add the relevant links to this article.)
I use the term "passcode" in preference to "password", because thinking in terms of a word is the wrong approach. A single word is not a strong passcode, multiple should be used. I used to use "passphrase" but that's also wrong - whilst a phrase may be better than a single word, a line of nonsense is better - several words that do not belong to a known phrase. (More below.)
Avoid If You Can
If possible, avoid the need for user accounts (and thus passcodes). This isn't an option if correctly identifying users is important, but not all situations require user accounts.
For example, a customer support portal may not need user accounts for basic support or one-off requests.
Similarly, if all you're doing is storing preferences, default to using cookies. Allow users to create accounts if desired, but if linking preferences to a machine is ok then allowing just cookies will reduce the number of accounts needed, and thus present a smaller target.
Store Passcodes Correctly
Not storing as plaintext is obvious - nobody who isn't a complete novice would insert passwords direct into their database.
But there are other ways to store as plaintext - if you log errors and include the form post data in that log, unless you've excluded passcodes you'll be storing them plaintext. Strip them out before storing the data - and don't forget to do the same with credit card numbers and any other sensitive information!
Another overlooked method of putting passwords in plaintext is submitting forms over HTTP - whilst HTTPS is becoming increasingly common, it wasn't that long ago that many sites had login and registration forms submitting over HTTP, and thus were sending those details in plaintext for any intermediary to read. Going HTTPS is cheaper and easier than before.
A common perceived step-up from plaintext that isn't actually much of an improvement is thinking hashing will solve the problem - it doesn't.
MD5 is broken - do not use MD5 for anything but especially not passcodes.
SHA is designed to be fast - it wasn't designed for passcodes, don't use SHA.
Anything you try to write yourself - or any code you grab from some other developer online is unlikely to be secure. Don't try writing your own algorithms
There are three algorithms available that are suitable:
- bcrypt - Wikipedia | Paper | Homepage
- pbkdf2 - Wikipedia | Paper (PDF) | Specification
- scrypt - Wikipedia | Paper (PDF) | Homepage
There are advantages/disadvantages to each, but with suitable parameters they are all acceptable. Obviously where security is critical you should read up on each and make your own decision, but otherwise just use bcrypt.
Implementations of bcrypt are available for all major languages - for convenience the results of appropriate searching are included below, but, as with all security matters, verifying with relevant language/library curators is obviously prudent.
- C / C++ - crypt_blowfish.
- C# / .NET / Mono - BCrypt.Net.
- CFML - cfPassphrase (jBCrypt wrapper).
- Erlang - erlang-bcrypt.
- Haskell - bcrypt.
- Java / JVM - jBCrypt.
- Lua - lua-bcrypt.
- NodeJS - bcrypt.
- Perl - Crypt::Eksblowfish::Bcrypt.
- PHP - built-in password_hash since v5.5, use phpass with earlier versions.
- Python - py-bcrypt.
- Ruby - bcrypt-ruby.
- Swift / Objective-C - JFBCrypt.
Again, the above list is not endorsement or recommendation, merely the result of searches looking for the canonical or most popular library.
cfPassphrase is my own project, but it only wraps jBCrypt for easy use in CFML - i.e. it re-uses existing cryptographic algorithms, and so does not violate the "don't write your own" directive.
Correct storage is only part of the solution - it's important to educate users correctly on what makes a strong passcode - and the preconceptions that many developers have about this are either inaccurate or incorrect.
The most important factor in passcode strength is length. Longer is stronger.
But this is not just about character length - "aardvark" is three times longer than "ant", but they are both only one word long, thus neither is strong.
Likewise, "to be or not to be" is six words, but it's one phrase - one very well known phrase, made up of short common words - and thus is not a strong passcode. It is less weak than "ant", but that's not enough.
No single unit - nothing that appears on any list - is strong on its own, and the more frequently it appears, the weaker it is.
Whilst length is most important - twenty common units (whether characters, words, phrases) is likely to be more secure than a single uncommon unit - using uncommon, unpredictable units is still important.
The old recommendation for making uncommon units was letter substitution - swapping letters for numbers and symbols - but with the increasing speed of computers, and thus the number of attempts that can be made, this is no longer sufficient. There are only so many alternatives for each letter (and predictable substitutions are already on the lists password crackers use).
Minimum character length should be at least 8. Even with good storage, cracking passcodes of six chars or less is too fast.
As the user types their passcode, you feed it into the function this library provides and - based on the length and uncommonness of units - it works out how long it would take an attacker to crack, and converts this to a number which can be used to populate a strength value.
It also provides a breakdown of where that number comes from - i.e. it identifies common words, keyboard patterns, etc and determines how much they contribute in making the passcode stronger. This information can be used to give hints to the user, e.g. "don't use common names", or "a sports team and number is not secure".
Of course, make sure they are only hints and you're not giving away what the user tried to anyone looking over their shoulder.
Common Mistakes To Avoid
A very common practice is expiring passcodes on a regular cycle, each month or six weeks, or similar. The idea behind this is that if a passcode is compromised it's only good for a period of time.
Not only is that premise itself invalid (once an attacker has access, they usually don't need that long and can use privilege escalation vulnerabilities to avoid it), but research conducted by Microsoft discusses how this results in weaker user passcodes, and causes people to simply add a sequential aspect which is all they change each month and makes it trivial to predict what their next change will be.
If an attacker gains access to the database containing passcode hashes, all passcodes need to be expired as soon as possible, so they can be changed before an attacker has a chance to crack them. (And if users do not change them within a suitable timeframe, the accounts should be locked.)
How Users Can Protect Themselves
Users need to assume that developers are incompetent and not storing their passcodes correctly. Assume that every site you visit is storing your details in an insecure manner which can be attacked at a rate of 200 billion every second.
Never use anything less than eight characters as an absolute minimum - twelve or more characters is better, so long as its not a single word.
One method to create a long but memorable password is to pick five or six uncommon words at random, put them into a made up nonsense phrase that will not appear on anyone's list. Use uncommon misspelling plus mixed punctuation to make it even less predictable.
You can use the six letters that start each word as a written reminder - they should be enough to jog the memory without giving away the words to anyone who finds the written copy (write on paper, not on any Internet-connected device).
Block third-party JS and adverts
It is trivial, without triggering a browser's cross-site scripting protection, to redirect a form to a different location, and thus have users providing their login details to someone else. (I have code that proves this, and I'll be writing it up for a future article as soon as I have time.)
Whilst you might trust the site in question, you also need to trust everyone whom that site trusts, and then everyone that they trust, and so on - it doesn't take long in a chain of trust to find someone making a mistake and trusting the wrong person. Why take the risk?
You can use uBlock Origin
to block scripts from most likely to be malicious sources, or you can use
NoScript or uMatrix to block all scripts and gradually whitelist
only the ones you need.
Note: This page previously recommended Ghostery and AdBlock Plus, which turned out to be a mistake - neither plugin can be trusted and should be replaced with uBlock Origin.
Complain about bad practices
Education goes both ways - whilst most users pick weak passcodes, it's equally true that most developers don't know how to store passcodes securely - so if you find a service that isn't doing it right, you need to complain - both to protect yourself and fellow users.
These are numerous signs that passcode security is not taken seriously - if length is limited, any punctuation or symbols are disallowed, you ever see your passcode in plaintext, if they can be reset with public information (like your mother's maiden name), etc.
Some companies/developers claim they are doing things securely when they're not (or have some parts secure whilst others are wide open), but they're probably not doing it deliberately - don't just tell them they're wrong, give them the information to help them understand why and how they can fix it. If they still ignore you, let them know you'll go public if it's not fixed - bad PR may be a bigger motivation than protecting users.
I hope this has been helpful, and one last tip: if you're unsure about anything involving passcodes or security, don't assume the first person you ask is correct, or even the tenth person - there's plenty of well-intentioned but incorrect information out there - so look for qualified experts: cryptographers or security specialists with clear credentials.