Switch On The Code RSS Button - Click to Subscribe
Jul
22

Secure Authentication Without SSL Using Javascript

Everybody loves the Web 2.0 style of no page loads and seamless transitions (almost everybody). But there are some security limitations placed on the xmlhttp callback objects that make life a little difficult sometimes. For instance, if your page originated on the server http://blog.paranoidferret.com/, you are only allowed to use the xmlhttp request object to request data from http://blog.paranoidferret.com/. If you try and request data from https://blog.paranoidferret.com/, the request will throw an exception and you'll get nowhere.

So, for instance, if you want to have your users authenticate over ssl, you either have to do a page reload, or your page has to be on ssl to begin with. Neither of those options is particularly attractive - doing a page reload destroys that nice illusion of seamlessness, and having the page on ssl all the time is a lot of overhead server-side.

A third option is to come up with a scheme to secure the data that needs to be secured without having to use ssl. And that is what we are going to take a look at today. Here, we are going to go over a (relatively) secure method of exchanging passwords over an unsecured http connection with only a little bit of extra work on the client and server side. First, I'm going to discuss how it will work in general (and what its main flaw is), and then I'll go over its implementation in code.

There are two main stages to this system: registration and authentication. The registration part is pretty simple. On the client side, the user will enter a password, and we will take the hash of that password. That hash will be transmitted to the server, where it will be stored (probably in a database).

Authentication is a little more complicated, but its not too bad. When the user goes to authenticate, the server creates a random number, stores it (probably in the session), and sends it to the user. The user will enter their password and it will be hashed. This hash will then be concatenated with the random number and hashed again. This double hash will then be transmitted to the server. On the server side, the server pulls out the password hash from the database and the random number from the session, concatenates them, hashes that value, and then compares that value with the value transmitted from the client. If the two values match, then we can authenticate the user.

The flaw here is with registration. If someone catches the registration transmission, they can get that original password hash. With that hash, they can probably manage to authenticate against the server, thereby impersonating the original user. They can't, however, ever get the user's actual password - because that value is never transmitted over the wire. The server doesn't even know it, it only knows that hash. And the beauty of a hash is that it is impossible to get the original value back out (if you want more info on how cryptographic hashing works, take a look at the Wikipedia article).

In many cases, this flaw is acceptable, because this risk of user impersonation is not a problem (i.e., perhaps there isn't any 'private' content on the site). In fact, if there is private content, then you should probably be using ssl anyway, so that that content is secured from eavesdroppers. But in the case where the only thing that really needs to be secured is the password itself, this method is perfectly acceptable (because, again, there is no way to get the original password).

So now that we know the method behind all of this, lets take a look at how we can actually implement it. Here, the client side work will all be done by javascript, and the server side will be in php. First things first - we need a hashing function. Now lucky for us, php already has a number of hashing functions, including the one we will be using - SHA1. Javascript, on the other hand, does not. Fortunately, a number of people have written SHA1 hashing functions for javascript, and we will be using one of them.

The following javascript SHA1 implementation is courtesy of Chris Veness and is licensed under the LGPL.

function sha1Hash(msg)
{
    // constants [§4.2.1]
    var K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6];


    // PREPROCESSING
 
    // add trailing '1' bit to string [§5.1.1]
    msg += String.fromCharCode(0x80);

    // convert string msg into 512-bit/16-integer
    // blocks arrays of ints [§5.2.1]

    // long enough to contain msg plus 2-word length
    var l = Math.ceil(msg.length/4) + 2
    // in N 16-int blocks
    var N = Math.ceil(l/16);             
    var M = new Array(N);
    for (var i=0; i<N; i++) {
        M[i] = new Array(16);
        // encode 4 chars per integer, big-endian encoding
        for (var j=0; j<16; j++) { 
            M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) |
                          (msg.charCodeAt(i*64+j*4+1)<<16) |
                          (msg.charCodeAt(i*64+j*4+2)<<8) |
                          (msg.charCodeAt(i*64+j*4+3));
        }
    }
    // add length (in bits) into final pair of 32-bit integers
    // (big-endian) [5.1.1]
    // note: most significant word would be
    // ((len-1)*8>>> 32, but since JS converts
    // bitwise-op args to 32 bits, we need to simulate
    // this by arithmetic operators
    M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32);
    M[N-1][14] = Math.floor(M[N-1][14]);
    M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;

    // set initial hash value [§5.3.1]
    var H0 = 0x67452301;
    var H1 = 0xefcdab89;
    var H2 = 0x98badcfe;
    var H3 = 0x10325476;
    var H4 = 0xc3d2e1f0;

    // HASH COMPUTATION [§6.1.2]

    var W = new Array(80); var a, b, c, d, e;
    for (var i=0; i<N; i++) {

        // 1 - prepare message schedule 'W'
        for (var t=0;  t<16; t++)
            W[t] = M[i][t];
        for (var t=16; t<80; t++)
            W[t] = ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1);

        // 2 - initialise five working variables
        // a, b, c, d, e with previous hash value
        a = H0; b = H1; c = H2; d = H3; e = H4;

        // 3 - main loop
        for (var t=0; t<80; t++) {
            // seq for blocks of 'f' functions and 'K' constants
            var s = Math.floor(t/20);
            var T = (ROTL(a,5) + f(s,b,c,d) + e + K[s] + W[t])
                          & 0xffffffff;
            e = d;
            d = c;
            c = ROTL(b, 30);
            b = a;
            a = T;
        }

        // 4 - compute the new intermediate hash value
       
        // note 'addition modulo 2^32'
        H0 = (H0+a) & 0xffffffff; 
        H1 = (H1+b) & 0xffffffff;
        H2 = (H2+c) & 0xffffffff;
        H3 = (H3+d) & 0xffffffff;
        H4 = (H4+e) & 0xffffffff;
    }

    return H0.toHexStr() + H1.toHexStr() + H2.toHexStr() +
                H3.toHexStr() + H4.toHexStr();
}

