Secure authentication without SSL (part2)

PHP, Zend Framework 1 Comment

After chatting with my colleagues, we noticed that my CRAM can be worth than nothing.

First, I have forgotten to precise what was the goal. It doesn’t prevent from stealing session by a man in the middle. The goal was to avoid seeing password on the network (if you use the same for all websites) and to prevent replay attack. The CRAM prevent replay by asking for a response with a different salt each time but it implies that the client and the server share the password. I was mistaken with this approach.

It’s a known and good practice to store a password hash instead of plain text password since a read access to the database doesn’t allow an attacker to authenticate since he sends the password in plain text and the server makes the hash. With my CRAM, even if you only have the hash, you can authenticate. That’s the security hole. Since you can’t prevent from a man in the middle attack without SSL, the CRAM seems useless.

But we still want to prevent replay and showing password. First, let’s come back to a basic authentication : we send username and password in plain text, the server makes the hash of the password and compare it with the one in database. To prevent the plain text exchange, we will store a double hash of the password in database : sha1(sha1(password)). The client sends sha1(password) and the server apply a new sha1 before comparing. Concerning the replay, we will use the CRAM. The server sends a unique salt for each try and the client still sends sha1(password) for authentication. The client will also send sha1(salt+md5(password)) as the challenge response. Then the server can authenticate the client with sha1(password) and ensure uniqueness of the challenge with the challenge-response.This implies to store sha1(sha1(password)) and md5(password) in database.

We have the best of the two approach. A man in the middle won’t see the password in plain text and won’t be able to replay the authentication because of the CRAM. He still is able to steal the session, but as long as it is valid. If the valid user logs out, the session becomes invalid. If an attacker has access to the database, he will find md5(password) and sha1(sha1(password)) what will allow him to answer to the CRAM but not to the basic authentication (which wait for sha1(password)).

This method remains low secure but I can’t see anything better without SSL. Like Bram sugessted, I should add a basic plain text authentication by default if javascript is disabled.

Secure authentication without SSL

PHP, Zend Framework 2 Comments

Last days, I’ve implemented authentication in Wisss. The goal is to have a secure authentication without the need to use SSL. I’ve then choose a basic Challenge Response Authentication Mechanism which prevent to send password in clear text.

The mechanisme is simple :

  1. The server send a unique salt, which can be used only once
  2. The client answer with its username and SHA1(salt+SHA1(password))
  3. The server retrieves user’s password SHA1 from database and do SHA1(salt,sha1_password)
  4. if the two match, the user is authenticated

Here is the code for the server (it’s inside a Zend controller, but it can easily be implemented for all framework/language) :

The CRAM authentication adapter for Zend_Auth :

class Wisss_Auth_Adapter_BasicCram implements Zend_Auth_Adapter_Interface
    protected $login ;
    protected $digest ;

    public funtion __construct($login,$digest)
    {
        $this->login = $login ;
        $this->digest = $digest ;
    }

    public function authenticate()
    {
        $auth_session = new Zend_Session_Namespace('auth');
        // retrieve salt from session and erase it since it's used once
        $salt = $auth_session->salt ;
        unset($auth_session->salt) ;
        // digest received from client
        $cram_from_client = $this->digest ;
        // retrieve password sha1 digest from database for the user provided
        require_once('Wisss/Dao/Factory/Abstract.php') ;
        $dao_user = Wisss_Dao_Factory_Abstract::getDao('User','core') ;
        $user = $dao_user->findBy(array('login' => $this->login)) ;
        $sha1_password = $user[0]->getPassword() ;
        // compute the CRAM digest
        $cram_from_server = sha1($salt.$sha1_password) ;
        if($cram_from_client == $cram_from_server) {
            return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS,$this->login) ;
        } else {
            return new Zend_Auth_Result(Zend_Auth_Result::FAILURE,$this->login) ;
        }
    }
 }

The code of the controller :

class LoginController extends Zend_Controller_Action
{
    public function indexAction()
    {
        $salt = md5(uniqid(rand(), true)) ;
        $auth_session = new Zend_Session_Namespace('auth');
        $this->view->assign('login_salt',$salt) ;
        $auth_session->salt = $salt ;
        $auth_session->referer = $_SERVER['HTTP_REFERER'] ;
    }

