php login two-way encryption

I'm using this great login software located here.

It uses md5 encryption which the author says precludes the possibility of password retrieval (because md5 is "one way"). From the site:

The id_user field will contain the unique id of the user, and is also the primary key of the table. Notice that we allow 32 characters for the password field. We do this because, as an added security measure, we will store the password in the database encrypted using MD5. Please note that because MD5 is an one-way encryption method, we won't be able to recover the password in case the user forgets it.

Is there anyway to use a two way encryption like this so that I can make a "forgot password" link?

In the fg_membersite.php file, why can't I simply change the md5 encryption lines 258 and 583 to be

base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($_SESSION['pw_key']), $string, MCRYPT_MODE_CBC, md5(md5($_SESSION['pw_key']))));

Open in new window


with something like $_SESSION['pw_key']="randomsessionkey"; up at the top of the page and the $string variable in the encryption  ($password) in line 258 and $this->$formvars['password'] in line 583

so that I could make the forgot password page decrypt with the stackoverflow decrypt key and send it to the user's email or something.

whenever I try to make these changes, I get the username/password do not match error. I guess I am missing something quite fundamental!

Thanks for any insight you can offer!!
hibbsusanAsked:
Who is Participating?
 
Beverley PortlockCommented:

Create a script that has a form that allows the user to key in their email address. Use this to generate a link that sends them to a special script that checks for definite parameters and if the parameters are correct then display a form allowing the user to reset the password. So...

(UNTESTED)

<?php
if ( $_POST['emailAddress'] != "" ) {

     // Check email address is in the database
     //
     ... omitting this for clarity


     // Generate link
     //
     $number = mt_rand(100000, 1000000);
     $md5 = md5( $number . "my secret salt string");

     $link = "http://mydomain.com/passwordrest.php?number=$number&amp;check=$md5";

     // Send email
     //
     $message = "Click here to reset your password: $link";
     mail( $_POST['emailAddress'], "Your password", $message );
}

?>
....
<form action='<?php echo $_SERVER['PHP_SELF']; ?>' method='post'>
   .... stuff
   <input name='emailAddress' type='text' value='' />
   <input name='submit' type='submit' value=='Submit' />
   ... stuff
</form>

Open in new window


Then in the reset script check for the parameters and use the number to generate the MD5 which should match up. If it does then show the form to update the password

<?php

     if ( $_GET['number'] != "") {
          $num = $_GET['number'];
          $md5 = $_GET['check'];

          $test = md5( $number . "my secret salt string");

          if ( $test == $md5 ) {
              // Match display form, do update of password
          }
          else
               die("Sorry - invalid link");
     }

Open in new window

0
 
TommySzalapskiCommented:
That is actually generally considered a very bad solution. Do not email the user the old password, just email them a link to reset the password. Then you never need to recover the old password (this is what all the major online companies are doing now, Google, Microsoft, Amazon, ebay, etc.).
0
 
Beverley PortlockCommented:
You can use MCRYPT like you are doing in your example, but I agree with the previous poster. There is a standard method for doing this and you would be better off using it.

The downfall for your method is that the decryption key would have to be visible to the webserver in order to send a clear text password and that means that if the server is hacked the hacker can just decrypt all your encrypted stuff.

Oops....
0
Cloud Class® Course: Microsoft Azure 2017

Azure has a changed a lot since it was originally introduce by adding new services and features. Do you know everything you need to about Azure? This course will teach you about the Azure App Service, monitoring and application insights, DevOps, and Team Services.

 
hibbsusanAuthor Commented:
ok. so I only allow the user to reset the password? It seems like whenever this happens for amazon or something, i receive a dynamically generated link in an email which allows me to reset my password. Do you know how to generate these? I'm a bit confused how to implement something like that..

Thank you both!
0
 
TommySzalapskiCommented:
You just generate some kind of random string and use that on both ends to ensure that it's from the email. It's just like the one you should have sent to their email to confirm the email used to activate the account.
0
 
hibbsusanAuthor Commented:
This works perfectly. Thank you so much!! Exactly what I needed.

$number should be $num in line 7. Took me a few minutes to figure out what was happening there.

Thank you!
0
 
Beverley PortlockCommented:
It was just some example code I threw together to illustrate the principle of the thing. I did not expect it to run straight off. So I had better clear up some additional stuff because forms that handle email addresses are POTENTIAL spam mail relays. Maybe you know about the following, but I will say it anyway.


1. Be suspicious of $_POST['emailAddress'] - in fact be suspicious of any data from $_POST or $_GET

