Java Platform, Enterprise Edition

Java EE Journal

Subscribe to Java EE Journal: eMailAlertsEmail Alerts newslettersWeekly Newsletters
Get Java EE Journal: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn


J2EE Journal Authors: Stackify Blog, Sumith Kumar Puri, Javier Paniza, Yakov Fain, Ken Fogel

Related Topics: Apache Web Server Journal, PHP Developer's Journal

Apache Web Server: Article

Two stupid PHP tricks

Seemingly insignificant, but important, details to help you run PHP successfully

(LinuxWorld) -- In the face of a slowing economy, it is increasingly difficult to justify spending any time working on my non-profit site VarLinux.org. I posted a notice stating there will be no further progress. Yet even after making it "official," I can't seem to stop myself from working on the site. Perhaps I have some secret hope it will blossom into a successful project that makes money instead of sucking the food out of my children's mouths. Or perhaps I find it therapeutic in these trying times.

Regardless, working on this site continues to be educational, particularly in when it comes to learning from my mistakes. (Whenever I think of that expression, I am reminded of the Peanuts strip where Charlie Brown says something along the lines of, "If it is true that you really learn from your mistakes, that makes me the smartest person in the world.")

I chronicle much of what I experience as it happens on VarLinux.org. But for the sake of my LinuxWorld readers and VarLinux.org readers alike, and to dispel any notion that I'm a master programmer, I will condense some of the most important lessons here. I've got reams of lessons I've learned regarding MySQL, Apache, and other pieces of the site, but I'll start with some of the places where I stumbled (or had to deal with the undesirable coding practices of others) while using PHP.

PHP lesson number 1

First, if you program in PHP, always use echo "text, variables, and html goes here"; instead of interspersing HTML and other displayed text with PHP commands. In other words, every ".php" file should begin with "<?php" and end with "?>" and never break away from PHP programming mode anywhere in-between.

This is the opposite of the way people tend to get started with PHP. They take their HTML files and insert some PHP commands here and there as necessary, breaking away from the HTML when they need the functionality PHP provides.

Even though that seems like a sensible way to get started, take my advice, even if you want to create a project based on existing HTML files. Wrap your existing HTML in echo statements, even though this means you'll have the tedious job of making sure all your double-quotes are preceded by a backslash. In other words, this...

<A HREF="http://wherever.domain">link text</A>

converts to...

echo "<A HREF=\"http://wherever.domain\">link text</A>";

The same goes for every other instance where quotes exist in your HTML. And remember that if you want your HTML to conform to the w3c standard, there are many places where you need to use quotes even if you can get away without using them. Most browsers aren't picky about such things these days, so it is tempting to leave out quotes where the HTML specification says you should use them. I'm a standards kinda guy, so I say use them just because you are supposed to use them.

I wouldn't be surprised if I hear from PHP programmers who tell me the above practice will have a negative affect on performance. It may, it may not. I have no idea, because I don't know how much overhead (if any) you are adding to your site when you make the PHP module parse echo statements rather than pass plain HTML through to Apache. But after having modified the code for VarLinux.org for the past couple of months, I can tell you it is a whole lot easier to maintain a site when you assume everything on the page is PHP. Use echo to send HTML to the browser.

Just one more note to satisfy the PHP gurus who are no doubt breaking out in a rash due to my focus on the echo command alone. It's true there's more to life in PHP than echo. For example, as you learn PHP, you'll find you don't need to use echo for every line of HTML. You can accumulate multiple lines, assign them to variables, use printf formatting, and then echo that output, for example. And you can use things like output buffers to play tricks and make your site more efficient (see the command ob_start() in the PHP documentation for an introduction to this technique).

My point is your should stay in PHP from start to finish if you want to make it easier to maintain your site down the road.

Stupid PHP trick number 1

In addition, make sure there are no blank lines above or below these two PHP opening and closing tag markers, "<?php" and "?>", respectively. If you follow this rule as well as the one about never breaking away from PHP, your server will never send anything to a visiting browser unless you have instructed it to.

This is extremely important if you ever expect to use cookies or redirect a visiting browser to a new location. If the PHP compiler/interpreter sees a blank line in a file outside of any PHP tags, it assumes it should send the blank line(s) to Apache immediately.

Once this occurs, Apache assumes it has sent the header for your page and you can no longer use the functions that manage cookies or redirect browsers to new locations. You can check for this condition with the PHP headers_sent() command. If it is true, then it's too late to set a cookie or redirect the browser with the PHP Header("Location: newlocation") command.

The easiest way to trip up on this particular rule is to include another file that has blank lines. For example, your hello.php file may read something like the following:

<?php
include("configvars.php");

function dosomething() { setcookie("thiscookie","thisinfo",time() - 3600); echo "Hello"; }

dosomething();

?>

If configvars.php has any blank lines outside of PHP tags, then you have unwittingly created the condition where headers_sent() is true, even if your hello.php file has no blank lines outside of the PHP tags and hasn't used any functions or variables from the included file. The setcookie() command in the function dosomething will fail simply because there was a blank line outside of PHP tags in the included file.

Stupid PHP trick number 2

I can't take the blame for how this stupid trick affected VarLinux.org, since the problem emerged from the way PHP-Nuke 4.4.1a was coded (I haven't checked to see if later versions of PHP-Nuke correct this). But the lesson I learned is simple. Always specify both arguments for the crypt() function. The first argument is the string you want to encrypt, and the second argument is the salt, which is something like the "key" to the encryption scheme.

