Showing posts with label database. Show all posts
Showing posts with label database. Show all posts

Wednesday, July 18, 2012

Encrypted User Signup/Login using hash_hmac

So I've been working with CodeIgniter for awhile now and needed a secure user login system. I chose to develop my own since I wanted to learn as much about building a framework as possible. At one point during development I realized I needed a good user password system that didn't store passwords as a hashed string. So I went online and implemented this solution:

Ok, before we do anything, we need to add a "site_key" to our application/config/config.php file:


/*
|------------------------------------------------------------------------
| Site Key
|------------------------------------------------------------------------
|
| This is the global site key used for secure password generation.
|
*/
$config['site_key'] = 'long_random_alphanumeric_string';


The site_key will be used below. I generated mine through KeePass. Now the signup controller:


// SIGNUP for user account controller:
public function validate_signup()
{
     // Use the form validation library to validate user input
     $this->load->library('form_validation');
     
     $this->form_validation->set_rules('...');

     // Run the form validation
     if ($this->form_validation->run() == FALSE)
     {
          $this->index("Optional custom error message!");
     }
     else // if form validation passed
     {
          // Run the INSERT statement and return TRUE/FALSE
          if ($this->user_model->InsertSanitize($_POST['password']))
          {
               redirect('login');
          }
          else
          {
               $this->index("ERROR: signup failed!");
          }
     }
}

The signup controller handles taking new user information, validating it, and then sending the information to the user model to be INSERTED. Next let's look at the user model:

public function InsertSanitize($password)
{
     // Retrieve user input data through POST:
     $user_name = $this->input->post('user_name');
     // Or passed through the method (choose one or the other, not both):
     $password = $password;


     // Do additional validation here if needed:
     if (!isset($user_name) || $user_name == "")
     {
          // You can also use regex or functions like is_numeric()
          return FALSE;
     }

     return $this->Insert($list_of_safe_values, $password);
}



private function Insert($list_of_safe_values, $password)
{
     // Now it's time for the fun stuff!
     // First we need to use crypt() to hash the input password
     $hashed_pass = crypt($password);
     
     // Next we need to call a custom function below (scroll down):
     $enc_pass = $this->encrypt($password, $hashed_pass);

     // Create an array of user info to INSERT
     // Be sure to store the encrypted and hashed password
     $data = array(
          'list_of_safe_values' => $list_of_safe_values,
          'enc_pass' => $enc_pass,
          'hashed_pass' => $hashed_pass
     );

     // Run the INSERT
     if ($this->db->insert('user_table', $data))
     {
          // Optional: Return the last records ID
          return mysql_insert_id();
          // Or simply:
          return TRUE;
     }
     else // if INSERT failed
     {
          return FALSE;
     }
}


private function encrypt($password, $nonce)
{
     // Retrieve the site_key
     $site_key = $this->config->item('site_key');

     // Return the encrypted password using hash_hmac

     return hash_hmac('sha512', $password . $nonce, $site_key);
}


Be sure when updating your website or overwriting files that you keep the site key! If you forget to re-add it, existing users won't be able to login and new users won't have the site_key attached to their accounts.

Finally, I'll show you how to use this system when logging in:

public function validate_login()
{
     // Just like before, run form validation

     $this->load->library('form_validation');
     
     $this->form_validation->set_rules('...');



     if ($this->form_validation->run() == FALSE)
     {
          $this->index("Optional custom error message!");
     }
     else // if form validation passed
     {
          // Next, run validation in the user model:
          $data = $this->user_model->Validate($_POST['password']);
     }
}



public function Validate($password)
{
     // Retrieve user input data through POST:
     $user_name = $this->input->post('user_name');
     // Or passed through the method:
     $password = $password;


     // Do additional validation here if needed:
     if (!isset($user_name) || $user_name == "")
     {
          // You can also use regex or functions like is_numeric()
          return FALSE;
     }

     // Next we need to call a custom function below (scroll down):
     $enc_pass = $this->retrieve($user_name, $password);



     // With the encrypt password we can now validate our user login
     $sql = sprintf(self::constant . " WHERE user_name = '%s'
          AND password = '%s' LIMIT 1",
          mysql_real_escape_string($user_name),
          mysql_real_escape_string($enc_pass));


     // Run your queries however you normally do
     return $this->LoadFromDb($sql);
}



private function retrieve($user_name, $password)
{
     // Find user by user_name
     $data = $this->FindUser($user_name);

     // Get the hashed user password

     if (isset($data))
     {
          foreach ($data as $obj)
          {
               $hashed_pass = $obj->hashed_pass;
          }
          return $this->encrypt($password, $hashed_pass);
     }
     else
     {
          return FALSE;
     }
}