2. Make sure that $_POST['emailAddress'] is a valid email address stored in your database - reject everything that is not in your database.

3. Make sure that only ONE email address is in there. A quick way is to count @ symbols using count_chars and if there is more than one @ then reject the submission. http://www.php.net/count_chars

4. If the email address gets past the above tests then run it through filter_var with a VALIDATE filter. if it passes then send the email.

The above mods would alter the code like so (UNTESTED)

<?php
if ( $_POST['emailAddress'] != "" ) {

     $emailAddress = strip_tags( $_POST['emailAddress'] );
     // Check email address is in the database
     //
     $rs = mysql_query("select * from .... where emailAddress = '". mysql_real_escape_string($emailAddress) ."'  " );
     if ( mysql_num_rows($rs) >= 1 ) {

          // Email address in database. Check for too many @ symbols
          //
          $chars = count_chars( $emailAddress );
          if ( $chars [ ord('@') ] == 1 ) {

               // Exactly one @ symbol. Check the address
               //
               if ( filter_var( $emailAddress, FILTER_VALIDATE_EMAIL ) !== false ) {

                    // Email address seems safe. Generate link
                    //
                    $number = mt_rand(100000, 1000000);
                    $md5 = md5( $number . "my secret salt string");

                    $link = "http://mydomain.com/passwordrest.php?number=$number&amp;check=$md5";

                    // Send email
                    //
                    $message = "Click here to reset your password: $link";
                    mail( $_POST['emailAddress'], "Your password", $message );
               }
          }
     }
}

?>
....
<form action='<?php echo $_SERVER['PHP_SELF']; ?>' method='post'>
   .... stuff
   <input name='emailAddress' type='text' value='' />
   <input name='submit' type='submit' value=='Submit' />
   ... stuff
</form>

Open in new window




I hope that the above is helpful. I am sorry I did not include it earlier, I was not expecting the illustrative code to run.

Cheers

Brian

0
 
Ray PaseurCommented:
I love questions like this.  What are you trying to protect?  Nuclear launch codes, the POTUS travel schedule, interbank financial transactions, gambling information, medical records transmissions, bowling scores? These all have different security profiles.

Please see this article.  Conspicuous by its absence is the encoding or encryption of the client password.  Maybe I will write another article about that part of things.
http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/PHP/A_2391-PHP-login-logout-and-easy-access-control.html

Md5() retrieval is "one way" but it is subject to rainbow attacks.  Consider the following hypothetical:  Let's way the password must be numbers only and must be exactly four characters in length.  That gives us the range from "0000" to "9999" and we can quickly build a data base table with these strings and the md5() digest strings that are made from these four digit number strings.  See:
http://en.wikipedia.org/wiki/Rainbow_table

The attack consists of getting the server's md5() string and comparing it to the data base md5() string set.  The match is the thing you use to attack the server.  It is not very hard to create matches, and some web sites are in the business of collecting data bases of strings.  They just take the client input and make the md5() strings which they store for future use or abuse.

This little script gets the md5() values for the numbers from 0000 to 9999.  It runs in less than 20 milliseconds on my server.
http://www.laprbass.com/RAY_temp_hibbsusan.php
<?php // RAY_temp_hibbsusan.php
error_reporting(E_ALL);
echo "<pre>";


// A SCRIPT TIMER FOR ALL OR PART OF A SCRIPT PHP 5+
// MAN PAGE http://php.net/manual/en/function.microtime.php


class StopWatch
{
    protected $a; // START TIME
    protected $s; // STATUS - IF RUNNING
    protected $z; // STOP TIME

    public function __construct()
    {
        $this->a = array();
        $this->s = array();
        $this->z = array();
    }

    // A METHOD TO REMOVE A TIMER
    public function reset($name='TIMER')
    {
        // RESET ALL TIMERS
        if ($name == 'TIMER')
        {
            $this->__construct();
        }
        else
        {
            unset($this->a[$name]);
            unset($this->s[$name]);
            unset($this->z[$name]);
        }
    }

    // A METHOD TO CAPTURE THE START TIME
    public function start($name='TIMER')
    {
        $this->a[$name] = microtime(TRUE);
        $this->z[$name] = $this->a[$name];
        $this->s[$name] = 'RUNNING';
    }

