samedi 25 novembre 2006

The end of unsecure authentification

I don't know what's your opinion about clear passwords on the web but mine is clear. I hate typing them on unsecure channels. When I'm pressing the submit button I'm always thinking that some body can discover my password without much effort. He just needs a sniffer like ethereal and wait for some passwords to be caught by the soft. One solution is to use the https protocol instead of http. Unfortunately too few web site use it. But why? Because to use https you need to generate or pay for a certificate, you need to configure your server and you need more cpu. Even if this is not really hard too many web sites don't encrypt passwords between the client and the server. Maybe we need a solution which haven't https'constraints. I have a proposal.

My proposal doesn't require certificates, doesn't need any configuration on the web server and little cpu.

First we have to admit that good web sites don't store passwords in plain text in their database. It's often a hash sum like md5 for example. But if two users have the same password then the same sum will appear. So more serious sites concatenate a random seed to the password and then compute the hash sum. It should look like this: md5(password + seed). Well, instead of sending clear passwords we can send their hash sum. No! Because the bytes sent to the server will always be the same. Consequently the sniffer will not get the clear password but the data representing the password. That's just what he needs for connecting. We can do better.

To improve the system we need to generate a temp seed for each connection attempt. Thus by computing a double hash our password will be never revealed. Here is the formula.

md5(md5(password + seed) + temp_seed)

Why double sum instead of a simple md5(password + seed + tmp_seed)? Remember, in database passwords are stored in hash sum with a seed. Consequently the web site only knows the value of md5(password + seed). Thus from this value we just need to concatenate the temp seed, compute the sum and check if bytes are equal.

Our theory is good, we've done the half work. Now we will build our secure authentification system with Ruby on rails. Because I love Ruby and the rails framework rocks !

In our form we need several fields. Login represents the user's name, password where the user types his password, seed contains the user's seed, temp seed is the seed generated for each form, and password sum will be the result of the client side compute.

The main difficulty is to get the seed of the specified user. Indeed the seed is unique for each user but we need it in the form to compute the hash. Consequently we can only get it when we know user's login. Thanks to rails and AJAX a method called observer_field allow to send request when the value of the specified field changed. Thus severals request will be sent when the user is tiping his name to get his seed. In my example when login is edited a request is sent to the seed action and then a new field is put in the seed_container.

<%= javascript_include_tag 'md5' %>
<%= javascript_include_tag 'connection' %>
<%= javascript_include_tag 'prototype' %>

<p style="color: red"><%= flash[:error] %></p>
<%= start_form_tag 'check' %>
Login: <%= text_field_tag :login %> <br/>
Password: <%= password_field_tag :password %> <br/>
Seed: <span id='seed_container'><%= text_field_tag :seed %></span> <br/>
Temp seed: <%= text_field_tag :tmp_seed, @tmp_seed %> <br/>
Password sum: <%= text_field_tag :password_sum %> <br/>
<%= submit_tag 'Connection', :onClick => "hash_password('password', 'seed', 'tmp_seed', 'password_sum')" %> <br/>
<%= observe_field :login, :frequency => 0.5, :url => {:action => 'seed'}, :update => 'seed_container' %>
<%= end_form_tag %>


Finaly we need to compute the password sum before sending the form. As you can see in the source code sample the function hash_password (in connection.js) is called when the user click on submit.

function hash_password(pwd_id, pwd_seed_id, tmp_seed_id, pwd_sum_id) {
var pwd = document.getElementById(pwd_id);
var pwd_seed = document.getElementById(pwd_seed_id);
var tmp_seed = document.getElementById(tmp_seed_id);
var pwd_sum = document.getElementById(pwd_sum_id);
// Compute md5(md5(password + seed) + temp_seed)
// And don't forget to blank the original password field
pwd_sum.value = md5_hex(md5_hex(pwd.value + pwd_seed.value) + tmp_seed.value);
pwd.value = '';
}


This function is quite simple. The two last lines are the most important. The first compute the sum as explained before. And the last blank the password field to not send it over the network. As you may noticed I included un md5 file which isn't mine and can be found here. You also can use sha-1 instead of md5.

I hope that this note will help you to have your own secure authentification and/or you have been conscious of clear passwords over internet.

Get source code

2 commentaires:

Anonyme a dit…

I haven't tested it yet, but it makes sense to me.
At last, people who can't connect to the 443 port, will be able to login securely to a remote Web system.

off topic :
Quite freaking : Blogger identifies me as Guillaume "Zifro" DESRAT without having to type any login/password pair (and without even knowing I had such a blog account)... is that related to this big .google.com cookie ? :p

Alexis Bernard a dit…

I think I will write a helper or something else to use this auth system without any effort.

You haven't a blog account but you have a gmail account that why you have been recognize :)