PHP encryption for the common man
In this increasingly virtual online world, you have to becareful to protect your data. Learn the basics of encoding andencrypting important bits of information, such as passwords, creditcard numbers, and even entire messages. Get an overview of what itmeans to encrypt and decrypt information, as well as some practicalexamples involving passwords and other data, using PHP's built-infunctionality.

Consider how today's world differs from the world of just 20 years ago. Long ago, in the 1980s, encryption was spy stuff-- something you read about in a techno-thriller by Tom Clancy. Ifsomebody wanted to keep a bit of information private, he encrypted thedata with a password, a pass phrase, or another basic method.

Fast-forward to today and encryption is everywhere. Passwords arestored encrypted in databases. Encrypted tunnels through cyberspace arepossible via SSL, SSH, and other technologies -- not to mention virtualprivate networks. Everyday people can and do use Pretty Good Privacy(PGP) to armor their sensitive files and e-mail.

As a PHP developer, you should be aware that strong securitypractices aren't just for exotic applications -- they're for theproject you're working on now. This awareness runs from the pedestrian(such as not showing plaintext in a password field on a login page) todizzying heights of cryptographic methods (such as DES, MD5, SHA1,Blowfish).

There's not enough time or room to discuss every aspect ofencryption here, but you'll learn the essentials that will cover mostsituations you'll find yourself in. We begin with an overview of whatit means to encrypt and decrypt information, followed by some practicalexamples involving passwords and other data, using PHP's built-infunctionality. Throughout, encryption will be discussed within thelarger context of security. Finally, other PHP extensions and plug-inswill be covered.

Cryptography primer

Cryptography is the art of "secret writing," as the word's Greekroots attest. One of the first, and simplest, forms of encryption isthe Caesar cipher, which takes a plaintext message, shifts the letters n places, and results in a ciphertext. For example:

  • Plaintext: Veni Vidi Vici
  • Ciphertext: Xgpk Xkfk Xkek

By examining the ciphertext, you can use a few heuristic tricks tofigure out the plaintext has been shifted two characters. The Caesarcipher is easy to break. For example: Examine this message and see thatX is repeated a lot, and so is k. It becomes a guessinggame to determine how many four-letter words have that many vowels.Once you have the vowels, you know which way to shift the rest of theletters. It also helps if you know the plaintext is in Latin, but youget the picture.

Modern encryption technologies are a lot more powerful, usingalgorithms that go beyond uniform shifting of letters and symbols. Thisarticle doesn't go into the blow-by-blow details of these algorithms,but just about any PHP installation will include all you need to keepyour data moderately (or extremely) secure.

There is no 100-percent uncrackable, full-proof cryptographicmethod. It seems that every month, some hacker kid and his friends tietogether 1,000 machines and crack the newest encryption method in a fewdays of raw, brute-force computational battering. However, you can makeyour systems and data impervious enough that hackers won't even bothertrying to break in. They'll look for easier pickings elsewhere.

With that in mind, let's jump into a sample PHP application secured by a simple login form.

PHP form-handling without security or encryption

Let's say you're a new Web application developer, and you haven'thad much chance to play around with security features. You've createdyour first application, one that stores usernames and passwords in adedicated user table, but you haven't encrypted those passwords.They're sitting there in plain sight for anyone who accesses yourdatabase. You build a login page like that shown below.



Listing 1. Simple login page



<form action="verify.php" method="post">

<p><label for='username'>Username</label>

<input type='text' name='username' id='username'/>

</p>



<p><label for='password'>Password</label>

<input type='text' name='password' id='password'/>

</p>

<p><input type="submit" name="submit" value="log in"/>

</p>

</form>





What's wrong with this HTML markup? The input type selected for the password field is text, which means that anything the user types into that field appears as plaintext on the screen.

You can easily change the type to password and replaceuser input in that field to a series of asterisks. Basic? Absolutely.However, this step is overlooked in more applications than you wouldimagine, and it's the little things that make people uncomfortable whenit comes to security. Would you put your money in a bank with brokenwindows in its lobby? Maybe. But you expect a bank to be well caredfor. The same goes for your application.

Let's move on to the verify.php file that processes the form submission.



Listing 2. Simple PHP login verification



<?php

$user = $_POST['user'];

$pw = $_POST['password'];



$sql = "select user,password from users

where user='$user'

and password='$pw'

limit 1';

$result = mysql_query($sql);



if (mysql_num_rows($result)){

//we have a match!

}else{

//no match

}



?>





The savvy among you are grinning as you read this. Those of you whoare waiting for the encryption part of this article may be gettingimpatient, but encryption is just one part of the security puzzle.You also have to be smart about how you handle incoming user data. ThedeveloperWorks tutorial "Locking down your PHP applications" (see Resources)talks about SQL injections: the art of sending malformed data to adatabase to cause harm or unwarranted access. No matter how muchencryption you use, leaving that vulnerability open won't help a bit.

