URL of the article:

Issue: 05.2003
Secure Forms using Crypt_HMAC
Using HMAC Message Digests, we can authenticate a user is who they say they are, not just that their credentials are correct.
Davey Shafik
HMAC is similar in concept to systems such as PGP/GPG, except there is only one key, rather than a key pair. HMAC is used for message integrity checks between two parties who share a secret key. In this article, I put this to use within a website login scenario.

By being able to positively identify that supplied information is from the expected source, we make our applications much more secure. HMAC does not perform any hashing of the data itself, it is simply a standard way of padding a string before hashing it using an arbitrary hashing algorithm - for example MD5 or SHA-1. The full specification for HMAC can be found in RFC 2104.

It is worth noting that I have proposed an Auth_HMAC package to PEAR, which uses the principles in this article. Auth_HMAC is still (currently broken) alpha code, and has yet to be accepted into PEAR.

Why do we need HMAC?
The need for HMAC is the same as the need for SSL, security. Without taking these precautions, sensitive data is sent in plaintext making it is possible for any third party to acquire this data and use it how they wish. Consider this scenario, a user is confronted with a login form, a malicious third party wishes to gain access to the resource protected by the login form. To do this, they need to get hold of the user's username and password and then simply login themselves using the same form. This is done by means of packet sniffing, the 21st century version of tapping a phone line. Using HMAC or SSL, we make it much harder for the malicious party to obtain the data. With SSL the data is sent through a secure SSL encrypt tunnel, whilst with the HMAC system described in this article, the data sent is only good for a single login attempt, making it useless even if obtained.

Using Crypt_HMAC
Crypt_HMAC is one the many great packages within PEAR (the PHP Extension and Application Repository), written by PHP Magazine's Derick Rethans. We will be using Crypt_HMAC to perform our server side HMAC Digesting. Crypt_HMAC is incredibly easy to use; all we need to do is provide our secret key, the method for hashing and the plaintext string. Crypt_HMAC allows for the use of both MD5 and SHA-1. Listing 1 is a simple example of using Crypt_HMAC.

Listing 1

<?php

require 'Crypt/HMAC.php';
$string = "my plain text string";

/* Create our secret key and store it as a session
* variable */
$_SESSION['HMAC_key'] = md5(uniqid(mt_rand(), 1));

/* Instantiate our Crypt_HMAC object, we are
* using MD5 hashing */
$Crypt_HMAC =
new Crypt_HMAC($_SESSION['HMAC_key'], 'md5');

/* Create our message digest */
$HMAC_hash = $Crypt_HMAC->hash($string);
echo $HMAC_hash;
?>


About MD5 and SHA-1

MD5 and SHA-1 are algorithms to create reproducible hashes given the same data, whilst it is impossible to re-construct the original data with just the resulting hash. This makes them ideal for password systems, by storing the original password as a hash and then hashing the password used during login, we can then compare them and if they do not match, the password entered is incorrect. This means that even if the database is compromised, any passwords found cannot actually be used to login with. MD5 produces a 32 character, 128bit hexadecimal hash, whilst SHA-1 produces a 40 character, 160bit hexadecimal hash. The functions for MD5 and SHA-1 are md5() and sha1() respectively. SHA-1 is the more secure of the two methods, however it has only been part of PHP since version 4.3.0 whilst MD5 support has been around since PHP.

Client side Devilry
For this to actually work, we need our remote party to be able to send us a HMAC digest that we can compare with our server side version. To this end, we need JavaScript. Now, we're all aware of the drawbacks to JavaScript, the biggest of which is that a lot of clients actually turn JavaScript off completely; I will offer a simple (though not as secure) fallback for this later on in the article. Luckily for us, we don't have to write the huge amounts of JavaScript required to implement MD5, SHA-1 and HMAC itself, there are several versions available on the web, and you will find copies of all three on this issue's CD.

Creating the Digest Client side
Once we are able to create our digest on the client side, we actually have something we can actually compare in our integrity check. Listing 2 shows a basic example of create our digests on the client side.

Listing 2

<?php
session_start();
if(!isset($_SESSION['HMAC_key'])) {
$hmac_key = md5(uniqid(mt_rand(),1));
$_SESSION['HMAC_key'] = $hmac_key;
}
if(isset($_POST['submit'])) {
require 'Crypt/HMAC.php';
$hmac_key = $_SESSION['HMAC_key'];
$Crypt_HMAC = new Crypt_HMAC($hmac_key,'md5');
$user_pass = $_POST['HMAC_user'];
$user_pass .= $_POST['HMAC_pass'];
$HMAC_hash = $Crypt_HMAC->hash($user_pass);
}
?>

