Challenge/Response Login Architecture not Functioning

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Post Reply
User avatar
The_Anomaly
Forum Contributor
Posts: 196
Joined: Fri Aug 08, 2008 4:56 pm
Location: Tirana, Albania

Challenge/Response Login Architecture not Functioning

Post by The_Anomaly »

Well, I'm ashamed to bring it here, as I'm under the impression it's something small I'm overlooking. But I've spent the past two days slaving over this (relatively) simple code. It's based on (and almost exactly) the code in this wonderful tutorial. I put it into two separate classes that are called based on whether we're processing the login, or the form page. The returned challenge on the challenge method is then put into a hidden field called challenge. Basically, exactly as that tutorial demonstrates.

I really did my best to find this problem on my own. I've tested every step of it with various var_dumps, and other techniques. I just can't find out what's wrong! The response and the challenges do not match, even though I've confirmed that the same challenge is being used, that the same password is being used, and that the same username is being used. I've checked all the queries, all the variables, everything. I have no idea what's wrong.

The wackiest thing of all, is that sometimes, it does work. At least, at the beginning, it worked perfectly. I don't recall changing anything--and even as I'm testing it, every once in a while, it'll log me in! Then I clear cookies, and try again as if I'm a new user, and it's wrong again.

Any help would really be appreciated.

Library page:

Code: Select all

 
public function Challenge(){
        /*
         We will use feyd's SHA256 PHP implementation to support SHA256 (does not require mcrypt enabled).
         Depending on where you get your version ensure no echo() statements are left uncommented out
         */
        require_once('sha256.inc.php');
 
        //Create the challenge, baby.
        $challenge = SHA256::hash(uniqid(mt_rand(), true));
 
        //Use extended class to connect to db
        $dbh = $this->Connect();
 
        //We want our challenges to last only 5 minutes. Anything older, with the same Session ID, mind you, must be killed.
        $timeDate = time();
 
        //Delete the challenges from the same session ID and from 360 seconds ago
        $prepared = "DELETE FROM challenge WHERE timeDate < " .$timeDate ." OR sessID = '" .session_id() ."'";
        $dbh->exec($prepared);
 
 
        //Store that sucker in the database. We give it's timestamp an exgtra 360 seconds, so as to provide those extra minutes before we screw it
        $time = time() + 360;
        $prepared = "INSERT INTO challenge SET challenge = :challenge, sessID = :sessID, timeDate = :timeDate";
        $insert = $dbh->prepare($prepared);
        $insert->bindParam('challenge',$challenge);
        $insert->bindParam('timeDate',$time);
        $insert->bindParam('sessID', session_id());
        $insert->execute();
        return $challenge;
    }
    public function Auth(){
        //Include Feth's SHA256 class
        require_once('sha256.inc.php');
 
        //Connect to DB
        $dbh = $this->Connect();
 
        //Basic validation
        if(isset($_POST['response']) && !empty($_POST['response']) && (!ctype_alnum($_POST['username']) || !ctype_alnum($_POST['response'])))
        {
            // we may log bad data, or make the user walk the plank for their trouble!
            die('Bad Input: Response or username are not alphanumeric!');
        }
        if(isset($_POST['password']) && !empty($_POST['password']) && (!ctype_alnum($_POST['username']) || !ctype_alnum($_POST['password'])))
        {
            // log or keel-haul the swabbies!
            die('Bad Input: Password or username are not alphanumeric!');
        }
 
        //Okay, if we're still alive, we get the challenge.
        $prepared= "SELECT challenge FROM challenge WHERE sessID = :sessID AND timeDate > :timeDate";
        $select = $dbh->prepare($prepared);
        $select->bindParam('sessID',session_id());
        $select->bindParam('timeDate', time());
        $count =  $select->execute();
        if($count == 0){
            //We timed out, deal with appropriately
        }
 
        //So, now we have our array. Assign the challenge
        $selected = $select->fetch();
        $challenge = $selected['challenge'];
 
        //Now, we have to prepare what the expected response
        //Select the user info.
        $prepared = "SELECT * FROM user_accounts WHERE username = :username";
        $select = $dbh->prepare($prepared);
        $select->bindParam('username',$_POST['username']);
        $count = $select->execute();
        $selected = $select->fetch();
        //Ensure the user exists
        if($count == 0){
            ///user does not exist, deal with appropriately.
        }
 
        //Assign the array appropriately
        $userInfo = $selected;
 
        //Okay, so, we already have the hashed copy of the user password. We also have the username from the posted array, and we have the challenge from the db
        //Let's generate that response, baby!
        $responseString = strtolower($userInfo['username'].':'.$userInfo['password'].':'.$challenge);
        $expectedResponse = SHA256::hash($responseString);
        //So, here's the poing of it all. We're now checking to see if the hashes are the same.
 
 
        if($_POST['response'] == $expectedResponse)
        {
            $_SESSION['authenticated'] = 1;
            $_SESSION['userid'] = $userInfo['userid'];
            $_SESSION['username'] = $userInfo['username'];
            header('Location: ../index.php?login=successful');
        }
        elseif(isset($_POST['userpass']) && !empty($_POST['userpass']))
        {
            //So, this is if the user had javascript disabled. So, we hash their password, because the javascript didn't do it. Then compare it to our hashed password in the db
            if(SHA256::hash($_POST['userpass']) == $userInfo['password'])
            {
                /*
                 Submitted plain text password from non-js client, when hashed, agrees to database stored password hash
                 We authenticate the User
                 */
                $_SESSION['authenticated'] = 1;
                $_SESSION['userid'] = $userInfo['userid'];
                header('Location: ../index.php?login=successful');
            }
            else
            {
                //So, we still are in the conditional statement saying that we have a nonempty password field, but it doesn't match.
                //This means that the user has the wrong password, javscript or not.
                $_SESSION['authenticated'] = 0;
                header('Location: login.php?error=pass');
            }
        }
        else
        {
            /*
             At this point:
             - The client Response does not agree with the server generated Expected Response
             This login attempt has failed - we should direct user to try again.
             */
 
            header('Location: login.php?error=response');
        }
 
    }
 
}
Javascript function that is called onsubmit:

