36

I have a small community website and I need to implement some sort of forgotten password function. I currently store the passwords in the DB, encrypted with MD5.

Is it possible to sort of 'decrypt' and send it to user via email or would I need to have a password reset page?

Liam
  • 9,725
  • 39
  • 111
  • 209
  • 7
    The whole idea of storing passwords MD5 encoded is that you cannot decode them. Even if somebody breaks into your system and dumps the DB they cannot get the password (unless of course the passwords are to weak, but that's another story...) – Karoly Horvath Jul 05 '11 at 16:11

12 Answers12

136

An MD5 hashed password is not reversible. (MD5 is hashing, and not really encrypting, so there's a subtle difference). And yes you'll definitely want to provide a password "reset" process (and not simply email the password).

To give you a high level workflow for secure password resets...

  1. When user asks to reset their password, make them enter their email address
  2. Don't indicate if that email address was valid or not (just tell them that an email was dispatched). This is open for debate as it lowers usability (i.e. I have no idea which email I registered with) but it offers less information to people trying to gather information on which emails are actually registered on your site.
  3. Generate a token (maybe hash a timestamp with a salt) and store it into the database in the user's record.
  4. Send an email to the user along with a link to your https reset page (token and email address in the url).
  5. Use the token and email address to validate the user.
  6. Let them choose a new password, replacing the old one.
  7. Additionally, it's a good idea to expire those tokens after a certain time frame, usually 24 hours.
  8. Optionally, record how many "forgot" attempts have happened, and perhaps implement more complex functionality if people are requesting a ton of emails.
  9. Optionally, record (in a separate table) the IP address of the individual requesting the reset. Increment a count from that IP. If it ever reaches more than, say, 10... Ignore their future requests.

To give you a little more detail into hashing...

When you hash a value like a password using the md5() function in PHP, the final value is going to be the same for that password no matter which server you run it on. (So there's one difference we can see right away between hashing and encryption... There's no private/public key involved).

So this is where you'll see people mention a vulnerability to rainbow tables. A very basic explanation of a rainbow table is... You md5() hash a bunch of dictionary words (weak passwords) in order to get their md5() hashed values. Put those in a database table (rainbow table).

Now, if you compromise a web site's database, you can run the users' hashed passwords against your rainbow table to (in essence) "reverse" the hash back to a password. (You're not really "reversing" the hash... But you get the idea).

That's where "salting" your passwords is best practice. This means (again, very basic idea here) that you append a random value to the users' passwords before you hash it. Now, when the rainbow table is run against your database, it's not as easily "reversed" because the md5() hash of "password" is different than "password384746".

Here's a nice SO Q/A that should help. Secure hash and salt for PHP passwords

Community
  • 1
  • 1
Jared Cobb
  • 5,167
  • 3
  • 27
  • 36
  • 1
    @Jared Cobb: What do you mean exactly with 8.? – testing Feb 08 '13 at 14:34
  • 2
    @testing Good question... It's very similar to point 9, except in this scenario the attacker is using a bot net, proxy, or other round robin method to hit you from a different IP address each time. For example, if your system detects that a huge increase of "Forgot Requests" are happening within a matter of minutes or hours, you might consider temporarily slowing or disabling the feature. (Of course, your threshold will be determined by your own subjective judgement on your user base size and what's considered "normal" volume). – Jared Cobb Feb 08 '13 at 21:53
  • @JaredCobb #7 I usually generate new token right after they reset password, so next time he must use new token to reset password. – Ing. Michal Hudak Dec 12 '13 at 10:13
  • Can I use previous user's password *(which is hashed and stored into database)* as that random token *(for the third step)*? Actually I need that to pass it as a argument for that reset-url. Can I do that? – stack Jun 24 '16 at 01:29
  • @stack while you _technically_ could, on principle I wouldn't. You'd essentially be exposing secure data about the user (a version of their password) over email. Whereas if you generate and store a _new_ token in a different field, you now have the ability to invalidate that token (delete it) based on different business rules without having to touch the password. You can store something like `md5( time() . 'my-site-unique-hash' )` just as easily. – Jared Cobb Jun 24 '16 at 13:23
  • @JaredCobb Ah I see. Also I've asked a related *(and more advanced)* question [here](http://stackoverflow.com/questions/38015123/how-can-i-create-a-forgot-password-system). If you have some free time please take a look at it. – stack Jun 24 '16 at 13:54
  • @JaredCobb - Regarding 5. - Do you suggest they have to submit fe. their username to validate the user when they land on the link you provided with mail? – Thoaren Jan 15 '17 at 21:46
  • 1
    @Thoaren Forcing them to submit their username is optional in my opinion. I don't think it presents any additional security. If someone obtained the link (fraudulently) from the user's email account, they would have presumably already compromised the account on _your_ site as well. If the user's email address **is** their username, again, it would be a moot point. – Jared Cobb Jan 15 '17 at 23:24
  • is this still the relevant way to 2019? – Imnotapotato Feb 25 '19 at 18:22
9

According to this post The definitive guide to forms based website authentication, for step 3. and 4., I'm not sure you should send the same token you are storing.

I guess you must send the token, then hash it and stored the hashed token in DB. Otherwise, if your database is compromised, one can have access to the reset password page.

To summarize :

$token = md5(microtime (TRUE)*100000);
$tokenToSendInMail = $token;
$tokenToStoreInDB = hash($token);

where hash is a hashing algorithm.

Community
  • 1
  • 1
Mansur Khan
  • 1,675
  • 1
  • 12
  • 24
  • 1
    That's a really good point, storing the hashed token in DB. +1 – Avinor Jul 30 '13 at 08:28
  • 1
    Using a predictable token is not a good idea though... It should not be based on anything predictable, like the time. (It would allow an attacker to iterate through the values around the time the token was generated) In PHP7, `random_bytes` should be used. On PHP5, the compatibility implementation or `openssl_random_pseudo_byte` should be used. – Gert van den Berg Mar 22 '16 at 11:52
  • 1
    (Another option is to use a hash of a secret random string and the user's details and hopefully some decent random data. Since it is being stored in the DB, there is no reason for the token to be predictable) (using this method, I would generate the token using `password_hash` on the long string and send a base64-encoded version of the hash in the link). (hashing it again before storing in the DB might be an option as well, but expiring the reset link after 4-24 hours is likely a better defense) (with proper random data, just sending the data base64 encoded should be fine) – Gert van den Berg Mar 22 '16 at 12:03
7

No, MD5 is irreversible. The point of hashing passwords is to make it so an attacker who gets access to your database can't access everyone's passwords.

That said, MD5 (particularly unsalted MD5) can generally be attacked using a rainbow table. For security, you're better off using bcrypt.

ceejayoz
  • 176,543
  • 40
  • 303
  • 368
3

As Marcus Reed stated, in 2015/2016 if you have PHP version >=5.5 don't use MD5, password_hash() and password_verify() provide an easy and secure hashing for your password with the ability to provide a cost and automatically salts the hash.

I don't have the ability to vote or comment currently which is why I'm providing a definitive statement to avoid confusion.

Chris
  • 269
  • 1
  • 3
  • 9
3

You cannot decrypt the password, and you shouldn't even consider sending a password to a user via plaintext. (That is the #1 way to make me never ever use a site again; it's a GIGANTIC security hole.) Provide a password reset page that is triggered from a link containing a time-associated key that is sent to the user's password recovery email; that's the current state of the art in password recovery.

Paul Sonier
  • 38,903
  • 3
  • 77
  • 117
3

The best thing for you to do is request people submit their email address when registering. Then if they forget, have a forgot password link which resets their password with a random value which is emailed to them so they can gain access and then change their password back to something more memorable. This way you don't need to compromise the security. You could have a link which they just need to submit their username into, butfor better security you should have a question and answer or memorable word.

tom
  • 31
  • 1
2

Use php's built in password_verify and password_hash.

Marcus Reed
  • 91
  • 1
  • 1
  • 8
2

MD5 is intended to be a one-way hash. You will need to have them reset their password.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
2

Write a page that accepts the md5 and email address as a get paramaeter and looks in the db for the email and md5'd password. Following Jared Cobb notes, that should get you on the right path. i just added some examples as well

eg url to send http://yourdomain.com/resetpassword.php?code=md5codesentviaemail

 $code = isset($_GET['code']) ? $_GET['code'] : '';
    $email = isset($_GET['email']) ? $_GET['email'] : '';
$checkPw = '';

    if(empty($code) || empty($email))
    {
     die();
    }
    $sqlQuery = 'SELECT * FROM users WHERE email = "'.$email.'";
//remember to check for sql injections
    //then get the results as an array, i use a database class eg $user

    if(!empty($user['password']))
    {
     $checkPw = md5($user['password']);
    }else
    {
     die();
    }

    if($checkPw !== $code)
    {
     die();
    }else
    {
    //display form for user to change password
    }

this should be sufficient enough for you to know that the user is a valid user and change his password

Ryan
  • 1,119
  • 7
  • 10
1

No you cannot decrypt it. that is the whole idea.

You would need to send them a temp password and for them to reset it.

Naftali
  • 144,921
  • 39
  • 244
  • 303
1

You'll need to do a password reset page. There's no way in PHP to decrypt MD5.

Michael Irigoyen
  • 22,513
  • 17
  • 89
  • 131
1

MD5 is a one way function. You can't decrypt it. SO you need to have a password reset page.

Balanivash
  • 6,709
  • 9
  • 32
  • 48