    // A METHOD TO CAPTURE THE END TIME
    public function stop($name='TIMER')
    {
        $ret = NULL;

        // STOP ALL THE TIMERS
        if ($name == 'TIMER')
        {
            foreach ($this->a as $name => $start_time)
            {
                // IF THIS TIMER IS STILL RUNNING, STOP IT
                if ($this->s[$name])
                {
                    $this->s[$name] = FALSE;
                    $this->z[$name] = microtime(TRUE);
                }
            }
        }

        // STOP ONLY ONE OF THE TIMERS
        else
        {
            if ($this->s[$name])
            {
                $this->s[$name] = FALSE;
                $this->z[$name] = microtime(TRUE);
            }
            else
            {
                $ret .= "ERROR: CALL TO STOP() METHOD FOR '$name' IS NOT RUNNING";
            }
        }

        // RETURN AN ERROR MESSAGE, IF ANY
        return $ret;
    }

    // A METHOD TO READ OUT THE TIMER(S)
    public function readout($name='TIMER', $dec=3, $m=1000, $eol=PHP_EOL)
    {
        $str = NULL;

        // GET READOUTS FOR ALL THE TIMERS
        if ($name == 'TIMER')
        {
            foreach ($this->a as $name => $start_time)
            {
                $str .= $name;

                // IF THIS TIMER IS STILL RUNNING UPDATE THE END TIME
                if ($this->s[$name])
                {
                    $this->z[$name] = microtime(TRUE);
                    $str .= " RUNNING ";
                }
                else
                {
                    $str .= " STOPPED ";
                }

                // RETURN A DISPLAY STRING
                $lapse_time = $this->z[$name] - $start_time;
                $lapse_msec = $lapse_time * $m;
                $lapse_echo = number_format($lapse_msec, $dec);
                $str .= " $lapse_echo";
                $str .= $eol;
            }
            return $str;
        }

        // GET A READOUT FOR ONLY ONE TIMER
        else
        {
            $str .= $name;

            // IF THIS TIME IS STILL RUNNING, UPDATE THE END TIME
            if ($this->s[$name])
            {
                $this->z[$name] = microtime(TRUE);
                $str .= " RUNNING ";
            }
            else
            {
                $str .= " STOPPED ";
            }


            // RETURN A DISPLAY STRING
            $lapse_time = $this->z[$name] - $this->a[$name];
            $lapse_msec = $lapse_time * $m;
            $lapse_echo = number_format($lapse_msec, $dec);
            $str .= " $lapse_echo";
            $str .= $eol;
            return $str;
        }
    }
}



// DEMONSTRATE THE USE -- INSTANTIATE THE STOPWATCH OBJECT
$sw  = new Stopwatch;

// START A TIMER TO GET ELAPSED TIME
$sw->start();



// PERFORM SOME ACTIVITY THAT YOU WANT TO TIME (LIKE GET md5() STRINGS FOR 0000 TO 9999
$num = 0;
while ($num <= 9999)
{
    // MAN PAGE: http://php.net/manual/en/function.str-pad.php
    $str = str_pad("$num", 4, '0', STR_PAD_LEFT);
    $md5 = md5($str);
    $arr[$str] = $md5;

    // ACTIVATE THIS TO SLOW DOWN THE SCRIPT AND SEE THE OUTPUT
    // echo PHP_EOL . "md5($str) = $md5";
    $num++;
}



// REPORT THE STOPWATCHES CONTENT
echo PHP_EOL . "I GOT $num md5() STRINGS IN MILLISECONDS: ";
echo nl2br($sw->readout());

Open in new window

0
 
Beverley PortlockCommented:
Ray said: "The attack consists of getting the server's md5() string and comparing it to the data base md5() string set."

There is a certain something in what you say. As I explained upthread I expected the code sample to be an illustration, not an exact solution. Having said that, it is easy to make md5s secure against the sort of attack you outlined by simply including other data items that are subject to change. Good examples are today's date or the IP address of the requester.

 $md5 = md5( $number . "my secret salt string" . date("Y-m-d" ) . $_SERVER['REMOTE_ADDR'] );

Such an md5 is only of use from one IP for one day, or, instead of sending a randomly generated number you might send an expiry time 20 minutes in the future.

                    $number = time() + 20 * 60
                    $md5 = md5( $number . "my secret salt string" .  $_SERVER['REMOTE_ADDR'] );

                    $link = "http://mydomain.com/passwordrest.php?number=$number&check=$md5";


and then your tests become