<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>
Creating clientside digests using HMAC and MD5
</title>
<!--
This is our HMAC JS file using MD5
hashing - use HMAC_sha1.js if you use sha1 in
your PHP
-->
<script type="text/javascript"
src="HMAC_md5.js"></script>
<script type="text/javascript">
/*
Take our serverside random HMAC_key
and store in a globally accessable variable
*/
window.HMAC_key =
'<?php
echo $_SESSION['HMAC_key'];
?>';
/*
Create HMAC Digest and show our digest
*/
function do_submit() {
var username =
document.getElementById('HMAC_user').value;
var password =
document.getElementById('HMAC_pass').value;
var user_pass = username + password;
document.getElementById('HMAC_hash').value=
hex_hmac(window.HMAC_key,user_pass);
}
</script>
</head>
<body>
<?php
if (isset($HMAC_hash)) {
?>
<p>
<strong>
PHP Generated Digest:
</strong>
<?php echo $HMAC_hash; ?>
<br />
<strong>
JavaScript Generated Digest:
</strong>
<?php echo $_POST['HMAC_hash']; ?>
</p>
<?php
}
?>
<form method="post"
action="<?php echo $_SERVER['PHP_SELF']; ?>"
onsubmit="do_submit();">
<p>
<input type="text" name="HMAC_user"
id="HMAC_user" />
<input type="text" name="HMAC_pass"
id="HMAC_pass" />
<input type="hidden" name="HMAC_hash"
id="HMAC_hash" />
<input type="submit" value="Login!"
name="HMAC_submit" />
</p>
</form>
</body>
</html
<?php
session_start();
if(!isset($_SESSION['HMAC_key'])) {
$_SESSION['HMAC_key'] = md5(uniqid(mt_rand(),1));
}
if(isset($_POST['submit'])) {
require 'Crypt/HMAC.php';
$Crypt_HMAC = new Crypt_HMAC($_SESSION['HMAC_key'],'md5');
$HMAC_hash = $Crypt_HMAC->hash($_POST['HMAC_user'] . $_POST['HMAC_pass']);
}
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Creating clientside digests using HMAC and MD5</title>
<!-- This is our HMAC JS file using MD5 hashing - use HMAC_sha1.js if you use sha1 in your PHP -->
<script type="text/javascript" src="HMAC_md5.js"></script>
<script type="text/javascript">
/*
Take our serverside random HMAC_key
and store in a globally accessable variable
*/
window.HMAC_key = '<?php echo $_SESSION['HMAC_key']; ?>';
/*
Create HMAC Digest and show our digest
*/
function do_submit() {
var username = document.getElementById('HMAC_user').value;
var password = document.getElementById('HMAC_pass').value;
document.getElementById('HMAC_hash').value = hex_hmac(window.HMAC_key,username + password);
}
</script>
</head>
<body>
<?php
if (isset($HMAC_hash)) {
?>
<p>
<strong>PHP Generated Digest: </strong> <?php echo $HMAC_hash; ?>
<br />
<strong>JavaScript Generated Digest: </strong> <?php echo $_POST['HMAC_hash']; ?>
</p>
<?php
}
?>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>" onsubmit="do_submit();">
<p>
<input type="text" name="HMAC_user" id="HMAC_user" />
<input type="text" name="HMAC_pass" id="HMAC_pass" />
<input type="hidden" name="HMAC_hash" id="HMAC_hash" />
<input type="submit" value="Login!" name="HMAC_submit" />
</p>
</form>
</body>
</html>

If you run this example script, you will see that we are able to generate the same digest both client and server side. This means that when they are compared the user credentials must be correct and we can log the user in. Note: Here you can see we actually submit the password in plaintext; this is just so that PHP can create the digest for comparison in this example.

What data gets sent where, why, how and in what format
To make this script as secure as possible, we want to send as little data in plaintext as possible. As mentioned above, we could send no human readable text (just 2 MD5/SHA-1 hashes), however I have opted not to do this to keep the code as fast as possible. A summary of the data involved, where it goes and how it's used can be found in Figure 1. String comparison is performed between PHP and JavaScript digests