You should follow the old security principles of "never trustuser-supplied data" and "defense in-depth." Clean up the incoming dataand protect the database by escaping the incoming string (see Listing3). Defense in-depth is all about having redundant security measures inplace -- not only encryption but also smart handling of user-supplieddata.



Listing 3. Protecting PHP form parsing from user data manipulation



<?php

$user = strip_tags(substr($_POST['user'],0,32));

$pw = strip_tags(substr($_POST['password'],0,32));



$sql = "select user,password from users

where user='". mysql_real_escape_string($user)."'

and password='". mysql_real_escape_string($pw)."'

limit 1';

$result = mysql_query($sql);



if (mysql_num_rows($result)){

//we have a match!

}else{

//no match

}

?>





With judicious use of strip_tags(), substr(), and mysql_real_escape_string(),you've stripped out any potentially harmful commands, cut the stringsdown to 32 characters, and escaped any and all special characters thedatabase might interpret as part of a nonintended command string.

At the end of this process, you still have a plaintext password inthe database. You can't let that stand. The easiest fix is to use PHP'sbuilt-in crypt() function.

Using crypt()

PHP's built-in crypt() function implements one-way encryption:Once you encrypt something, you can never get it back into plaintext.At first blush, this idea seems ridiculous. The point is to protectinformation, then be able to use that information, which usually meansbeing able to decrypt it.

Don't despair. One-way encryption schemes, and crypt()in particular, are extremely popular, secure ways of protectinginformation. If your list of user passwords falls into the wrong hands,there's literally no way it can be decrypted to plaintext.

Let's go back to the password example. The notational PHPapplication probably includes a module that lets system administratorscreate, edit, and delete users. Before storing a user's record into theusers table, for example, it's likely that a PHP script uses crypt() to encrypt the password.



Listing 4. Using crypt() to encrypt a password



<?php

$user = strip_tags(substr($_POST['user'],0,32));

$pw = strip_tags(substr($_POST['password'],0,32));



$cleanpw = crypt($pw);



$sql = "insert into users (username,password)

values('".mysql_real_escape_string($user)."',

'".mysql_real_escape_string($cleanpw)."')";

//.....etc....

?>





crypt() takes as its first argument a string of plaintext, applies a saltto it to influence the randomness of the encryption algorithm, andgenerates a one-way ciphertext of the input plaintext. If you don'tsupply a salt, PHP generally defaults to its system salt, which can beone of the following values and lengths:

AlgorithmSalt
CRYPT_STD_DESTwo-character (default)
CRYPT_EXT_DESNine-character
CRYPT_MD512-character, starting with $1$
CRYPT_BLOWFISH16-character, starting with $2$


Many modern PHP installations use MD5 salts, which use a strong12-character salt, but don't take anything for granted. You can checkyour server's setting with the following snippet of PHP code:



<?php echo "System salt size: ". CRYPT_SALT_LENGTH;?>

The answer will come back 2, 9, 12, or 16, which tells you what you're using. To use MD5 or higher, you can explicitly call the crypt() function along with the md5() function both in the plaintext and salt arguments to get a random ciphertext (see Listing 5). The md5()function hashes whatever string is fed to it and turns it into a32-character fixed-length string. You may prefer another method,depending on your security requirements and personal preferences.



Listing 5. Using crypt() and md5() to encrypt a password



<?php

$user = strip_tags(substr($_POST['user'],0,32));

$pw = strip_tags(substr($_POST['password'],0,32));



$cleanpw = crypt(md5($pw),md5($pw));



$sql = "insert into users (username,password)

values('".mysql_real_escape_string($user)."',

'".mysql_real_escape_string($cleanpw)."')";

//.....etc....

?>





You now have an encrypted password in a database, but no way todecrypt it. How is that useful? Easy: Use the identical method ofencryption on any incoming user-supplied password and compare theresult to your stored password.



Listing 6. Revisiting verify.php



<?php

$user = strip_tags(substr($_POST['user'],0,32));

$pw = strip_tags(substr($_POST['password'],0,32));

$cleanpw =crypt(md5($pw),md5($pw));



$sql = "select user,password from users

where user='". mysql_real_escape_string($user)."'

and password='". mysql_real_escape_string($cleanpw)."'

limit 1';

$result = mysql_query($sql);



if (mysql_num_rows($result)){

//we have a match!

}else{

//no match

}

?>





For example, if the stored encrypted password is i83Uw28jKzBrZF,encrypt the incoming password and compare it to the stored one. Theonly way an attacker can break your encryption is to compare a verylong list of strings to your encrypted password, one at a time, until amatch is made. This is known as a dictionary attack, and it's one of the many good reasons why your password shouldn't be password or the name of a Star Trek character or even your dog's name. Just because you encrypt Fidoand it becomes gibberish doesn't mean your password is safe from thiskind of attack. Making sure your password is of a certain length (eightor more characters) and contains uppercase letters, numbers, andspecial characters -- like ! and $ -- will go a long way toward makingyour data harder to guess. Even f1D0! is a better password, in the short term, than a longer password like GandalftheGray, which is longer, uses lowercase letters, and is the name of a character from "Lord of the Rings."