    public function authenticateAction()
    {
        $auth_session = new Zend_Session_Namespace('auth');
        require_once 'Zend/Auth.php';
        $auth = Zend_Auth::getInstance();
        // Set up the authentication adapter
        require_once('Wisss/Auth/Adapter/BasicCram.php') ;
        $authAdapter = new Wisss_Auth_Adapter_BasicCram($this->_getParam('login'), $this->_getParam('password'));
        // Attempt authentication, saving the result
        $result = $auth->authenticate($authAdapter);
        if (!$result->isValid()) {
            // Authentication failed
            $this->_redirect('/login') ;
        } else {
            // Authentification succeeded
            // regenerate id to prevent session fixation after successfull authentication
            Zend_Session::regenerateId();
            $referer = $auth_session->referer ;
            unset($auth_session->referer) ;
            if($referer != $_SERVER['HTTP_REFERER']) {
                $this->_redirect($referer) ;
            }
        }
    }
}

and finally, the client code (it requires the Prototype Javascript framework) :

<div>
<form action="login/authenticate" method="POST" onSubmit="cram()">
<?php echo $this->formHidden('login_salt',$this->login_salt) ?>
<fieldset>
<div><label for="login">Login : </label><?php echo $this->formText('login') ?></div>
<div><label for="password">Password : </label><?php echo $this->formPassword('password') ?></div>
<div><input type="submit" name="authenticate" value="login"/></div>
</fieldset>
</form>
</div>

And the associated js :

function cram()
{
    $('password').value = sha1Hash($('login_salt').value+sha1Hash($('password').value)) ;
    $('login_salt').value = "" ;
}

// © 2002-2005 Chris Veness
// http://www.movable-type.co.uk/scripts/sha1.html
function sha1Hash(msg)
{
    // constants [§4.2.1]
    var K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6];

    // PREPROCESSING
    msg += String.fromCharCode(0x80); // add trailing '1' bit to string [§5.1.1]

    // convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
    var l = Math.ceil(msg.length/4) + 2;  // long enough to contain msg plus 2-word length
    var N = Math.ceil(l/16);              // in N 16-int blocks
    var M = new Array(N);
    for (var i=0; i<N; i++) {
        M[i] = new Array(16);
        for (var j=0; j<16; j++) {  // encode 4 chars per integer, big-endian encoding
            M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) |
            (msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
        }
    }
    // add length (in bits) into final pair of 32-bit integers (big-endian) [5.1.1]
    // note: most significant word would be ((len-1)*8 >>> 32, but since JS converts
    // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
    M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14])
    M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;

    // set initial hash value [§5.3.1]
    var H0 = 0x67452301;
    var H1 = 0xefcdab89;
    var H2 = 0x98badcfe;
    var H3 = 0x10325476;
    var H4 = 0xc3d2e1f0;

    // HASH COMPUTATION [§6.1.2]

    var W = new Array(80); var a, b, c, d, e;
    for (var i=0; i<N; i++) {
        // 1 - prepare message schedule 'W'
        for (var t=0;  t<16; t++) W[t] = M[i][t];
        for (var t=16; t<80; t++) W[t] = ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1);
        // 2 - initialise five working variables a, b, c, d, e with previous hash value
        a = H0; b = H1; c = H2; d = H3; e = H4;

        // 3 - main loop
        for (var t=0; t<80; t++) {
            var s = Math.floor(t/20); // seq for blocks of 'f' functions and 'K' constants
            var T = (ROTL(a,5) + f(s,b,c,d) + e + K[s] + W[t]) & 0xffffffff;
            e = d;
            d = c;
            c = ROTL(b, 30);
            b = a;
            a = T;
        }

        // 4 - compute the new intermediate hash value
        H0 = (H0+a) & 0xffffffff;  // note 'addition modulo 2^32'
        H1 = (H1+b) & 0xffffffff;
        H2 = (H2+c) & 0xffffffff;
        H3 = (H3+d) & 0xffffffff;
        H4 = (H4+e) & 0xffffffff;
    }

    return H0.toHexStr() + H1.toHexStr() + H2.toHexStr() + H3.toHexStr() + H4.toHexStr();
}

//
// function 'f' [§4.1.1]
//
function f(s, x, y, z)
{
    switch (s) {
    case 0: return (x & y) ^ (~x & z);           // Ch()
    case 1: return x ^ y ^ z;                    // Parity()
    case 2: return (x & y) ^ (x & z) ^ (y & z);  // Maj()
    case 3: return x ^ y ^ z;                    // Parity()
}
}

//
// rotate left (circular left shift) value x by n positions [§3.2.5]
//
function ROTL(x, n)
{
    return (x<<n) | (x>>>(32-n));
}