Table Showing Data flow within our Message system

Are you my friend?
In the example script, we use the same secret key every time for the duration of the session, unfortunately this means that if someone manages to acquire this along with the users HMAC digest and session cookie, they will then be able to masquerade as the user. However, if we set a new secret key upon request of the login form and invalidate it upon login, this means that a different digest is sent every time and that it can only be used once. What this means is that any potential hacker must acquire the secret key, the digest, hi-jack the session and submit all this data during the time it takes for the data to be sent to the server. Remember the digest doesn't exist until right before the form is actually POSTed. The slightly different alternative to this, is the hacker must get the plaintext username and password and the secret key, they can then create the digest themselves and again with hi-jacking the session can login successfully. However, this requires some way of grabbing data that is never sent as plaintext (the password) and the only way they would have time to do all that is needed to login requires that the user that is trying to login is either an extremely slow typist or has left her computer at the login screen.

All in all, I believe this to be one of, if not the most secure, non-SSL web based login system available to use today.

Quick Recap
This is quite a lot to take in, so I felt a quick re-cap was in order before jumping into actually building the final login system.
  • HMAC is a standard way of hashing data using a secret key known only to the parties needing to validate that data.
  • We can create HMAC digests both client side and server side, and by comparing these, we can authenticate our users.
  • Only the username is sent in plaintext, and even this can be avoided at the expense of extra database queries.
  • By using a new secret key on every request, we can almost be certain that the client sending the login credentials is the client that requested to login and not an unwanted third party.

Building Our Login Application
We need to create several things for our login application, a client side login form, a server side form handler, and login verification. Our client side login form must be capable of the following:
  • pass the password through an MD5/SHA-1 hashing function,
  • pass the hashed password and the plaintext username through the HMAC Digest function using a server side generated secret key,
  • submit the plaintext username, secret key and the HMAC digest to the server side script,
  • allow for non-JavaScript capable browser to still login.
Listing 3

<form method="post"
action="<?php echo $_SERVER['PHP_SELF']; ?>"
onsubmit="do_submit(); return false;"
id="HMAC_form">
<p>
<input type="text" name="HMAC_user"
id="HMAC_user" />
<input type="text" name="HMAC_pass"
id="HMAC_pass" />
<input type="hidden" name="HMAC_use_js"
id="HMAC_use_js" value="false" />
<input type="hidden" name="HMAC_key"
id="HMAC_key"
value="<?php echo $_SESSION['HMAC_key']; ?>"
/>
<input type="hidden" name="HMAC_hash"
id="HMAC_hash" />
<input type="submit" name="HMAC_submit"
value="Login!" />
</p>
</form>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>" onsubmit="do_submit(); return false;" id="HMAC_form">
<p>
<input type="text" name="HMAC_user" id="HMAC_user" />
<input type="text" name="HMAC_pass" id="HMAC_pass" />
<input type="hidden" name="HMAC_use_js" id="HMAC_use_js" value="false" />
<input type="hidden" name="HMAC_key" id="HMAC_key" value="<?php echo $_SESSION['HMAC_key']; ?>" />
<input type="hidden" name="HMAC_hash" id="HMAC_hash" />
<input type="submit" name=HMAC_submit value="Login!" />
</p>
</form>


Table 2: Login form input elements, the data they contain and what they are used for

Element ID Data Type What it is used for
HMAC_user Client's Username String, Plaintext Used to identify the user in our database
HMAC_pass Client's password String, Plaintext, never sent to server Used to create HMAC Digest
HMAC_use_js Set to TRUE if JavaScript is used Boolean, defaults to false Used to identify if the user has JavaScript enabled or not
HMAC_key Secret random key String, hexadecimal hash Secret key used for the creation for the HMAC Digest
HMAC_hash Resulting HMAC Digest String, hexadecimal hash Resulting HMAC Digest of the concatenated plaintext username and MD5 password
HMAC_submit Submit buttons text String, plaintext To identify if the form has been submitted or not

The only thing we also need to include in our XHTML output is the correct JavaScript <script> tags, which will be output by our PHP depending on the hashing function we choose. Now that we know what is being output, lets see how to generate it. Listing 4 shows the show_form() method from our class.

Listing 4

<?php
/**
* Show Login Form
* @param errors array[optional]
* @return void
*/

