Secure your users' passwords from the browser on
The user enters their password, but you send a PBKDF2 generated key based on a pre-chosen shared salt, their password and (perhaps) their username (to make the salt unique per user).
The code is at the bottom, just scroll down.
Instead of sending the user's password to your server, send a proof of that password.
WARNING: It's important that your application salt be the same for iOS, Android, and the Web.
The goal of this is to send proof that the user provided their secret. If you change the salt or algorithm used between platforms, then the Secure Remote Password would only work on that one device.
What the User Enters
Note: The difference between using the app salt only or the app salt plus a user-specific salt (such as the username) is the difference between an attacker needing to create a single rainbow table, or one for each user. However, since you'll still be using PBKDF2, bcrypt, or scrypt on your server, it doesn't matter that much.
The Secure Remote Password
jamais deux sans trois
One thing we know about the universe is that if something has happened once, it is infinitely more likely to happen again.
Heartbleed showed us that no one measure is sufficient to protect us, or our users.
So what's the best way to keep a secret?
I used to work at a company where occassionally the boss would come into the software room and exclaim things like "Great news guys! We just got a contract with ACME corp! But remember, this is strictly proprietary so don't tell anyone, not even your wives."
There are two well-accepted philosophies, but that isn't one of them.
The best way to keep a secret is to never know it in the first place.
Dead men tell no tales.
However, it turns out that the second isn't actually true.
So... Philosophy 1 it is.
Never take a user's password!!!
How do you verify a secret you don't know?
It seems like this should be rather obvious, because it's already standard practice on the server.
DERIVED_KEY = HASH(SALT + PASSPHRASE)
The DERIVED_KEY is the hashsum (say SHA-256, for example) of the SALT (a long, unique value tied to the user) and the PASSPHRASE.
One immediate problem that you might see with this approach is that you're probably used to generating the SALT when you create a user and retrieving it during every login attempt.
The solution is to have a public SALT for your application (or for each tenant of your application) and join that to your user identifier.
SALT_2 = HASH(SALT + USERNAME) DERIVED_KEY = HASH(SALT_2 + PASSPHRASE)
Remember, the purpose of the SALT is to make it difficult to precompute a rainbow table.
It doesn't need to be secret, it just needs to be different for each user.
Allowing either USERNAME or EMAIL
In the case that you allow a user to login with either a USERNAME or an EMAIL, you have a couple of options:
Treat logins separate from accounts
Allow the user to have multiple logins that have access to the same account. When they update the passphrase for one login, ask them if they would like to also update the passphrase for the other logins linked to the account as well.
Store both DERIVED_KEYs
Only use an email
Simplify things and require a user to have an email address as their login, but be sure to allow them to pick a displayname / username for their account.
The Unicode Problem
See my UtahJS Web Crypto Presentation for details, but suffice it to say
"I'm a ☢ ☃ who speaks 中国语文, loves 𝄢 guitar, and plays in 💩!"
Since there is no standard way to convert a Unicode String to a UTF-8 TypedArray Buffer, I'm using Unibabel.js to perform the conversion in the code below.
The derive-key function, wrapped in a bow, just for you:
By AJ ONeal
Did I make your day?
Buy me a coffee