ruby! food! kids! … and other fun from terry heath
RSS icon Email icon Home icon
  • Secure Passwords sans SSL

    Posted on January 6th, 2010 terry 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.

    Prerequisites for this are jQuery and the ridiculously-easy jQuery sha256 plugin.

    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:

    The meat and potatoes of the authentication goes in the User model, and the code looks painfully similar to its Javascript counterpart:

    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

     

    12 responses to “Secure Passwords sans SSL” RSS icon

    • 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.

      It seems to me a better solution would be to just use javascript to mimic public/private key encryption. There are js implementations of RSA public key encryption, so if the server just sent over its public key in the javascript, then let the javascript encrypt the password with the public key, then once on the server it could use the private key to decrypt it.

    • Even with public/private key, I think you have the same issue of someone snooping what you sent and re-posting that.

      I think that your idea would shake out simpler, though, and you could use javascript to keep all things (even data) encrypted to/from.

    • You miss a major point: SSL allows users to ensure server identity…

    • I think, again, if you’ve got valuable information that needs to be protected and worry about MITM or server spoofing, you should put down the money for SSL.

      Something less important, like a fantasy football league, probably not so much.

    • 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.

    • @Mike
      1) The password is not stored as plain text on the server side, it’s stored as an SHA256.

      2) Brute forcing a hash is always a possibility. I don’t know of any encryption scheme that prevents brute force attacks.

      3) I think you’re missing the point of this post. It’s for when SSL isn’t necessary or wants to be avoided, but you want to keep a password more secure than sending it as plaintext to the server.

    • 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.

    • I notice that you don’t use salt in your database. In my solutions I use ajax to request salt for the user and then scramble it using salt and per-login secret value. This way I am protected from database leaks. Look at http://git.kitsu.ru/mine/quicknote.git in public/javascripts/users/login.js and other logic.

      But thanks for pointing out HMAC and sha256 plugin for jquery. :)

    • @mike
      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.

    • Can't you see the problem here ?

      What happends if javascript is disabled/unavaillable ?

      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).

      Like Donald Parish suggested, just use HTTP Digest Authentication. It’s the Standart Way That Works, and you can still use javascript to make it beautiful to your users if you want.
      But don’t expect it to be as secure as SSL. SSL is SSL for a reason.


    2 Trackbacks / Pingbacks