2005-11-21

Password authentication without revealing your password

The majority of personalized web sites use some kind of form-based password authentication where you have two form fields for username and password, and a login button. When you submit your authentication, the password is sent to the server for verification against a user database. This method has several security implications:
  1. The Password is transmitted unprotected. This can be solved by transport-level encryption, but is vulnerable to in-the-middle attacks on networks with rogue DNS servers.
  2. The Web Application has access to the password. You'll just have to trust that the web site developer has employed proper data protection principles. Cross-side scripting attacks and other attacks might expose the password to an adversary.
  3. The Password might be stored without adequate protection in the user database. You as the user have no means to verify that the web site actually stores the password in a sufficiently secure form. This makes your password vulnerable to break-ins at the web site, and brute-force attacks.

Solution: Hash the password on the client side

By protecting the password on the client side, the clear text password never leaves the browser. This is simply done with an onSubmit JavaScript: <form onsubmit="pwField.value = b64_sha256(pwField.value);"> See a demonstration of how this works here. This solution has several benefits:
  1. Password is always protected. Since the hashing is performed before form is submitted, the password travels protected, and the web site sees only a hash of the password.
  2. Password length and other password properties are hidden. With the b64_sha256() function above, all password and passphrases are hashed to a 43-character string, regardless of password length. This hides the actual length of the password, and what characters or character set in use.
However, there are also limitations:
  1. Requires JavaScript enabled in the browser. Some users disable JavaScript in their browser, or use a browser with a broken or incomplete JavaScript implementation.
  2. Dictionary attacks on passwords slightly simpler. Hashing algorithms can be much faster than other encryption algorithms that might be used to protect the password in the database.
  3. Vulnerable to password sniffing The hashed password is password-equivalent, i.e. it can be used to login just as the original password, and must be protected in transit with SSL.
Some might have heard that recently there has been discovered vulnerabilities in the MD5 and SHA-1 algorithms most commonly used as hashing. Therefore my demonstration uses SHA-2 with 256 bits. I have tested the demonstration on Opera 8.5, Firefox 1.0, MS IE 6.0, Safari 2.0, and they all yield the same results.

Further improvements

This scheme can be further improved.

Keying the hash with user ID

To protect against password equality tests and dictionary attacks, the hash can be keyed with the user id or some other data available within the same form: <form onsubmit="pwField.value = b64_hmac_sha256(userId.value, pwField.value);"> See a demonstration here. This solution is still vulnerable to replay attacks, so the result must be submitted over a secure transport.

Challenge-response schemes

To protect against replay attacks, challenge-response schemes could be employed. In this scenario, the password hash is keyed with data generated by the server when it presents the login form. Upon submission, the server re-computes the hash based on the stored password information. To see a more detailed explanation of this, and an example, see Paul Johnston's description and proposed solution. Paul's suggestion is based on the server generating a challenge when presenting the login form and storing these challenges to test their recency and validity. A simpler scheme could be to generate the challenges based on the date and time, reducing the resolution to yield a time slot for the challenge's validity. Furthermore, the challenge could be fetched by JavaScript during submit instead of being part of the HTML code for the login page. In Perl, the challenge can be generated like this:
use Digest::SHA qw(hmac_sha256_base64); print hmac_sha256_base64('Company Secret', time() >> 5);
Please choose a better key than Company Secret to generate your challenges... Since the challenge is updated every 16 seconds, one might want to test against a number of generated challenges when performing the authentication, for instance like this:
function hmac_sha256_base64($data, $secret) {
return substr(base64_encode(hash_hmac("sha256", $data, $secret, TRUE)),0,-1);
}
$seed = time() >> 4;
$ok = FALSE;
$tries = array(0,1,2);
foreach (array(0,1,2) as $i) :
$hash = hmac_sha256_base64($passwd_hash, hmac_sha256_base64("Company Secret", $seed-$i));
if ($hash == $_POST["password"]) : ?>

See a demonstration here. To further improve security several hashing keys could be used instead of the constant Company Secret suggested above The hashing keys can be employed using a rotational scheme, or even generated randomly and stored in-memory in the servers.

Other issues

Some other areas also need attention to maintain the improved security of this scheme;
  • user sign-up,
  • brute-force break-in attempts,
  • compromised passwords, and
  • initial password distribution.
The important thing to bear in mind is to distribute new passwords via a trusted, out-of-band, means of delivery. Examples include:
  • GnuPG or S/MIME encrypted e-mail to a verified e-mail address and pre-registered public key or certificate
  • Text message to a mobile phone
  • Certified mail
  • Personal delivery
As for brute-force break-ins, they can appear in several forms:
  1. Attempts against the same user from the same computer
  2. Attempts against multiple users from the same computer
  3. Attempts against multiple users from multiple computers
  4. Phishing attempts
The important issues to bear in mind is having an audit system capable of detecting such attempts. One can also reduce the probability of successful attacks by:
  • keying challenges with remote host and / or user id, and
  • limiting the rate of challenges produced for a particular remote host or user name to for instance once per minute.