A not-so-good way to use crypt()

There's another way to use crypt() that isn't so good: using as the salt the first n characters of the plaintext.



Listing 7. Using characters from plaintext for the salt



<?php

$user = strip_tags(substr($_POST['user'],0,32));

$pw = strip_tags(substr($_POST['password'],0,32));

$cleanpw =crypt($pw, substr($pw,0,2));



$sql = "select user,password from users

where user='". mysql_real_escape_string($user)."'

and password='". mysql_real_escape_string($cleanpw)."'

limit 1';

$result = mysql_query($sql);



if (mysql_num_rows($result)){

//we have a match!

}else{

//no match

}

?>





If your password is Mygr3atpw!, the salt becomes Myand is embedded at the front of the ciphertext, making it that mucheasier for someone to try to guess that password (especially if plentyof passwords in the list are similar to each other or begin with thesame sequence). Not a good idea.

Encryption and decryption with PHP

Most of this article has discussed one-way encryption using crypt(). But what if you want to send a message to someone and provide a way to decrypt the message? Use public-key cryptography, which PHP supports.

Users of public-key encryption have a private key and a public key,and they share their public keys with other users. If you want to senda private note to your friend John Doe, you encrypt that message withJohn Doe's public key (which you've stored on your own keyring). OnceJohn Doe receives the message, only he can decrypt the message usinghis private key. The public key and private key for any given useraren't mathematically related. With PGP and other public-key encryptionmethods, there's no way to deduce someone's private key from the publickey.

An added feature of PGP is that the password for the private key isn't really a password; it's a passphrase. And it can be an entire saying, including punctuation, spaces, and all manner of characters.

One way to use PGP-based public-key encryption is to use GNU PrivacyGuard (GPG). Any messages encrypted using GPG can be decrypted withGPG, PGP, or any number of e-mail client plug-ins that support eitherprogram. In the example, an online form accepts user input, including amessage; encrypts that message for a particular recipient using GPG;then sends it on.



Listing 8. Using GPG



<?php

//set up users

$from = "webforms@example.com";

$to = "you@example.com";



//cut the message down to size, remove HTML tags

$messagebody = strip_tags(substr($_POST['msg'],0,5000));

$message_body = escapeshellarg($messagebody);



$gpg_path = '/usr/local/bin/gpg';

$home_dir = '/htdocs/www';

$user_env = 'web';



$cmd = "echo $message_body | HOME=$home_dir USER=$user_env $gpg_path" .

"--quiet --no-secmem-warning --encrypt --sign --armor " .

"--recipient $to --local-user $from";



$message_body = `$cmd`;



mail($to,'Message from Web Form', $message_body,"From:$from\r\n");



?>





In this example, PHP invokes /usr/local/bin/gpg (this location mayvary on your server) to encrypt a message using the sender's privatekey and the recipient's public key. In effect, only the recipient candecrypt the message and know that the message came from the sender.Furthermore, setting the HOME and USERenvironment variables tells GPG where to look for the keyrings on whichthese keys are stored. The other flags do the following:

  • --quiet and --no-secmem-warning suppress warnings from GPG.
  • --encrypt performs the encryption.
  • --sign adds a signature to verify the sender's identity.
  • --armor produces ASCII output instead of binary so it can be easily sent via e-mail.

Normally, and as mentioned, secret keys are protected by apassphrase. This particular instance doesn't use a passphrase becauseit would need to be manually entered on each form submission. You haveother options, of course, such as providing the passphrase in aseparate file or protecting the form from public use with its ownauthentication scheme (for example, if it's a form that can only beaccessed by sales reps for your company).

Also note that unless you're using SSL on the form that allows theuser to enter the e-mail message, whatever is typed is in cleartext. Inother words: It's visible to anyone between the client machine and theserver. That, however, is another subject.

Summary

I've explained enough about security, encryption techniques, andeven public-key cryptography to help make your next PHP project asuccess. The point of using encryption and other cryptographic methodsisn't to create a 100-percent foolproof, uncrackable system. The onlypositively unhackable system is a computer that's turned off, and eventhat isn't a guarantee because someone might be able to physically walkup to it, turn it on, and hack it. The point of all this work is tomake it so difficult to get at sensitive data that hackers don't eventry, or they move on after a few failed attempts.

All security considerations have to exist on the spectrum betweenconvenience and protection. Requiring everything to beone-way-encrypted using a strong algorithm key means that your data isvery secure, but not convenient to use. The opposite is just as bad --using no encryption whatsoever -- because however convenient that maybe for you, it's also terribly convenient for someone else to get toit, too. Strike a good balance by encrypting important confidentialdata (like passwords, credit card numbers, and secret messages) andadding good security measures like defense in-depth, filteringuser-supplied data, and plain old common sense.

더보기

댓글,