function show_form($errors = FALSE) {
$hmac_key = md5(uniqid(mt_rand(),1));
$_SESSION['HMAC_key'] = $hmac_key;
?>
<!-- Hash type specific JS file -->
<script type="text/javascript"
src="HMAC_<?php echo $this->hash_type; ?>.js">
</script>
<!--
Miscellaneous JavaScript, form
submission and error handler
-->
<script type="text/javascript" src="HMAC.js">
</script>
<form method="post"
action="<?php echo $_SERVER['PHP_SELF']; ?>"
onsubmit="doSubmit(); return false;"
id="HMAC_form">
<div id="HMAC_errors">
<?php
if($errors != FALSE) {
foreach($errors as $error) {
echo '<p class="error">' .$error. '</p>';
}
}
?>
</div>
<p>
Username: <input type="text" name="HMAC_user"
id="HMAC_user" /><br />
Password: <input type="text" name="HMAC_pass"
id="HMAC_pass" /> <br />
<input type="hidden" name="HMAC_use_js"
id="HMAC_use_js" value="false" />
<input type="hidden" name="HMAC_key"
id="HMAC_key" value="<?php echo $_SESSION['HMAC_key']; ?>" />
<input type="hidden" name="HMAC_hash"
id="HMAC_hash" />
<input type="submit" name="HMAC_submit"
value="Login!" />
</p>
</form>
<?php
}
?>
/**
* Show Login Form
* @param errors array[optional]
* @return void
*/

function show_form($errors = FALSE) {
$_SESSION['HMAC_key'] = md5(uniqid(mt_rand(),1));
?>
<!-- Hash type specific JS file -->
<script type="text/javascript" src="HMAC_<?php echo $this->hash_type; ?>.js"></script>
<!-- Miscellaneous JavaScript, form submission and error handler -->
<script type="text/javascript" src="HMAC.js"></script>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>" onsubmit="doSubmit(); return false;" id="HMAC_form">
<div id="HMAC_errors">
<?php
if($errors != FALSE) {
foreach($errors as $error) {
echo '<p class="error">' .$error. '</p>';
}
}
?>
</div>
<p>
Username: <input type="text" name="HMAC_user" id="HMAC_user" /><br />
Password: <input type="text" name="HMAC_pass" id="HMAC_pass" /> <br />
<input type="hidden" name="HMAC_use_js" id="HMAC_use_js" value="false" />
<input type="hidden" name="HMAC_key" id="HMAC_key" value="<?php echo $_SESSION['HMAC_key']; ?>" />
<input type="hidden" name="HMAC_hash" id="HMAC_hash" />
<input type="submit" name=HMAC_submit value="Login!" />
</p>
</form>
<?php
}