And that's it! But before I leave, let me explain quickly in case it's a little unclear.

First we created a site key, which is used to further enhance the encryption process.

Then we allowed a user to signup, validating form data and then data that's passed to the model. Within the Insert() function we took the submitted user password and hashed it, creating a randomly generated, alphanumeric string. This is what is used as a key for decrypting the encrypted password. Then we called the encrypt() function which takes the user password and the hashed key, along with the site key and encrypts them altogether. We returned this encrypted password and INSERTED it into the database along with the hashed key and any other user data (like username, email, etc.).

Now that we have a new user account with an encrypted password, we need to login. We validate the login form and data that's passed to the model. If everything looks good, we call the retrieve() function which loads the requested user information. We grab the supposed user's hashed key, along with the password they provided at login, and run it through the encryption process again.

From here we created a query using the provided user_name and processed encrypted password to see if any entries in the database are returned. If there is a record, that means that the username and password the login provided, matched and was successful. If it fails, that means either the username or password was wrong, or the user doesn't even exist.



Hopefully that all makes sense. Good luck!


Note: sorry if the code looks messed up, Blogger apparently sucks at pasting...also we're technically never decrypting anything. Just encrypting again and comparing.

Simple PHP loop script to transfer user table

A while back I was working on my CodeIgniter project and decided to restructure my database. Since I didn't have many rows that actually mattered (it was all test data), the only thing I needed to transfer over was my user table. So here's a simple example of a script that will transfer records over from one database to another.


<?php

// Set the connection information
$con = mysql_connect("localhost", "root", "root");

// Select the original database containing old records
mysql_select_db("old_db", $con);

// Check the connection
if (!$con)
{
die('Could not connect: ' . mysql_error());
}

// Select the records from the database
$query = "SELECT * FROM user_table";

// Run the query
$result = mysql_query($query);

// Now select the new database
mysql_select_db("new_db", $con);

// Loop through each row from the old database
while ($row = mysql_fetch_assoc($result))
{

     // Set each column to a variable
     $user_name = $row['user_name'];
     $password = $row['password'];
     $email = $row['email'];
     // You could also reset defaults or check for NULL columns

     // Here's an example for permissions:
     if ($row['permissions'] == 0)
     {
          $permissions = 1;
     }
     else if ($row['permissions'] > 3)
     {
          $permissions = $row['permissions'] + 1;
     }
     else
     {
          $permissions = $row['permissions'];
     }
     $date = $row['date'];

     // Set up the INSERT query
     $insert = "INSERT INTO new_user_table (user_name, password, email, permissions, date) VALUES ('$user_name', '$password', '$email', '$permissions', '$date')";



     // Run the INSERT query
     mysql_query($insert);


     // Check to make sure this particular INSERT worked
     if (!mysql_insert_id())
     {
          echo "ERROR: there was a problem inserting data.";
     }
}

// Close the connection
mysql_close($con);

?>

That's it! This script is pretty simple and might not be great for really large user tables, but for smaller sites or when you're just testing data, it works fine.

Friday, July 13, 2012

Using CRON and PHP scripts with Dreamhost

As a followup to my previous post regarding inserting RSS feed data into a MySQL database, I wanted to quickly explain how I used DreamHost's CRON to create an automated news feed.

First make sure you have a script ready to use. Something that you want to run at certain time intervals. This could be the RSS feed script from before, something that scans user accounts, looks at site data and publishes an XML document, whatever. Just make sure it works (connects to DB, grabs or modifies data without problems, and closes the connection). Also be sure to change any local settings. For instance, if your script still says "localhost" for the connection string, it's not going to work!

Next we need to set up a Shell account, which is really easy in DreamHost:




It may take a few minutes for the new user to be created. After it's done, FTP into your web server using the new Shell account. I use Filezilla for this. If you don't know how to FTP in or setup Filezilla, this guide will probably help (or just Google it). Once you're in, create a folder called "cron" (or whatever you want to name it) and upload your scripts.




Almost done. Head back to DreamHost and click on "Cron Jobs", then "Add New Cron Job".



Finally we need to enter the cron settings. Select the shell user account you just created and title this cron job whatever you'd like. For "email output to", optionally enter an email address. If you do this, it will email you the results of your script. For instance, if you wrote your script in PHP and had any echo statements, you'll receive them. This is handy for when you first test your cron job so you can check that each part is running successfully. For example, you may want to echo/output any possible errors, success messages for DB connections/inserts/selects/etc., and a message at the end saying "script successful" or "script failed". After a few successful iterations of the cron job you can turn it off if you please.


