Secure Passwords sans SSLPosted on January 6th, 2010 14 comments
I use a lot of webapps that don’t use SSL. I don’t think this is necessarily bad – SSL costs money, is slower, introduces development and IT headaches – but I do worry that I’m sending my password in cleartext.
I set out tonight to offer a solution for that. I haven’t tried to work it into any existing plugin, but just the links and the code should suffice for someone who doesn’t want to let passwords go readable across the wire.
I decided that I would send an HMAC using the SHA-256 encrypted password as the key, and the username and a unix style timestamp concatenated as the data. To make this work I dropped password salts. I haven’t decided if it’s safer to send the salt to the user or just to drop it. Hints in this area would be appreciated.
To start things out, I created a Rails app (using 2.3.4, if you’re curious) with a User model that had a login and password field. I meant for the login to be, well, the login, and the password to be a SHA-256 hexdigest of the actual user’s password. If you adapt this code to work with RestfulAuthentication, which handles the password digests nicely, you’ll have to solve the salt problem mentioned above either by dropping it or sending it to the client.
Here’s how I set up my user:
Then, I needed a login form. Here’s the relevant HTML:
As you can see, as soon as a user submits the form, I hash the password and create the digest, and then blank the password field out. Obviously that last part is important, because otherwise you’re sending the user’s password as cleartext.
This code is more of a jumping off point than a landing place, but here’s what my (trivial) action to login looks like:
Essentially this goes, “ok, if you took a hash of your password locally and then used that to hash whatever timestamp you received and your username, and I get the same result on my end, it’s really likely that you’re the right user.”
Now, this is almost secure, but not quite. There’s still nothing preventing someone from re-posting the same data and logging in. This is just as bad as simply sending the digested password over to the server. I suggest (if you’re not using SSL), sending neither the password or the digest of the password itself over the wire.
The benefit of using the timestamp is that you can let the client send 1-time valid digests. So, I added a new model to my Rails app, LoginRequest, consisting of a timestamp attribute, and a User has many of them.
Here’s what the more secure version of authenticate looks like:
Now, even if a bad guy snoops everything you sent to the server, he (or she!) can’t reuse it, because that timestamp’s been used. Changing the timestamp should significantly change the hash, and without the secret information necessary for the encryption (the password hash), it’s not feasible to guess.
Also, brute forcing against a single timestamp isn’t an option, because even *failed* login attempts are registered as a LoginRequest. You get one try per timestamp to login.
If you wanted to, you could even put a check in the authenticate method that required that the timestamp be from the last day or some other arbitrary time limit.
So, if you aren’t storing important data, and for some reason can’t use OpenID, this seems to be a candidate for at least keeping passwords from going out in the open. I’m pretty sure there’s some hole in it that I haven’t seen, so if you see something, please put it in the comments so I might fix it. Thanks!
* Note: I used OpenSSL’s HMAC functionality over ruby-hmac because (1) the author of ruby-hmac suggests as much and (2) Nathaniel Bibler showed significantly better performance from the OpenSSL implementation here: http://blog.nathanielbibler.com/post/63031273/openssl-hmac-vs-ruby-hmac-benchmarks
You would want to make sure the timestamp wasn’t in the future. Because you only allow one login attempt per timestamp, someone blasting your server with invalid future logins could keep the real user from logging in for a period of time.
You miss a major point: SSL allows users to ensure server identity…
I see some issues with this:
The first rule of cryptography is that you don’t create a cryptographic scheme unless you are a real cryptographer.
The mechanism presumes that you are storing passwords in clear text on the server side, which is a massive no-no. You should never store passwords in the clear, or even as a simple hash. You use salts for good reason: they prevent simple lookups of passwords using an MD5/SHA rainbow table.
Also, if the data passed from client to server is intercepted, everything is provided to enable off-line brute-force recovery of the password. The message-portion of your HMAC consists of the username and timestamp, which is passed in the clear, so I know half the equation. Using the SHA-256 of the password as the key does nothing to improve things, except for adding a minute computational exercise to produce a potential key.
I wouldn’t waste my time trying to figure out a way to securely pass a salt to the client. You’re not going to without implementing public-key encryption.
The solution is:
1. Be good, and salt&hash your user’s passwords when you’re storing them in a DB.
2. Use SSL to encrypt the authentication.
3. If you are worried about brute-forcing passwords, then rate-limit logins or set up account lockouts after X number of failed attempts.
IamJosh did something similar using RSA encryption by creating a public/private key, and encrypting the password with the public key, while storing the private key in session (make sure you use EncryptedCookieStore if you are using CookieStore!). See: http://iamjosh.wordpress.com/2008/03/18/encrypting-login-password-without-ssl-in-ruby-on-rails/
Don’t forget, the standard HTTP way to authenticate without sending passwords in the clear is HTTP Digest Authentication (RFC2617)
Also is similar to WSSE used for ATOM authentication (http://www.xml.com/pub/a/2003/12/17/dive.html). This is an extension to HTTP authentication that hashes the password with a nonce sent in a challenge from the server and the current date. It does require the password to be stored on the server in plaintext.
But thanks for pointing out HMAC and sha256 plugin for jquery.
Why would you want a way to securely pass salt to clients? Salt is just an arbitrary random value, stored unencrypted in the database. There’s no reason in treating salt as secret: it is not.
Oh, and one important note: you shouldn’t use cookie session stores with non-SSL authentication. The problem is that man-in-the-middle can intercept cookie snapshots and reuse the same stamp/message again for authenticating a different browser. To be sure no one can ‘rollback’ session like that you must use database store.
Simple, not only you send the password in plaintext for anyone to see, but the authentication is rejected every time, so it’s likely the user will try again (just in case it’s password wasn’t stolen already).
But don’t expect it to be as secure as SSL. SSL is SSL for a reason.
2 Trackbacks / Pingbacks
[...] Secure Passwords sans SSL [...]