The crypt() function is a one-way encryption technique that is useful for storing user passwords. You can't decrypt anything you encrypted using crypt(). (You might be asking yourself how such a function could possibly be useful, but we'll get to that in a moment.)

PHP provides other encryption functions that allow you decrypt what you encrypted. But I like using crypt() for storing VarLinux.org passwords because it provides my users with more privacy. If you use this function, not even the administrator can decrypt a user's password. If someone uses an embarrassing string of text, I can't find out what it is.

More important, it is difficult to figure out any VarLinux.org user's password even if a bad guy gains direct root access to the VarLinux.org server. If a VarLinux.org user's password includes any information that might help a cracker break into that user's own system, this technique makes it almost impossible for anyone to exploit information in the VarLinux.org database for such a purpose, even if they gain access to these passwords.

The crypt() function takes one or two arguments and returns the encrypted string. The first argument is the text that you want to encrypt. If you do not provide a second argument, crypt() will choose the default encryption algorithm from the following list (according to the PHP documentation):

  • CRYPT_STD_DES - Standard DES encryption with a 2-char SALT
  • CRYPT_EXT_DES - Extended DES encryption with a 9-char SALT
  • CRYPT_MD5 - MD5 encryption with a 12-char SALT starting with $1$
  • CRYPT_BLOWFISH - Extended DES encryption with a 16-char SALT starting with $2$

If you provide a second argument, crypt() will use the encryption scheme that matches the type of salt you provided as the second argument.

As I said earlier, the salt is like a key to the encryption algorithm. It is what the encryption algorithm uses to encrypt your text. If you use the crypt() function with only a single argument, it creates its own salt automatically, according to the default algorithm it has selected. Then it outputs the encrypted string. The first portion of that encrypted string is the salt that it used. This is what makes this function useful for storing passwords.

Here's how it works. (This example is entirely bogus and is meant only to illustrate the way the function is used in practice). Suppose you enter "password" as your password. VarLinux.org encrypts "password" with the crypt("password") function, which returns the string "2j38sn3km4", which is what VarLinux.org stores in its database for your password. We didn't specify any salt, but since we know the default encryption technique is standard DES, we know the first two characters of the encrypted output string comprise the salt for the string, because that's how standard DES works.

Assuming the first two characters of this string, "2j", comprise the salt, we now have a way to validate you as a user without having to know your actual password. The next time you log in, you will type the word "password". The VarLinux.org PHP code will then use your login name to find your encrypted password string, "2j38sn3km4". It extracts the first two characters, and attempts to re-encrypt your password with the following function: crypt("password","2j").

Notice this time we specify the salt as the second argument. Since you are using the same password, "password", and since the crypt() function is using the same salt it automatically created when it encrypted your password in order to store it in the database, it should create the same encrypted string again now. The two encrypted strings match, and you're validated.

The problem

Suddenly VarLinux.org users complained they couldn't log in again after changing their password. As a shot in the dark, I looked at the encrypted password strings in the database, and noticed some were much longer than others.

At some point while updating software on my server, PHP stopped using standard DES as the default for the crypt() function and started using MD5 encryption instead.

The mismatch arose from PHP-Nuke 4.4.1a specifing the first two characters of your encrypted password as the salt when you log in. In other words, it forces crypt() to use standard DES when it validates you at login. But PHP-Nuke 4.4.1a leaves out the salt as the second parameter when it creates your first password, or when it allows you to change your password.

As long as PHP defaulted to standard DES, there was no problem. But when PHP started using MD5 as a default, it started storing MD5-encrypted passwords in the database whenever someone would create a new password or change an existing one. The problem is the code continued to try to validate these encrypted strings as standard DES when you tried to log in.

If you take my advice above and always specify the second argument for crypt() whenever you use it, this mismatch will never occur. That's how I fixed the problem. Whenever anyone creates or changes a password, my code now calls a routine that generates a random two-character salt. It specifies this salt as the second parameter of the crypt() function, which forces crypt() to use standard DES at all times. No mismatch. No more failed login attempts.

Here is the makesalt() function, which I simply lifted from the comments section of the online PHP manual. There are better salt generators than this, but if you are truly concerned about security I recommend you base your site on MD5 or better instead of standard DES.

function makesalt() {
  srand((double)microtime()*1000000);
  $saltseeds =
  array('.','/','0','1','2','3','4','5','6','7','8','9',
   'a','b','c','d','e','f','g','h','i','j','k','l','m',
   'n','o','p','q','r','s','t','u','v','w','x','y','z',
   'A','B','C','D','E','F','G','H','I','J','K','L','M',
   'N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
  return $saltseeds[rand(0,63)] . $saltseeds[rand(0,63)];
}

More Stories By Nicholas Petreley

Nicholas Petreley is a computer consultant and author in Asheville, NC.

Comments (1) View Comments

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


Most Recent Comments
TheBrain 05/05/04 04:22:47 AM EDT

One thing to take note of, If performance is a issue then you are better mixing your HTML in with your PHP, as apposed to echoing it. Another performance booster might be to collect all data to be outputted into a variable, and then output it at the end (you can use 'ob_start')