For status we obviously want it enabled, although you can disable it later if you deem this script unnecessary. I prefer this as opposed to deleting it in case you want it later. For the command, we enter the path to the script. You can also write command code here, but I prefer having an external script I can modify and play with. I also leave locking on. And lastly, you can set your cron interval. This particular script runs every 15 minutes since I need fresh news frequently.




And that should be it. Once you submit it the cron job will run at the next interval and hopefully the script will execute successfully! For me, this allows me to read incoming RSS feeds, insert it into the DB, and then use the data on my website. It works great and I haven't had any problems since implementation. Good luck!


UPDATE: that space between php and /home/ in the command is on purpose! Be sure to do the same.

Wednesday, January 4, 2012

RSS feed(s) to MySQL database - a script using Simplepie and PHP in Codeigniter

A few weeks ago I was looking for a PHP script that would take RSS feeds and insert them into a MySQL database so I could manipulate them in my environment. There were a few examples here and there that were useful, but not one big example that suited my needs. Here's what I ended up doing.

First I needed to choose a RSS parser. A quick Google search revealed Magpie and Simplepie. I compared the two and ultimately chose Simplepie since it's being actively developed and the documentation seemed thorough and accessible.

After downloading the latest version (1.2.1 at the time of writing), I unzipped it and grabbed the "simplepie.inc" file. This went in the root of my /CodeIgniter/scripts folder along with a new PHP file called "rss.php".

The Code

require_once('simplepie.inc'); 

This allows the script to take advantage of Simplepie. Next:

// Create a new instance of Simplepie.
$feed = new SimplePie();


// There are two options to parse RSS feeds depending on your needs.
// The first is a single url:
$feed->set_feed_url('http://www.yoururl.com/rss.xml');
// The second is an array of feeds, if you're parsing more than one:
$feed->set_feed_url(array(
'http://www.yoururl.com/rss.xml',
'http://www.yoururl.com/rss.xml'
));

// Only use one of the above ways. Comment out or delete the other.

// Set a folder where the cache can reside.
$feed->set_cache_location($_SERVER['DOCUMENT_ROOT'] . '/CodeIgniter/application/cache');

// If you're on Dreamhost like I am, you'll need to modify the cache location to something like this:
$feed->set_cache_location('/home/[UserName]/myFolder/cache');
// This will ensure the output from the RSS feed matches the MySQL encoding, otherwise you end up with characters like this: รข€
$feed->set_output_encoding('ISO-8859-1');
// Initialize Simplepie.
$feed->init();

// This makes sure that the content is sent to the browser as text/html and the UTF-8 character set (since we didn't change it).
$feed->handle_content_type();


Next we need to establish a database connection. For testing purposes, I ran this script on my local machine using XAMPP.

// Make sure to put in the proper server, username, and password information here.
$con = mysql_connect("localhost", "mysql_username", "mysql_password");
// Select the database you want to use.
mysql_select_db("myDB", $con);

// Then check if the connection failed. If it did, die with the error message.
if (!$con)
{ die('Could not connect: ' . mysql_error()); }

Now it's time to loop through the parsed items and load them to the database.

foreach ($feed->get_items() as $item)
{
// Here are some of the various items you can pull from an RSS feed:
        $permalink = $item->get_permalink();
$title = $item->get_title();
$content = $item->get_description();
$date = $item->get_date('Y-m-j g:i:s');

        // This next part will check for duplicated items by hashing the content and comparing it to the hashes in the database. This part is optional, but recommended.
        $content_hash = md5($content);
$result = mysql_query("SELECT * FROM my_posts WHERE content_hash = '" . $content_hash . "'");
$num_rows = mysql_num_rows($result);

if ($num_rows > 0) { }
        // If this item's hash doesn't match any in the database
else
{
// This part will check if the date has been set. Sometimes feeds don't post the date so I found this to be necessary. If it has a date, then INSERT everything, if not, set a default date in MySQL and don't INSERT the date.
                if (isset($date))
{
mysql_query("INSERT INTO my_posts (date, content, content_hash, url, title) VALUES ('" . $date . "', '" . $content . "', '" . $content_hash . "', '" . $permalink . "', '" . $title . "')");
}
else
{
mysql_query("INSERT INTO my_posts (content, content_hash, url, title) VALUES ('" . $content . "', '" . $content_hash . "', '" . $permalink . "', '" . $title . "')");
}
}
}

echo "SCRIPT COMPLETE.";

// Finally, remember to close the connection.
mysql_close($con);

That's it. I can run this, parse feeds, grab the data and INSERT it into the MySQL database. There's probably a more elegant and simple way to do it, but this worked for my needs. From here you can access these posts and manipulate them on your site however you please. I added the ability to comment, rate them, modify and delete, etc.