Sponsored by

  • Intel
  • HP

netmagTutorial

Store and retrieve passwords with PHP

Think your data is safe? Paul Hudson suggests you think again...

Poor security can be fatal, as Mary Queen of Scots would tell you if she were still alive today (although she’d probably only speak French so you might not understand her). Along with some Catholic noblemen, she plotted to overthrow the rule of her cousin, Queen Elizabeth, in what has become known as the Babington Plot, after the lead conspirator. Mary exchanged letters about the plot with Babington using hollow beer barrel stoppers, with each letter kept safe from prying eyes by the use of a substitution cipher made up of 59 symbols representing letters and common phrases.

Unfortunately for Mary, Sir Francis Walsingham – Elizabeth’s spymaster – intercepted each barrel, extracted the letter, decoded it, and replaced it so the conspirators were unaware of his intrusion. Found guilty of treason, Mary was sentenced to death, and her 14 co-conspirators were hanged, drawn and quartered.

Advertisement

Most of you – we hope – won’t be working in such life-and-death situations, but we all know that securing your customer data is the number one priority of any online business. If you’re storing your passwords in plain text, you’ve got several big problems to overcome. First, anyone with the most basic network sniffer can watch your web packets whizz by along with user passwords as needed. Second, anyone with access to your databases – and this is probably many people, if you’re using a shared host solution – can simply read the passwords straight from your tables. Third, anyone with access to your backup tapes/ discs/servers (particularly if you’re using an online backup solution) can extract passwords just as easily as if they had the databases locally.

Safe transfer

If you’re already using HTTPS then your data is safe during transfer, but completely vulnerable in storage – and we can use PHP to fix that. The quickest and easiest way to protect passwords is using a hash, which you can think of as being oneway encryption; what goes in, can’t come back out. If you’re thinking this is useless, think again – it’s a highly secure way to save passwords when you don’t actually want to know what the password is in plain text. For more advanced protection, PHP has a complete encryption system capable of locking out even the most capable hackers.

Please keep in mind that encryption is only as good as your implementation. A couple of years ago, WinZip incorporated AES encryption into its product, and branded it as a huge step forward for security. Sadly its implementation was poor, and could be hacked in minutes – just using the AES buzzword didn’t save them. As Bruce Schneier said: “cryptography is hard, and simply using AES in a product does not magically make it more secure.” So, while our implementation is quite strong here, don’t knock it: it’s better to be safe than sorry!

Both hashing and encryption have their uses: hashes are very fast to do, require very little code and are extremely hard to break. Encrypted text is two-way (meaning that you can get the plain text back from the cipher text) but is CPU-heavy and if someone discovers your encryption key then they can read all your data. PHP has built-in support for hashes, whereas it requires the mcrypt library to be able to do encryption. If you’re on Windows, remove the semi-colon from the ;extension=php_mcrypt.dll line in your php.ini file. On Unix, compile your PHP with --with-mcrypt.

1. The easiest way to secure your passwords is using a hash, with the most common hash being MD5 (MD is short for Message Digest). When you pass a string into the md5() function, you’ll get a 32-character hash back that uniquely identifies that string.

There are several important things you need to know about hashes. First, a given string will produce the same hash every single time; the hash doesn’t vary. Second, a slightly different string – even just capitalising one letter that was lowercase before – will produce a vastly different hash value. Third, no matter how long the input string is, the output hash will always be a fixed size. In the case of MD5, this is 32 characters. Fourth, it’s very hard to find another string that will generate the same hash value as your input string. When this happens (if “hello, Mary” and “hello, Bob” were to generate the same hash value, for example), it’s called a ‘collision’, but the chances of finding a collision with MD5 hashes is terrifically small – approximately one in 340 billion billion billion billion billion billion billion billion billion billion. So quite tiny, then.!

<?php&#36;string1 = "Hello, world!";&#36;string2 = "Hello, World!";&#36;string3 = "abc";&#36;string4 = "This is a verylong string oh yes it is"; echo md5(&#36;string1), "\n";echo md5(&#36;string2), "\n";echo md5(&#36;string3), "\n";echo md5(&#36;string4), "\n";?>

That will generate the following output:

6cd3556deb0da54bca060b4c394798
39
65a8e27d8879283831b664bd8b7f0a
d4
900150983cd24fb0d6963f7d28e17f
72
33ee255a4220346f442efd09325642
06

If you didn’t see the source code, it’d be impossible to tell which hash came from which string – that’s the beauty of hashes.

Now, the problem with MD5 is that it has now been broken – researchers can generate an MD5 collision in a little over an hour using a very powerful computer. This isn’t a huge problem if you don’t expect legions of hackers to band together to crack your data, but if you want to be extra sure, there’s an even stronger hash system called SHA1. This stands for Secure Hash Algorithm, and was developed by the National Security Agency in the US for governmental use. This works in the same way as MD5, except it generates a 40-character hash. That might not sound much stronger, but behind the scenes it’s the difference between a 128-bit hash value (MD5) and a 160-bit hash value (SHA1), with each extra bit effectively doubling the time it takes to do a brute-force hack. So, what takes researchers an hour to crack in MD5 would take them approximately four billion hours using SHA1.