You will notice that we create our HMAC_key every time we show the form, this means that a form is only valid for one submission, and because we remove the secret key after the form has been submitted, an old key will no longer work either. Now that we can output our form, we need to take the input and validate it, remember I said that we would take care of non-JavaScript enabled browsers? Well here is where we do that, Listing 5 shows our check_auth() method, and you will notice that we check on the value of $_POST[`HMAC_use_js'], if it is true we do HMAC authentication, if not we simply hash the password and compare it with the password in the database - the standard authentication most people will be familiar with - this is much more insecure than the HMAC authentication.

Listing 5

<?php
/**
* Verify Login Credentials
* @return boolean
*/
function check_auth() {
/*
First thing we do is check the HMAC_key's
match, if not, theres no point in
carrying on
*/
if ($_POST['HMAC_key']!=$_SESSION['HMAC_key']) {
$this->errors[] = "Secret Key Mismatch,
please try again";
return FALSE;
}
$sql = "SELECT password FROM users WHERE"
. "username='{$_POST['HMAC_user']}'";
if(!$connection = mysql_connect(
$this->sql['host'],
$this->sql['user'],
$this->sql['pass'])) {
$this->errors[] = "An Error Occured trying to"
." connect to the database."
." Please try again later";
$this->raise_error();
}
mysql_select_db($this->sql['db'],$connection);
if (!$result = mysql_query($sql)) {
$this->errors[] = "An Error Occured trying to"
." access the database. "
."Please try later.";
return FALSE;
}
if (mysql_num_rows($result) == 0) {
$this->errors[] = "Your details could not be "
."found in our database, "
."please try again";
return FALSE;
}
$user_info = mysql_fetch_array($result);
if ($_POST['HMAC_use_js'] == 'true') {
require_once 'Crypt/HMAC.php';
$hmac = new Crypt_HMAC($_SESSION['HMAC_key'],
$this->hash_type);
unset($_SESSION['HMAC_key']);
$hmac_hash = $hmac->hash($_POST['HMAC_user'] .
$user_info['password']);
if ($hmac_hash != $_POST['HMAC_hash']) {
$this->errors[] = "Invalid Username or "
."Password, please try "
."again";
return FALSE;
} else {
$_SESSION['HMAC_Authed'] = TRUE;
$_SESSION['HMAC_last_active'] = time();
return TRUE;
}
} else {
$hash_type = $this->hash_type;
if ($hash_type($_POST['HMAC_pass']) !=
$user_info['password']) {
$this->errors[] = "Invalid Username or "
."Password, please try
."again";
return FALSE;
} else {
$_SESSION['HMAC_Authed'] = TRUE;
$_SESSION['HMAC_last_active'] = time();
return TRUE;
}
}
}
?>
/**
* Verify Login Credentials
* @return boolean
*/
function check_auth() {
// First thing we do is check the HMAC_key's match, if not, theres no point in carrying on
if ($_POST['HMAC_key'] != $_SESSION['HMAC_key']) {
$this->errors[] = "Secret Key Mismatch, please try again";
return FALSE;
}
$sql = "SELECT password FROM users WHERE username='{$_POST['HMAC_user']}'";
if(!$connection = mysql_connect($this->sql['host'],$this->sql['user'],$this->sql['pass'])) {
$this->errors[] = "An Error Occured trying to connect to the database. Please try again later";
$this->raise_error();
}
mysql_select_db($this->sql['db'],$connection);
if (!$result = mysql_query($sql)) {
$this->errors[] = "An Error Occured trying to access the database. Please try later.";
return FALSE;
}
if (mysql_num_rows($result) == 0) {
$this->errors[] = "Your details could not be found in our database, please try again";
return FALSE;
}
$user_info = mysql_fetch_array($result);
if ($_POST['HMAC_use_js'] == 'true') {
require_once 'Crypt/HMAC.php';
$hmac = new Crypt_HMAC($_SESSION['HMAC_key'],$this->hash_type);
unset($_SESSION['HMAC_key']);
$hmac_hash = $hmac->hash($_POST['HMAC_user'] . $user_info['password']);
if ($hmac_hash != $_POST['HMAC_hash']) {
$this->errors[] = "Invalid Username or Password, please try again";
return FALSE;
} else {
$_SESSION['HMAC_Authed'] = TRUE;
$_SESSION['HMAC_last_active'] = time();
return TRUE;
}
} else {
$hash_type = $this->hash_type;
if ($hash_type($_POST['HMAC_pass']) != $user_info['password']) {
$this->errors[] = "Invalid Username or Password, please try again";
return FALSE;
} else {
$_SESSION['HMAC_Authed'] = TRUE;
$_SESSION['HMAC_last_active'] = time();
return TRUE;
}
}
}

Both of these methods are part of a class called secure_login, however we are still missing two important methods as well as our constructor. Listing 6 shows our simple constructor while Listing 7 and 8 show our two missing methods. Listing 7 contains auth() which handles whether or not we call show_form() or check_auth() and handles errors with the authentication. Listing 9 contains is_authed() a simple method thats returns TRUE or FALSE depending on whether or not a user has logged in or not respectively.

Listing 6

<?php
/**
* Constructor
* @param hash_type string
* @param sql array
* @param timeout int[optional]
*/

function secure_login($hash_type,$sql,
$timeout = 3600) {
if (!isset($_SESSION)) {
session_start();
}
$hash_type = strtolower($hash_type);
$this->timeout = $timeout;
if (in_array($hash_type,
$this->allowed_hashes)) {
$this->hash_type = $hash_type;
} else {
$this->errors[] = 'Unknown Hash Type. '
.'Supported hash types are "
.implode('","',$allowed_hashes)
. '".';
$this->raise_error();
}
if (!is_array($sql)) {
$this->errors[] = 'secure_login::secure_login'
.'expected argument of type'
.'"array" for argument 2';
$this->raise_error();
} else {
$this->sql = $sql;
}
}
?>
/**
* Constructor
* @param hash_type string
* @param sql array
* @param timeout int[optional]
*/

function secure_login($hash_type,$sql,$timeout = 3600) {
if (!isset($_SESSION)) {
session_start();
}
$hash_type = strtolower($hash_type);
$this->timeout = $timeout;
if (in_array($hash_type,$this->allowed_hashes)) {
$this->hash_type = $hash_type;
} else {
$this->errors[] = 'Unknown Hash Type. Supported hash types are "' .implode('","',$allowed_hashes). '".';
$this->raise_error();
}
if (!is_array($sql)) {
$this->errors[] = 'secure_login::secure_login expected argument of type "array" for argument 2';
$this->raise_error();
} else {
$this->sql = $sql;
}
}

The secure_login constructor requires 2 arguments, the first is our hash method, this can be `md5' or `sha1' - remember that whichever you choose, the passwords in our database must also be hashed using this method. The second argument is an array containing all the data needed to connect to our MySQL database, if you don't want to use MySQL, you can simply change the check_auth method. You could even use PEAR::DB or PEAR::MDB making it database independent, this is indeed the approach my Auth_HMAC package will take.

Listing 7

/**
* Handles authentication logic
* @return void
*/

function auth() {
if (!isset($_SESSION['HMAC_Authed'])) {
if (!isset($_POST['HMAC_key'])) {
$this->show_form();
} else {
if (!$this->check_auth()) {
$error[] = $this->get_error();
$this->show_form($error);
}
}
}
}

Listing 8

/**
* Returns Authed status
* @return boolean
*/

function is_authed() {
if(isset($_SESSION['HMAC_Authed'])) {
return TRUE;
} else {
return FALSE;
}
}

We have one final method to add to our class, this is a simple method called get_error(), its role is to simply return the last error in our error stack. You can see this method in Listing 9.

Listing 9

/**
* Return the last error in our error stack
* @return string
*/

function get_error() {
return array_pop($this->errors);
}

Using our secure_login class
Now that our class is complete, we can authenticate our users for any page or even for any specific content within a page with a few simple lines of PHP. Listing 10 shows a simple example of using our secure_login class.

Listing 10

<?php
/**
* secure_login class example
*/

require_once 'secure_login.class.php';

$sql = array('host'=>'localhost','user'=>'myuser',
'pass'=>'mypass','db'=>'mydb');

$secure_login = new secure_login('md5',$sql);

$secure_login->auth();

if ($secure_login->is_authed()) {
echo "Hello and welcome to our secret"
."content!";
} else {
echo "You need to be authenticated to see "
."our secret content";
}

?>
<?php
/**
* secure_login class example
*/

require_once 'secure_login.class.php';

$sql = array('host'=>'localhost','user'=>'myuser','pass'=>'mypass','db'=>'mydb');

$secure_login = new secure_login('md5',$sql);

$secure_login->auth();

if ($secure_login->is_authed()) {
echo "Hello and welcome to our secret content!";
} else {
echo You need to be authenticated to see our secret content;
}

?>

What we have accomplished
In this article, we have learnt what makes the current standard authentication insecure and how we can make it much more secure through the use of HMAC Digests. We have utilised the PEAR class Crypt_HMAC and accompanied it with JavaScript to verify login data without sending our clients passwords in plaintext. Because we have also used a dynamic secret key, we can also be sure that whoever is sending the login credentials is the user that requested the login form and we also stop tampering because all secret keys are invalid immediately after use. We have looked at how this system can be attacked and whilst it may be possible, I think most people will conclude that it is extremely hard and therefore highly unlikely. Despite this, I still believe SSL to be the most secure option and for commercial projects and when transferring data such as credit card or personal information it should still be used.

Even though this method relies on the client having JavaScript, by using as simple hidden form element we are able to tell easily whether or not a user has JavaScript enabled and can act upon the input data accordingly. Using a similar system of defaulting to non-JavaScript settings, we can show to our user whether or not they are using our advanced HMAC based authentication or the standard authentication.

What the future holds
This article shows us how to build a secure login system using HMAC, I hope that now you will be able to improve on the class that we have built and will start integrating it by default in to all of your future applications. I expect in the future to see all popular forum software and the like implementing an authentication system based on that shown here, let's all hope that this advanced authentication will soon become de facto, making the web a much safer and secure place.

Reference

© 2004 Software & Support Verlag GmbH. Reproduction has to be permitted by the publisher. Questions?