Home Articles FAQs XREF Games Software Instant Books BBS About FOLDOC RFCs Feedback Sitemap
irt.Org

Related items

CGI Security : Better Safe than Sorry

Creating a Page Counter In Perl

Speed Thrills : CGI Please ... and Fast!

CGI Programming Made (Relatively) Easy Using Libraries

Server-Side Includes and its Extensions

Timestamping an HTML Document

Deleting Files in Perl

Creating a mailing list using Perl

Reading and Writing to Files on the Server

Server Side Includes and CGI Security

Random and Recursive Crypting using Salt on Unix and Win32

You are here: irt.org | Articles | CGI & Perl | Random and Recursive Crypting using Salt on Unix and Win32 [ previous next ]

Published on: Monday 31st May 1999 By: Ha Quach

What for Encryption?

You don't need to know anything about encryption to use some of the scrambling algorithms out there to generate messy passwords. And when do you need messy passwords? When you want to protect your content, be it a piece of email attachment, a Web directory, or just stuff you pass out to your special friends. OK, since this article is for the Perl/CGI section, I assume you're reading this because you want to know how Perl's built-in crypt() function can help you protect Web pages or otherwise authenticate users before letting them go after your goodies. Ever seen this?

Well, you can do the same for your own site. Some time soon in another article, I'll talk about protected directories under the Apache Web server. But let's not get ahead of ourselves. Before you jump into Apache, you need to know how to generate the encrypted passwords in the first place (sorry, Apache doesn't do it *automagically* for you). And I'll show you just that if you're running a UNIX system or a Win32 system so that no matter what port of Perl or Apache you have, by the time I start talking about Apache (please remind me to write that article), you'll be wearing the white hat and ready to ride the bronco.

Perl's Built-in crypt() Function

Perl's crypt() works similar to UNIX crypt(). To answer you question: No, there is no decrypt() function, although if you were trying to break it by brute force, you shouldn't be reading this article anyway. To answer your next question: Since crypt() is a one-way function, you create encrypted passwords (or any string) AND check that string against a user's plaintext password by running both strings through your crypting routine, which you'll roll your own. If the user ever loses his/her password, run a new password through your crypting routine and voila!, a new mess of mumbled text. You, the Web guru, will never know the user's password. Isn't that what security is all about? Now, on with the rolling!

Perl's crypt() takes two arguments, the unencrypted string and the salt. The final encrypted string is the first two characters of the salt, which is the key for the system to figure out the rest of the mumble jumble after the salt. That's all you need to know about the function. So, if you have a salt like 'salt', the substring 'sa' is the first two characters. Here's our username and salt in the literal:

	# prints sa./eocij6UyY
	print crypt('username','salt');

And here's something slightly more real-world:

   # prints toFP3Ud0a1vGA
   print crypt('john','tomorrow');

Random Salts for UNIX

Of course, the whole idea behind the salt is to make it random enough so that two users named 'john' would not result in the same encrypted string Same salt + same string = same encrypted string. Since you can't make the password random, your best bet for a random encrypted string is a random salt. Also, you want a random salt for better security so that not even you, the maintainer, can duplicate the key off the top of your head.

This routine gets us a random salt and, by effect, a more random encrypted string:

   sub  encrypt_password
   {
      my $unencrypted_string = shift @_;
      my @salt_chars         = ('a'..'z','A'..'Z','0'..'9');
      my $salt               = $salt_chars[rand(63)] . $salt_chars[rand(63)];
      return crypt($unencrypted_string, $salt);
   }

Here's how you talk to the &encrypt_password() routine:

   my $encrypted_password = &encrypt_password($unencrypted_password);

Let's look at each line of the routine.

   my $unencrypted_string = shift @_;

takes the $unecrypted_password you passed into the routine and assigns it to $unencrypted_string.

   my @salt_chars = ('a'..'z','A'..'Z',0..9);

is an array of characters from lowercase a through z, uppercase A through Z, and the integers zero through nine. The array has 62 members, from which we will pick two members to generate the first and second characters of our salt. Of course, you can find more creative ways to generate your array, such as using HTML colors in hexadecimal, dates, the moves of a classic Fischer-Spasky chess game, the notes to your favorite song from your favorite band, anything that can be translated into characters.

The next line

   my $salt = $salt_chars[rand(63)] . $salt_chars[rand(63)];

uses Perl's rand() function to get a random fractional number (something with a decimal in it). The behavior of rand() is in your Perlfunc documentation. Even though the index of an array is an integer and your rand() returns a fraction, Perl is smart enough to automatically--er, invisibly--treat your fraction as an integer. This makes sense because C treats a float like an integer if you're trying to pass it into an integer context, but I digress.

   my @stuff = qw(cat dog moose mouse goose);
   print $stuff[3.1415927];   # prints mouse

works as if it were

   print $stuff[int(3.1415927)]; # same as $stuff[3];

If you don't know the number of members in your array, use:

   my $salt = $salt_chars[rand(scalar @salt_chars)] . $salt_chars[rand(scalar @salt_chars)];

The whole idea is to make sure that our index number is inclusively within the total number of members in our array, so you want to control the return value of rand() a bit.

Finally, the &encrypt_string routine returns what you want it to with the line

   return crypt($unencrypted_string, $salt);

which takes for granted that Perl will evaluate crypt() before doing the return.

Recursive crypt() for Win32

On a Win32 system, you can't use a random salt. You'll never be able to repeat your &encrypt_string process to authenticate a user because Win32 doesn't know what to do with a salt (it kinda loses it somewhere that we don't know about). A workaround is to recursively encrypt a static salt and use that as the new salt for a modified version of your &encrypt_string routine. This is slightly better obfiscation than the static salt string itself, say 'tomorrow', but it'll have to do.

   sub encrypt_string_on_Win32
   {
      my $unencrypted_string  = shift @_;
      my $unencrypted_salt    = 'tomorrow';
      my $first_crypt         = crypt($unencrypted_string,$unencrypted_salt);
      my $encrypted_salt      = substr($first_crypt,3,1).substr($first_crypt,7,1);
      my $encrypted_string    = crypt($unencrypted_string,$encrypted_salt);
      return crypt($encrypted_string,$encrypted_salt);
   }

