1. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Posts
    311
    Rep Power
    8
    I will try to post my other codes tonight, if I can't find the time, I will tomorrow! And where do I have to put in the bin2hex(), base64_encode() when I want to get a number and letter output? (I am using the RNG previously posted by either Jacques1 or E-Oreo)
  2. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Posts
    311
    Rep Power
    8
    Ok here is the RNG


    rnum.php

    PHP Code:
    <?php
    function rnum () {
     
        
    $raw_length 16
        
    $buffer ''
        
    $buffer_valid false
        if (
    function_exists('mcrypt_create_iv') && !defined('PHALANGER')) { 
            
    $buffer mcrypt_create_iv($raw_lengthMCRYPT_DEV_URANDOM); 
            if (
    $buffer) { 
                
    $buffer_valid true
            } 
        } 
        if (!
    $buffer_valid && function_exists('openssl_random_pseudo_bytes')) { 
            
    // Note: I added a check against $is_crypto_strong 
            
    $buffer openssl_random_pseudo_bytes($raw_length$is_crypto_strong); 
            if (
    $buffer && $is_crypto_strong) { 
                
    $buffer_valid true
            } 
        } 
        
    // Note: I added an error supression operator on is_readable 
        // If open_basedir is enabled, /dev/urandom is almost certainly not going to 
        // be inside it, and this will cause PHP to throw an E_WARNING from is_readable 
        
    if (!$buffer_valid && @is_readable('/dev/urandom')) { 
            
    $f fopen('/dev/urandom''r'); 
            
    // Note: I added a check to make sure $f was actually opened successfully 
            
    if ($f) { 
                
    $read strlen($buffer); 
                while (
    $read $raw_length) { 
                    
    $buffer .= fread($f$raw_length $read); 
                    
    $read strlen($buffer); 
                } 
                
    fclose($f); 
                if (
    $read >= $raw_length) { 
                    
    $buffer_valid true
                } 
            } 
        } 
        if (!
    $buffer_valid || strlen($buffer) < $raw_length) { 
            
    $bl strlen($buffer); 
            for (
    $i 0$i $raw_length$i++) { 
                if (
    $i $bl) { 
                    
    $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0255)); 
                } else { 
                    
    $buffer .= chr(mt_rand(0255)); 
                } 
            } 
        } 
         
        
    // This is your secure random number 
    return $buffer;

    }

    and forgot_password.php:

    PHP Code:
    <?php 

        
    // First we execute our common code to connection to the database and start the session 
        
    require("common.php"); 
        require(
    "lib/rnum.php"); 
        require(
    "lib/mail.php"); // an email function is stored here, called mail_f
        
    require("lib/password.php"); // calling the library for the hash

        // This variable will be used to re-display the user's username to them in the 
        // login form if they fail to enter the correct password.  It is initialized here 
        // to an empty value, which will be shown if the user has not submitted the form. 
        
    $submitted_username ''
         
        
    // This if statement checks to determine whether the login form has been submitted 
        // If it has, then the login code is run, otherwise the form is displayed 
        
    if(!empty($_POST)) 
        { 
            
    // This query retreives the user's information from the database using 
            // their username. 
            
    $query 
                SELECT 
                    id,
                    username, 
                    email 
                FROM users 
                WHERE 
                    username = :username 
            "

             
            
    // The parameter values 
            
    $query_params = array( 
                
    ':username' => $_POST['username'
            ); 
             
             
                
    // Execute the query against the database 
                
    $stmt $db->prepare($query); 
                
    $result $stmt->execute($query_params); 
           
             
            
    // This variable tells us whether the user has successfully logged in or not. 
            // We initialize it to false, assuming they have not. 
            // If we determine that they have entered the right details, then we switch it to true. 
            
    $login_ok false
             
            
    // Retrieve the user data from the database.  If $row is false, then the username 
            // they entered is not registered. 
            
    $row $stmt->fetch(); 
            if(
    $row
            { 
               if (
    $_POST['email'] == $row['email'])
                {
                       
    //A mail has been sent, click on the link to recover your password.";  
    $email $row['email'];
    $password_token rnum();
      
    $query 
                SELECT
                    id
                FROM users 
                WHERE 
                    username = :username 
            "

             
            
    // The parameter values 
            
    $query_params = array( 
                
    ':username' => $_POST['username'
            ); 
             
            
                
    // Execute the query against the database 
                
    $stmt $db->prepare($query); 
                
    $result $stmt->execute($query_params); 
            

    $row $stmt->fetch(); 
      
    $query 
                INSERT INTO responses ( 
                    id,
                    response
                ) VALUES ( 
                    :id,
                    :response
                ) 
            "

              
    $hash password_hash($password_tokenPASSWORD_BCRYPT, array("cost" => 10));  
            
    // Here we prepare our tokens for insertion into the SQL query.  We do not 
            // store the original password; only the hashed version of it.  We do store 
            // the salt (in its plaintext form; this is not a security risk). 
            
    $query_params = array( 
                
    ':id' => $row['id'], 
                
    ':response' => $hash
            
    ); 
             
             
                
    // Execute the query to create the user 
                
    $stmt $db->prepare($query); 
                
    $result $stmt->execute($query_params); 
            
    $id $row['id'];
    $mail_to      $email;
    $mail_subject 'Forgot password';
    $mail_body 'Submit this code and number at the link below: ';
    $mail_body .= $password_token;
    $mail_body .= ' number ';
    $mail_body .= $id;
    $mail_body .= ' click this link: your link';

    mail_f ($mail_to$mail_subject$mail_body);

                }
               
            } 
           else {
                    echo 
    "Please enter your email and username."
           }
        } 
         
    ?> <html>
    <body>
    <form action="forgot_password.php" method="post"> 
        Username: 
        <input type="text" name="username" value="<?php echo $submitted_username?>" /> 
        <br /><br /> 
        Email:
        <input type="text" name="email" value="" /> 
        <br /><br /> 
        <input type="submit" value="Login" /> 
    </form> 
    </body>
    </html>
    and the table 'responses' where the tokens are stored, has the structure:

    Code:
    CREATE TABLE responses (
      id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
      response varchar NOT NULL,
    ) ENGINE=InnoDB
    ;
    Last edited by derplumo; April 16th, 2013 at 02:52 PM.
  3. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Apr 2013
    Posts
    15
    Rep Power
    0
    sorry man for not replying, just had some bad news from my coordinator that the prescription system is lacking features. and have therefore included google maps api with customised markers and marker thumbnails.. if the code interests anyone. once I finish off stylying and another feature, I'll jump back on the forgot password option.

    cheers everyone!
  4. --
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Jul 2012
    Posts
    3,904
    Rep Power
    1045
    You're talking to a spam bot. I've already notified the mods of the spam.

    Back to topic:

    The encoding must be done right after generating the raw bytes and before hashing and storing the token. It's best when you put it into the random function itself. I recommend hex encoding, because it's least obscure and URL-safe.

    During debugging, store the token itself along with the hash so that you can check whether it's correct. 16 bytes are 32 hex digits, so you need a CHAR(32).

    Some comments on your code:
    • Get rid of the try-catch. Seriously. All it does is bloat the code and cut off important information. You don't need it, neither for development nor during production. Just let the exception bubble up and show you a detailed error message. Only catch an error if you actually wanna do something with it.
    • Get rid of the comments. This is no longer a tutorial, so no need to bloat your code with long explanations.
    • For the random number generation, use the unaltered, original code from the library. The file checks are a good modification, but the $is_crypto_strong makes no sense. The only situation when this is false is when /dev/urandom has run out of entropy. How does reading from the same source in the next step solve this problem? Again: Do not fumble with crypto algorithms.


    You also need to change the table structure to be able to check the expiration of a request and to actually track the password resets and identify attacks. If, for example, the same IP address requests a password reset for every user, there's clearly something wrong.

    password_reset_requests
    Code:
    - reset_key CHAR(32) PRIMARY KEY -- the random token used to identify a password reset
    - user INT NOT NULL -- the requester
    - secret CHAR(60) NOT NULL -- the hash of the secret random token
    - request_timestamp DATETIME NOT NULL -- used to identify whether the reset is still active and to identify attacks
    - request_ip VARCHAR(39) NOT NULL -- the IP address (v4 or v6) that made the request; used for analysis
    - used BOOLEAN DEFAULT 0 NOT NULL -- whether the reset has taken place
    - active BOOLEAN DEFAULT 1 NOT NULL -- to explicitly deactivate requests when a new request has been made or an attack is going on
    Before you insert a new request, you have to deactive all current requests. This should be done by the database itself with a trigger. But MySQL is crap and cannot do this. So do it with PHP -- or get a real database system like PostgreSQL.

    When sending the reset token, you should include the key in the URL so that the user doesn't have to copy/paste is manually. But it's a good idea to keep the secret separate to avoid accidental sharing of the reset link.
    Last edited by Jacques1; April 17th, 2013 at 06:10 AM.
  5. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Posts
    311
    Rep Power
    8
    Stupid spambots -_-

    I've PhpMyAdmin, so I can also make triggers, and yes, I will finally get rid of all that excessive stuff.

    But do you mean with reset_key in the sql the original key? that didn't have to be stored in plain text right?

    Thanks for the comments, I'll alter it tonight.
  6. --
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Jul 2012
    Posts
    3,904
    Rep Power
    1045
    Originally Posted by derplumo
    I've PhpMyAdmin, so I can also make triggers
    Of course you can create triggers, but MySQL cannot do triggers that modify the table itself. So you cannot set all current requests to active = 0 before inserting a new one. There are workarounds for this, but they're ugly and complicated.

    If you have any chance to get rid of MySQL and switch to PostgreSQL, do it. If not, use PHP.



    Originally Posted by derplumo
    But do you mean with reset_key in the sql the original key? that didn't have to be stored in plain text right?
    The "reset_key" is only used to identify a reset request and look it up in the database. Call it "reset_id", if that's clearer. The "reset_key" is stored as plaintext.

    And "secret" is the hashed random string used to authenticate the requester.

    Most password reset scripts today only generate a random reset token and store it as plaintext. But this means anybody with access to the database (which includes attackers) can capture any account by simply requesting a password reset, fetching the token and setting their own password. The above solution is much more secure, because even if the database gets compromised, the tokens are still not accessible.
  7. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Apr 2013
    Posts
    15
    Rep Power
    0
    who's the spambot?
  8. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Posts
    311
    Rep Power
    8
    some guy with those etc.
  9. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Posts
    311
    Rep Power
    8
    Alright, working all the try catch and comments away, now I have to get the original RNG, but where can I find it? And I try to get that database-system you talked about Jacques1
  10. --
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Jul 2012
    Posts
    3,904
    Rep Power
    1045
    The library:
    https://github.com/ircmaxell/password_compat

    PostgreSQL:
    http://www.postgresql.org/

    But don't rush the latter. Installing and configuring a new database system always takes some time, and PostgreSQL differs from MySQL in several aspects. MySQL is a rather sloppy, simple system mainly aimed at noncritical applications. You know, a blog, a simple shop or whatever. PostgreSQL, on the other hand, is a fully-featured, professional database system much like the big enterprise systems (Oracle DB etc.).

    If you're used to throwing garbage at your database and relying on magical conversions, you'll have to relearn. PostgreSQL is very strict and doesn't allow nonsense values. For example, you can't insert strings into an integer column. You can't fill a TIMESTAMP column with invalid dates etc.

    But you can do pretty much anything that's possible with a relational database, not just SELECTs, INSERTs and UPDATEs.

    It's a shame that PostgreSQL is still so little-known compared to MySQL -- at least in the nonprofessional area. I mean, this is like Photoshop vs. Paint. People deserve better.
  11. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Apr 2013
    Posts
    15
    Rep Power
    0

    Lightbulb Pagination


    sorry guys to bother again, I've been whole day busy adding necessary features to the database.

    Anyone interested, I have pagination code for showing large lists of data and google map API integration which is considerably trivial. But will be more than happy to share, seeing that other people's share lead to the start of my project.

    I've been following through on the forgot pw thingy, seems getting quite complex now, IIRC it was sending mails as it was, although it was using plaintext. Would love to incorporate it to the project.

    Cheers and good night!
    Ian
  12. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Posts
    311
    Rep Power
    8
    Jacques1, what file in the library is the RNG? Funky, tell us something about your features and codes for google maps, send me a pm if you want, or an new threat or something... because we have to keep this forum clean
    Last edited by derplumo; April 18th, 2013 at 03:24 PM.
  13. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Posts
    311
    Rep Power
    8
    Something else, if I want someone to visit the site only once, or one feature is active for one time. So what I want is to validate the IP-adress and store it, and if the user was on the site before (with that IP) the IP is in the database and the feature is blocked. But how do I do this and do I need to hash the IP?
  14. --
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Jul 2012
    Posts
    3,904
    Rep Power
    1045
    Originally Posted by derplumo
    Jacques1, what file in the library is the RNG?
    Well, there's only one file with actual code: lib/password.php

    The PRNG code goes from line 82 to 118. Here's a function for it:
    PHP Code:
    /**
     * Generate a relatively secure hex encoded pseudo random number.
     *
     * Code taken from the password_compat library by Anthony Ferrara (github.com/ircmaxell/password_compat).
     * The resulting number is _not_ necessarily cryptographically secure.
     *
     * @param int $length The length of the resulting hex string
     *
     * @return string|boolean The hex string or false in case of an error
     */
    function gen_rand($length 32) {
        
    $length = (int) $length;
        if (!
    $length) {
            
    trigger_error('Invalid length for random string'E_USER_WARNING);
            return 
    false;
        }
        
    $buffer '';
        
    $raw_length $length 2;        // the number of bytes
        
    $buffer_valid false;
        if (
    function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
            
    $buffer mcrypt_create_iv($raw_lengthMCRYPT_DEV_URANDOM);
            if (
    $buffer) {
                
    $buffer_valid true;
            }
        }
        if (!
    $buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
            
    $buffer openssl_random_pseudo_bytes($raw_length);
            if (
    $buffer) {
                
    $buffer_valid true;
            }
        }
        if (!
    $buffer_valid && is_readable('/dev/urandom')) {
            
    $f fopen('/dev/urandom''r');
            
    $read strlen($buffer);
            while (
    $read $raw_length) {
                
    $buffer .= fread($f$raw_length $read);
                
    $read strlen($buffer);
            }
            
    fclose($f);
            if (
    $read >= $raw_length) {
                
    $buffer_valid true;
            }
        }
        if (!
    $buffer_valid || strlen($buffer) < $raw_length) {
            
    $bl strlen($buffer);
            for (
    $i 0$i $raw_length$i++) {
                if (
    $i $bl) {
                    
    $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0255));
                } else {
                    
    $buffer .= chr(mt_rand(0255));
                }
            }
        }
        if (!
    $buffer_valid || strlen($buffer) < $raw_length) {
            
    trigger_error('Could not generate random string'E_USER_WARNING);
            return 
    false;
        }

        return 
    bin2hex($buffer);



    Originally Posted by derplumo
    Something else, if I want someone to visit the site only once, or one feature is active for one time. So what I want is to validate the IP-adress and store it, and if the user was on the site before (with that IP) the IP is in the database and the feature is blocked. But how do I do this and do I need to hash the IP?
    You cannot reliably limit usage on a public website.

    Using the IP address is a particular bad idea, because it can easily be circumvented (by using proxies, TOR or whatever), and it discriminates a lot of legitimate users that happen to be behind a proxy or router.

    Use a cookie and maybe some of the less known techniques like web storage. A well-informed user can break this at any time, but at least it will hold off average visitors and only has a small risk of creating false positives.

    No, don't hash IPs. They're not that sensitive.
  15. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Posts
    311
    Rep Power
    8
    web storage... if I would use Chrome first, use the function and then use firefox, then I could circumvent the code right? but I've been thinking, if we use web storage, someone might log in to an other computer (on work or something) then he can do it twice, but what you said with the IP, using proxies etc., is also bad... what to do...

    And unfortunatly, I have to use the PHP (when we talked about the triggers), I've to use phpmyadmin... how do I do that?
    Last edited by derplumo; April 19th, 2013 at 12:05 PM.

IMN logo majestic logo threadwatch logo seochat tools logo