//
// function 'f' [§4.1.1]
//
function f(s, x, y, z)
{
    switch (s) {
    case 0: return (x & y) ^ (~x & z);           // Ch()
    case 1: return x ^ y ^ z;                    // Parity()
    case 2: return (x & y) ^ (x & z) ^ (y & z)// Maj()
    case 3: return x ^ y ^ z;                    // Parity()
    }
}

//
// rotate left (circular left shift) value x
// by n positions [§3.2.5]
//
function ROTL(x, n)
{
    return (x<<n) | (x>>>(32-n));
}

//
// extend Number class with a tailored hex-string method
//   (note toString(16) is implementation-dependant, and
//   in IE returns signed numbers when used on full words)
//
Number.prototype.toHexStr = function()
{
    var s="", v;
    for (var i=7; i>=0; i--) {
        v = (this>>>(i*4)) & 0xf; s += v.toString(16); }
    return s;
}

Now that we have the SHA1 hash in both php and javascript, we can go ahead and write the actual implementation. First off, the xmlhtttp callback function:

function doCallback(url, callbackFuntion)
{
  var xmlObj =null;
  if(window.XMLHttpRequest)
    xmlObj = new XMLHttpRequest();
  else if(window.ActiveXObject)
    xmlObj = new ActiveXObject("Microsoft.XMLHTTP");
 
  if(xmlObj == null)
    return false;

  xmlObj.onreadystatechange =
    function(){
      if(xmlObj.readyState == 4)
      {
        callbackFuntion(xmlObj.responseText);
      }
    }

  xmlObj.open ('GET', url, true);
  xmlObj.send ('');
 
  return true;
}

This function is just a simple wrapper around the xmlhttp object. It takes as arguments the url to callback to, and the javascript function to call with the callback result. It returns true or false depending on whether it is able to start the callback. A return of true does guarantee completion - it just means the callback has been started. We will be using this function to do the callbacks for both registration and login.

Now onto the code for registration client-side:

var loginURL = "http://someurl.com/somePathToLogin.php";

function RegisterClicked()
{   
  var name = document.getElementById('registerName');
  var pass = document.getElementById('registerPass');

  var url = loginURL + "?pass=" + sha1Hash(pass1.value)
      + "&name=" + name.value + "&Register";

  if(!doCallback(url, RegisterResponse))
    alert("Error!");
}

function RegisterResponse(responseText)
{
  alert("Registered!");
}