Code: Select all

 
  function doChallengeResponse() {
    str = document.login_form.username.value.toLowerCase() + ":" +
    sha256_digest(document.login_form.userpass.value) + ":" +
    document.login_form.challenge.value;
    document.login_form.userpass.value = "";
    document.login_form.challenge.value = "";
    document.login_form.response.value = sha256_digest(str);
    return false;
  }
 
User avatar
The_Anomaly
Forum Contributor
Posts: 196
Joined: Fri Aug 08, 2008 4:56 pm
Location: Tirana, Albania

Re: Challenge/Response Login Architecture not Functioning

Post by The_Anomaly »

I hate to bump this, but I really could use some help here. Does anyone have any idea as to what might cause my problems?
User avatar
The_Anomaly
Forum Contributor
Posts: 196
Joined: Fri Aug 08, 2008 4:56 pm
Location: Tirana, Albania

Re: Challenge/Response Login Architecture not Functioning

Post by The_Anomaly »

Wow. Well, I finally found the problem. I had a placeholder image in there, as I was procrastinating setting up a CAPTCHA, which had the following code:

Code: Select all

<img src="" alt="" name="CAPTCHA" width="271" height="53" id="CAPTCHA" />
Thanks to the miracle that is Google, I found this thread, which solved it all! The above code for some reason was making my queries insert twice, due to the fact it seemed to load the page again. So, the first db entry was the one that was assigned to the hidden field, where the other was the one that the auth() function was calling to compare the two. Hence, as you can imagine, it didn't work.

Just wanted to share this with anyone. If, for some crazy reason, your queries seems to be executing twice--see if you have the above code (or anything with src="")--and get it out of there. Maybe that'll save you from the three days of suffering I went through with this.
Post Reply