<?php

     if ( $_GET['number'] != "") {
          $num = $_GET['number'];
          $md5 = $_GET['check'];

          if ( $num < time()
               die("Link expired");
          else {
              $test = md5( $num . "my secret salt string"  .  $_SERVER['REMOTE_ADDR']  );

              if ( $test == $md5 ) {
                  // Match display form, do update of password
              }
              else
                  die("Sorry - invalid link");
         }
     }



Like you say Ray - what is being protected here and is it worth the effort of a major hack?

Cheers

Brian

0
 
hibbsusanAuthor Commented:
Thanks to both for all the time you've put in on this question. Very helpful!

The information being tracked is only some simple address information and, all things considered, not a likely candidate for malicious expl. oitation. But the idea of an expiry and an ip-track are appealing on a sort of principle : ' why not?'. It seems like just a few extra lines.

Thanks again!

0
 
hibbsusanAuthor Commented:
Thanks to both for all the time you've put in on this question. Very helpful!

The information being tracked is only some simple address information and, all things considered, not a likely candidate for malicious expl. oitation. But the idea of an expiry and an ip-track are appealing on a sort of principle : ' why not?'. It seems like just a few extra lines.

Thanks again!

0
 
hibbsusanAuthor Commented:
Thanks to both for all the time you've put in on this question. Very helpful!

The information being tracked is only some simple address information and, all things considered, not a likely candidate for malicious expl. oitation. But the idea of an expiry and an ip-track are appealing on a sort of principle : ' why not?'. It seems like just a few extra lines.

Thanks again!

0
 
hibbsusanAuthor Commented:
Thanks to both for all the time you've put in on this question. Very helpful!

The information being tracked is only some simple address information and, all things considered, not a likely candidate for malicious expl. oitation. But the idea of an expiry and an ip-track are appealing on a sort of principle : ' why not?'. It seems like just a few extra lines.

Thanks again!

0
 
hibbsusanAuthor Commented:
Thanks to both for all the time you've put in on this question. Very helpful!

The information being tracked is only some simple address information and, all things considered, not a likely candidate for malicious expl. oitation. But the idea of an expiry and an ip-track are appealing on a sort of principle : ' why not?'. It seems like just a few extra lines.

Thanks again!

0
 
hibbsusanAuthor Commented:
Thanks to both for all the time you've put in on this question. Very helpful!

The information being tracked is only some simple address information and, all things considered, not a likely candidate for malicious expl. oitation. But the idea of an expiry and an ip-track are appealing on a sort of principle : ' why not?'. It seems like just a few extra lines.

Thanks again!

0
 
rinfoCommented:
In the fg_membersite.php file, why can't I simply change the md5 encryption lines 258 and 583 to be

in line 258 md5 of the password is checked with the stored passed ( which is md5 of the password stored in database - stored in db line 583).
You can surely use the code you mentioned but you must understand the technique used.
The concept is you create a salt. Use a  simple routine to create this salt .
This salt will be stored along with the encrypted password in the database.
You would need to add a column for salt in your table.
when you encrypt the password while adding the user detail in table
use this  function
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), $string, MCRYPT_MODE_CBC, md5(md5($key))));
Here variable $key will be salt generated by you earlier and stored in database as well.
&string will be the user password,
To retrieve the password  use the function.
$decrypted = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($key), base64_decode($encrypted), MCRYPT_MODE_CBC, md5(md5($key))), "\0")
 here once again $key is retrieved from database
$encrypted is the encrypted password in the database.
Why you are failing using the above code.
Because every time you are using $Key is a different value
Naturally results are different and not matched.
If you are still unclear. Let me know




0
 
Ray PaseurCommented:
Brian: Great suggestion (as usual)!  I especially like the use of time() as a "salt" in the md5() input.  Not sure I would restrict on the basis of the IP, since this would make it impossible for me to use any other device to complete the transaction.  But I might restrict on the basis of the email address which could be the same across multiple devices.  Just a thought...

In my applications that are not all that critically secure (for example, when the only thing the client can modify is the client's own data) I keep the password in clear text and employ a "forgot my password" link that sends the password to the registered email address.  This strategy has never resulted in a security breach, probably because nobody cared enough to try to use a packet sniffer to steal the passwords, and there was no nosy spouse sneaking into someone's email with malicious intent.  It has only failed me once, when a client forgot his password and simultaneously changed email providers -- I had to take a phone call and do a manual lookup.  Out of a population of thousands of man-years, this seems to be an acceptable failure rate.
0
 
crazedsanityCommented:
There's a hidden security risk in storing passwords in plain text or in a way that is reversible: most people re-use the same password for *everything*.

So if I were to steal a username+password combination for a certain person, it is very likely that I could use those *exact* same credentials for other things.  Social networking sites (Facebook, Twitter, etc), Paypal, eBay, and even their bank's website.

In that event, it would be extremely easy to steal their identity, post inflammatory material that could get them fired or sued or worse.
0
 
hibbsusanAuthor Commented:
thanks for all the participation! really informative posts!
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.