JavaScript One-Time Passwords
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.
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.
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.
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:
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.