//
// extend Number class with a tailored hex-string method
//   (note toString(16) is implementation-dependant, and
//   in IE returns signed numbers when used on full words)
//
Number.prototype.toHexStr = function()
{
    var s="", v;
    for (var i=7; i>=0; i--) { v = (this>>>(i*4)) & 0xf; s += v.toString(16); }
    return s;
}

Demo of Wisss 0.2.0

Wisss 4 Comments

Things are moving on in Wisss, I’ve been working on presentation and business logic over the last couple of weeks. It’s beginning to be presentable. This 0.2.0 milestone is not yet finished but already I can show you how it handles the model and what it generates.

Concerning the metamodel, I’ve defined two new packages: view and workflow. View metamodel allows you to define widgets by adding attributes from class data, references to classes or other widgets. The goal is to avoid generating CRUD forms by mapping to classes, which is not always exactly what we want. In the example, it allows you to create a new blog post with a new category. Another use case is to only show a part of a class.

The workflow metamodel, which may be soon renamed to dataflow (thanks to Tortoose for pointing this out) aims to define simple business processes. Like for views, generating simple CRUD processes is not enough for real projects. You often want a form which will create an object associated with another object or a wizard to dispatch creation in several actions. To achieve this, I’ve made a kind of dataflow to define simple algorithms. It allows you to link activities with each other using transitions and define what is for instance a blog post creation process. Activities can be either predefined or user defined. For instance, there is a Create activity which is related to a class. It takes several parameters in entry, mapped to the related class and returns an object.

For these predefined model entities, I’ve played with the code generated by JET. It’s the part which allows me to have a model editor from the ecore metamodel. For instance, what I’ve made is to add in and out parameters to a create activity when you choose its type.

I’ve made a flash demo of the model creation for a very small blog example (Icon set for new packages have not yet been done).

Concerning generation, I’ve added a configuration file for an apache virtualhost, a simple bootstrap, controllers, business objects and view templates. Each widget generates a template and IO activities (input or output) generate the templates which will be used with the controllers. I’ve used the new partial() function to achieve widget inclusion.

Concerning the work dataflow, I generate a business object for each process which contains a static method for each activity (which is not IO activity). A controller is also generated for each process and a controller action is added for each IO activity. A java service generates activity calls in the right order by browsing transitions.

I’ve also made a flash demo of the generation and the webapp working.

Concerning the next steps in Wisss, I may achieve the 0.2.0 in the next days, implementing all typical activities. The next 0.3.0 will add authentication and ACLs and the 0.4.0 will be the right time to add error management and user code blocks. I may find a 0.5.0 milestone before the 1.0 but I hope it will be the last. Now Wisss is presentable, I hope that the buzz will grow in the PHP community.

Thanks to Akrabat, TubularBell and norm2782 from #zftalk for proof reading.

Wisss icon set

Wisss No Comments

While preparing the Wisss module for Acceleo 2.2 release, I’ve made a tiny icon set to replace default one and to show you a simple Wisss model :

Wisss model

I’ve seen that old demo videos have been clicked although the link is down. I will put them back in the next days.

Wisss 0.1.0

Wisss 1 Comment

It’s 6. a.m. I’ve not yet commited since the Acceleo’s SVN is down but I’ve achieved my first milestone : manage persistence. For now, Wisss allow to define a data model, similar to Merise and generate all DTO and DAO based upon Zend Framework.

I will enhance this feature in the future but it can already save dto in a mysql database. It manages 1-1, 1-n and n-n associations too. It allows you to save each object independently or in one step with a “sync” method. Loading is done by lazy loading and array of primitive type attributes are serialized to blob.

It avoids duplicate objects (and then save memory) by using a dto registry wich insure that there is only one instance of a same Business Object (or Dto, no distinction in Wisss for now) at a time.

Concerning the saving policy, I’ve put the focus on generating optimized queries. That’s why a change recorder observe each object for changes (sic!) and is then polled during saving process to only insert or update what is needed.

Finally, the “sync” method have to be preferred most of the time since it shortcuts the problem of browse a graph of objects. It browses the dto registry and save all new or modified objects. The save method will be useful for specific saving inside a transaction.

Bugs must remain but I’m very happy of what I’ve done, mainly in the last 2 days rush. I’m hopeful for the future, a future where I do not bother anymore about writing sql queries rather than concentrating on business logic. Simplicity like in Zope is possible, even in PHP !

See you later, I need to sleep and come back to my musician life for the week end.

« Previous Entries Next Entries »