Here, we take the static $unencyrypted_salt 'tomorrow' and apply crypt() to it to get our first-time-around $first_crypt. Then we take the third and seventh characters of $first_crypt as our new salt called $encrypted_salt and concatenate the two with a dot. Choosing the third and seventh characters is rather arbitrary. On the second application of crypt(), we used the $encrypted_salt to get the $encrypted_string and return the whole thing with a third application of crypt().

That's just one way of obfiscating things. You could get really extreme and call &encrypt_string_on_Win32 many, many times like so:

   my $password = 'john';
   for(1..13) {
      $password = &encrypt_string_on_Win32($password);
   }
   print "3 times inside and 13 times outside yields $password\n";

Of course, you get the same result every time, which is not as powerful as a random salt on UNIX, but you can have a lot of fun with this. Play around with different ways to generate an array of salts, or to derive a salt from wacky sources such as the longitute and latitude of the place where you were born, or how much recursion you want or even the number of recursions as a derivative of something crypted and cryptic.

Final Caution for WIN32 Users

Remember, your encrypted string is only as secure as the algorithm you use when you're playing with static data. If someone figures out "Oh, yes, I see that the person is using the last prime factor of the double-integral of the first bridge in his sister's favorite Beatles tune translated to an atonal scale," then there's nothing you can do. The chances of someone figuring that out and reverse-engineering your algorithm is much smaller than their being able to get to the source code by some other hackery.

Demo: &encrypt_password and &encrypt_password_Win32

The source code for the working example is available to download as a zip file.

Please send suggestions and corrections to Ha Quach. Next Article: Password-Protected Directories for Apache

Related items

CGI Security : Better Safe than Sorry

Creating a Page Counter In Perl

Speed Thrills : CGI Please ... and Fast!

CGI Programming Made (Relatively) Easy Using Libraries

Server-Side Includes and its Extensions

Timestamping an HTML Document

Deleting Files in Perl

Creating a mailing list using Perl

Reading and Writing to Files on the Server

Server Side Includes and CGI Security

Feedback on 'Random and Recursive Crypting using Salt on Unix and Win32'

©2018 Martin Webb