First off, there will probably be some sort of constant url to the php file on the server we will be calling back to. Here, that is a global variable called loginURL. The two functions following that variable are extremely simple. The first one, RegisterClicked(), would probably be hooked to some button or link on the page. Calling it just grabs the new user name and password from some text boxes on the page and creates a url. As you can see, it calls the SHA1 hash function on the password before it is even put in the url. This url is then posted back to the server, with the function RegisterResponse set to be called with the result. The RegisterReponse function, as it stand right now, does nothing, but in reality it should be checking the result to make sure there were no errors in registration.

Now lets take a look at the client-side code for logging in:

function LoginClicked()
{
  if(!doCallback(loginURL + "?Challenge", ChallengeResponse))
    alert("Error!")
}

function ChallengeResponse(challenge)
{
  var name = gID('loginName');
  var pass = gID('loginPass');
   
  var url = loginURL + "?name=" + name.value + "&pass="
    + sha1Hash(sha1Hash(pass.value)+challenge);

  if(!doCallback(url, LoginResponse))
    alert("Error!");
}


function LoginResponse(response)
{
  if(response == "Yay")
    alert("Logged In!");
  else
    alert("Login Failed.");
}

Here, the login process is broken into two major parts: getting the challenge value, and then the actual authentication. The function LoginClicked (which is probably called by some link or button on the page) immediately goes and requests a challenge value from the server. When the server responds, the ChallengeResponse function will be called, and the argument challenge will hold that challenge. Now, we can build the url - as we needed the challenge value to build the correct hash of the password. We callback using this new url, and the response goes to the LoginResponse function. And here , if the response is "Yay", apparently the server has successfully logged the user in.

I'm betting, at this point, you want to know what this all looks like on the server side. Well, lets take a look:

<?php
session_start();

/////
//Probably some database includes here
/////

