JavaScript One-Time Passwords

go next top of page

The Problem

Suppose you have a password protected javascript based web-application. For some reasons, you cannot or do not want to use https connection for encryption. However the passwords should not be transferred in clear-text. One solution around this are one-time passwords.

go next top of page go back

The one-time password scheme

The core of the one-time password scheme is a one-way hash function. I use MD5 for this purpose. In principle, you can use any hash function you like. The algorithm takes the user password, a random salt and an iteration count:
function otp(password, iter, salt) {
  digest = hash(password+salt);
  while (iter-- > 0)
    digest = hash(digest+salt);
  return digest;
}
Here + denotes the concatenation of strings. In my scheme the digest is taken as binary. The passwords need to be used in descending order, i.e. first the password for iter=400 is given, then for 399, and so on. To check the validity of a password, the server needs to know the previous password. It can then check if the result of hash(password+salt) equals the previous one. On the other hand, an attacker would need to reverse the one-way hash to compute a valid next password. To prevent the iteration count to get zero, the client needs to regularly reset the password by generating a new salt, resetting iteration count, and sending both the current password and the password for the new salt and iteration count to the server. The same mechanism can also be used to change the password. go next top of page go back

Client-Server Communication

When the client wants to access the password protected service, it sends the username to the server. The server responds with the salt and iteration count for that user. For security reasons, the server responds similarly for unknown users, e.g. by generating a random salt and iteration count.

The client now computes the next one-time password and uses it to access the service. Now, it keeps track of the iteration count itself. When the counter is out of sync, e.g. because the user accessed the site from a different window, the password is not accepted and the server sends the current salt and iteration count, again. If the iteration count goes below some limit, the client generates a new salt and sends the server the new password, salt and iteration count.

go next top of page go back

Client-side code

I programmed the client side with the Google Widget Toolkit. This allows programming in Java and provides a compiler from Java to JavaScript. Of course, you can also program this directly into JavaScript. You can use this MD5 implementation in JavaScript. If you use gwt, here is an excerpt of my Java code:

 top of page go back

Server-side code

In my setting the server code is written in PHP. Here is the relevant part (I use json for communication):

$secret = "<some random characters>";
$authenticated=0;
if (isset($_POST["user"])) {
  $dbhandle = db_logon($dbhost, $dbuser, $dbpass, $dbdatabase);
  $user = $_POST["user"];
  $pw =  isset($_POST["pw"]) ? $_POST["pw"] : "";

  $query = "select password, seq, salt ".
           "from users where user=".db_quote($user);
  $cursor = db_query($query, $dbhandle);
  $row = "";
  if (db_fetch_into($cursor, $row)) {
    if (ereg("^[0-9a-fA-F]{32}$", $pw)
        && $row[0] == md5(pack("H32", $pw).$row[2])) {
      $authenticated = 1;  
      if (isset($_POST["nsalt"]) && isset($_POST["nseq"])
          && isset($_POST["npw"])) {
         $update="password=".db_quote($_POST["npw"]).
	         ",seq=".db_quote($_POST["nseq"]).
	         ",salt=".db_quote($_POST["nsalt"]);
      } else {
         $update="password=".db_quote($_POST["pw"]).
	         ",seq=".db_quote($row[1]-1);
      }
      db_exec("update users set ".$update.
              " where user=".db_quote($user), $dbhandle);
    } else {
      $seq = $row[1];
      $salt = $row[2];
    }
  } else {
    $rand = md5($user.'!'.$secret);
    $seq = ("0x".substr($rand,0,2)) + 145;
    $salt = substr($rand,4, 16);
  }
  db_close($cursor);
  if (!$authenticated) :
    echo "{\"auth\":false,\"seq\":".$seq.",\"salt\":".json_quote($salt)."}\n";
    exit;
  endif;
} else {
  // User was not specified.  Anonymous access, if allowed
}

If the field user is set, the server gets the entry from the user database. If the user exists in the database and the password is correct, the server needs to update the entry to invalidate the old password. Normally it decrements the sequence counter and stores the given password as new hash. If npw, nseq, and nsalt parameters are specified by the client, the server uses them to set a new password.

If the user does not exists, we generate a random seq and salt. This is done in a way that generates a stable id for each user without giving out information that the user does not exists. Of course, you need to make sure, that seq and salt look similar to those of valid users.