SHA1 is not perfect. Four billion hours is the best case scenario, where a hacker has to try every combination. In reality, they’re likely to find it in about half that time using brute force, and a lot less time again if they use a more sophisticated attack. Don’t let that put you off: SHA1 is more than secure enough to protect you against everything except large governments. And you don’t have anything to hide from them, do you?

2. Now you know how hashing works, we can knock together a quick password authentication system that protects passwords using SHA1. What we need to do is accept user input from a form (their username and password), hash the password, then check it against the database of hashed passwords to see if there’s a match. Remember, a given string (in this case, the user’s password) will always generate an identical hash value, so we store hashes in the database and check them against hashed user input. Once the user authenticates successfully, we’ll want to store their user ID as a session value so we know who they are. Here’s how it looks in PHP:

<?phpif (isset(&#36;_POST username"])) {&#36;user = &#36;_POST ["username"];&#36;password = sha1(&#36;_POST ["username"]);&#36;result = mysql_query("SELECT ID FROM users WHEREUsername = &#36;user ANDPassword = &#36;password ;");if (mysql_num_rows (&#36;result)){ extract(mysql_fetch_assoc(&#36;result), EXTR_ PREFIX_ALL, "user"); &#36;_SESSION["USER_ID"] =&#36;user_ID;echo "Authentication was successful!";}else { echo "Authentication failed."; }} ?>

You’ll need to set up the users table yourself – Username should be a VARCHAR field of at least 15 characters (some people like really long usernames!), and Password should be a CHAR field of exactly 40 characters.

3.Another thing hashes are good for are for verifying that files have been downloaded properly. Any Unix-based computer (probably Linux) will come with the program md5sum, which takes a filename as its parameter and returns the MD5 hash of that file. This essentially treats the entire content of the file as a string, which means that if the last part of the file was corrupted during download it will have a different MD5 sum to the original.

In practice sites that offer large downloads, such as Linux distro DVD ISOs, which are usually at least 2GB, provide MD5 sums for the ISO so that downloaders can check they have received the ISO properly and not some corrupt copy, or worse, some virus-ridden replacement a hacker managed to insert on to the server. Rather than making you convert a file to a string then hash it yourself, PHP provides the md5_file() and sha1_file() functions, which take a filename as their only parameter and return the hash of that file. For example:

echo md5_file("fedoracore5.iso");

Yes, sha1_file() does make longer hashes that are much more secure, but in this function it’s not about security, it’s just about checking that a file is safe.

4. The last thing we’re going to look at is proper, two-way encryption. Unlike hashes, ‘real’ encryption enables you to retrieve the original password from the database in plain text. In practise, the difference is this: when someone forgets their password, you need to reset it to a random password if you’re using a hash, but with encryption you can just email them their password.

This is where it gets hard: if you’re going to do encryption, you need to do it properly. That entails selecting an encryption algorithm and block cipher mode, producing an initialisation vector, generating a key, then performing the actual encryption. Encryption algorithms are the meat of this process: they decide how strong the encryption process is. For this example we’ll be using 256-bit Rijndael, also known as the Advanced Encryption Standard (AES). It can easily be argued that other encryption algorithms are stronger than Rijndael (if you want the strongest possible, try 256-bit Serpent), but the advantage of Rijndael is that it is the standard for encryption as adopted around the world. If some day it gets hacked, you don’t have to worry that you chose an unusual algorithm – no one ever got sacked for choosing a standard.

The Block Cipher mode decides how individual chunks of your plain text get encrypted. The most popular system is Cipher Block Chaining (CBC), which splits your plain text into chunks, encrypts the first block, then uses the plain text from the first block to XOR against the second block of plain text, and so on. The advantage of this over simply encrypting each block individually is that it makes duplicate chunks of plain text look different. Finally, initialisation vectors (IVs) are there to whiten plain text – to make it look more random.

To create a properly random encryption key, we’re going to use a sha1() hashed string chopped to the exact size required by AES. With that in mind, here’s a working example of encryption using PHP:

<?php&#36;td = mcrypt_module_open(MCRYPT_RIJNDAEL_256, ,MCRYPT_MODE_CFB, );&#36;iv = mcrypt_create_iv(mcrypt_enc_get_iv_size (&#36;td),MCRYPT_RAND); &#36;key = substr(sha1( frosties ), 0, mcrypt_enc_get_key_size(&#36;td));mcrypt_generic_init(&#36;td, &#36;key, &#36;iv);&#36;ciphertext = mcrypt_ generic(&#36;td,See you at 9pm );mcrypt_generic_deinit (&#36;td);mcrypt_module_close(&#36;td); printtrim(&#36;ciphertext) . "\n"; ?>

To decrypt, you need all the same code, with the only exception being that you use mdecrypt_generic() instead of mcrypt_generic(). Yes, you do need to open deinit() and close() mcrypt before you decrypt. Yes, that’s an awful lot of code to do a very simple task, but it’s bulletproof – as long as your key and IV remain secure. Hopefully, we can leave that much in your capable hands.

Log in to Creative Bloq with your preferred social network to comment

OR

Log in with your Creative Bloq account

site stat collection