if(isset($_GET['Register'])) //Register Block
{
  $user = User::createUser($_GET['name'], $_GET['password']);
  $user->save();
  echo "Registered!";
}
else if(isset($_GET['Challenge']))  //Challenge Block
{
  $_SESSION['challenge'] = mt_rand()."".mt_rand();
  echo $_SESSION['challenge'];
}
else  //Login Block
{
  $user = Database::getUser($_GET['name']);
  if(strtolower(sha1($user->Password.$_SESSION['challenge']))
       == strtolower($_GET['password'])
  {
    echo "Yay!";
  }
  else
  {
    echo "No!";
  }
}
?>

Now, there are a number of details missing here, mostly because theres no need to get into the details of php and databases. But the basics are here, and should make sense. So, based on the arguments on the callback url, we either enter the Register, the Challenge, or the Login block. For Register, we take the new name and password, create a "user" (something, perhaps, defined elsewhere in php code), and save that user to the database. For Challenge, we create a decently long random string, save it in the session, and shoot it off to the client. And then for Login, we get the user out of the database based off of the given name, and then see if the password is valid. We call strtolower on both hashes because both are hexadecimal strings, and it is possible that one string has the letters capitalized (like A3F1543ED287) while the other is lowercase (a3f1543ed287). Since they are equivalent hashes either way, we just lower the case on both so we can do a string comparison. And if they match, the password is valid, so we can authenticate the user!

And that about covers it. You won't be able to lift the code directly off this page and use it without modification - because you will need to write the database functionality for php and you will probably want to flesh out the communication between server and client so that errors are communicated more appropriately. But I hope you find the technique useful and easy to use. If you have any questions, feel free to leave them in the comments.



Posted in PHP, Javascript by The Tallest |

16 Responses

  1. josoroma Says:

    Excellent article, very nice!

  2. mcnd Says:

    All you need to avoid the problem with the registration is change the way the you do the authentication. Take the password, concatenate the random number, hash both and then send that hash.
    This way the only problem is with short passwords (which can be quickly bruteforced). To eliminate this problem, concatenate the password x times, until the length is greather than what you consider secure.

  3. Adriano Says:

    Hi! I am writing a game in Javascript (Silverlight). At the end, this script call a website and give the highscore.
    There is a problem with this method: You can pause the request (with Fiddler) and change the highscore. Is there a way to bypass this problem? At begin of the game, I send a code, at the end the game send it back, but a hacker can easly change the request and send a higher highscore! Thanks, Adriano

  4. MSDATE Says:

    function SignInOnClick(user,password)
    {
    var myform=document.createElement(”form”);
    myform.id = “LoginForm”;
    myform.name = “LoginForm”;
    myform.method=”POST”;
    myform.action=”https://MySite/LoginPage.aspx”;

    var userName = document.createElement(”input”);
    userName.type=”hidden”;
    userName.name=”UserName”;
    userName.value=user;
    myform.appendChild(userName);

    var userPassword = document.createElement(”input”);
    userPassword.type=”hidden”;
    userPassword.name=”Password”;
    userPassword.value = password;
    myform.appendChild(userPassword);

    document.appendChild(myform);
    document.forms[”LoginForm”].submit();
    }

    This will always work.

  5. MSDATE Says:

    This is a clean way of implementing as well.

  6. The Tallest Says:

    But won’t that reload the page?

  7. herzengel Says:

    Very nice code snippet. Well I will try that on my site wich needs a secure part of site for special users. THX

  8. Saad Says:

    That is really nice and kind of you, man! I am a beginner, and it is a very good read.

  9. Saad Says:

    Thanx man, it is a real good thing.

  10. Aaron Says:

    Neat, I came up with the same thing a year or so ago it is a very ingenious technique.

    It’s not full proof from Brute Force attacks because of the ability of (someone watching wire transmissions) to take the session random number then they can just use the same hash technique with a dictionary attack to get the transmitted password.

    If you are going to buy SSL for protecting passwords this solution is much better.

  11. Andrea Says:

    I find the complete implementation. I am not an expert and i have much problem to implement this…Thanks

  12. Cyril Says:

    WARNING this technique is not safe at all!

    Any javascript based encryption is non sense because if someone could intercept the communication, it could change the javascript code too (just imagine it change SHA(x) to return x)
    Then it does get the password, can authenticate on the server, and you’ll never see anything wrong!.

    The only solution is to use methods that are implemented directly in the browser (so they can’t be forged), and to my own knowledge, SSL is the only way.

  13. The Tallest Says:

    A good point, this technique does not protect you from man-in-the-middle attacks - it only protects you from sniffing attacks (like on an open wireless network).

    I wish that the root certificate for a place with free ssl certificates (like CACert) was actually included with Firefox/IE - there would be no reason then _not_ to use ssl.

  14. GrokCdoe Says:

    @The Tallest It is easy enough to generate a self-signed certificate. From an encryption perspective, it is every bit as safe as a certificate from a major certifying authority.

    What you are getting beyond that when using a certifying authority is some level of identity verification. The authorities attempt to verify the identity of the person or company that owns the domain.

    As to the article in general, I don’t think the inconvenience of reloading the page to enable SSL is worth using a much less secure JavaScript implementation. In any case, all you need to do is set up mod_rewrite to redirect http to https, and that problem is solved.

    Encryption is hard. Stick with the tried and true methods developed by experts.

  15. Henrik Says:

    From *purely* an encryption perspective, yes, a self signed certificate has at least as much strength than one from a CA, but unless the user on the other end has already been supplied a copy of your public key out of band, it’s a complete waste of time.

    There’s nothing stopping someone who’s MitMing you from using OpenSSL to spit out an “identical” (except for key, fingerprint, etc) self-signed certificate on the fly at the start of a session.

    The solution here is nice, but I’d move the actual authentication data into POST variables. Sure, it won’t stop someone from sniffing it, but it’ll keep out casual readers of proxy logs (assuming they can then be arsed reverse engineering it and brute-forcing.. it’s just another minor barrier).

    Either way, this isn’t a perfect solution (though even HTTP-HTTPS redirection can be MitMed - I mean, how many users would be able to explain to you how to tell if they were on a secure connection?), but it’s still streets ahead of the vast majority of sites out there still using clear text password forms…

  16. Aaron Says:

    I have been using this solution in scripts for years, I thought I was the only genius that came up with it. Now you have taken my thunder great job!

    Na I am just teasing ya, I have always liked this method never employed it into AJAX but I do use it when an SSL cert is out of the question.

    However I don’t see how SSL is much more secure than using this method only using an algorithm like base64 instead of sha1. But I guess I will educate myself on the difference’s at a later date.

    Great Read, great refresher.

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.

Powered by WP Hashcash