Show Contents Previous Page Next Page Chapter 5 - Maintaining State In this section...
The cookie-based implementation of the hangman game is a lot classier than the first implementation. Not only does it have the advantage of maintaining state across browser sessions, but the game is also somewhat harder to cheat. While the user is actively playing the game, the cookie is kept in memory where it is difficult to read without the benefit of a debugger. However, after the user quits the browsing session, the cookie is written out to disk; determined cheaters could still find and edit the cookie database file if they wanted to make their statistics look better. When you store information on the client side of the connection, peeking and tampering is a general problem. Fortunately, the cure is relatively simple. To prevent tampering, you can use a message authentication check (MAC)--a form of checksum that will detect if the user has altered the information in any way. To prevent peeking, you can encrypt the information using an encryption key that is known to you but not to the user. Show Contents Go to Top Previous Page Next PageLet's add a MAC to the cookie used in the last section's example. There are many ways to compute a checksum, but the most reliable use a class of algorithms known as message digests. A message digest algorithm takes a large amount of data (usually called the "message") and crunches it through a complex series of bit shifts, rotates, and other bitwise operations until it has been reduced to a smallish number known as a hash. The widely used MD5 message digest algorithm produces a 128-bit hash. Because information is lost during the message digest operation, it is a one-way affair: given a hash, you can't reconstruct the original message. Because of the complexity of the digest operation, it is extremely difficult to deliberately create a message that will digest to a particular hash. Changing just one bit anywhere in a message will result in a hash that is utterly unlike the previous one. However, you can confirm that a particular message was likely to have produced a particular hash simply by running the message through the digest algorithm again and comparing the result to the hash. To create a MAC, follow this general recipe:
$MAC = MD5->hexhash($secret . MD5->hexhash(join '', $secret, @fields)); The MAC is now sent to the user along with the other state information.
Example 5-3 shows the changes needed to add a MAC to the cookie-based hangman system. use MD5 (); use constant COOKIE_NAME => 'Hangman3'; use constant SECRET => '0mn1um ex 0vum'; At the top of the script, we add a line to bring in functions from the MD5 package. This module isn't a standard part of Perl, but you can easily obtain it at CPAN. You'll find it easy to compile and install. The only other change we need to make to the top of the script is to add a new constant: the secret key (an obscure Latin phrase with some of the letters replaced with numbers). In this case we hardcode the secret key. You might prefer to read it from a file, caching the information in memory until the file modification date changes. We now define a function named MAC() whose job is to generate a MAC from the state information and, optionally, to compare the new MAC to the MAC already stored in the state information: # Check or generate the MAC authentication information sub MAC { my($state, $action) = @_; return undef unless ref($state); my(@fields) = @{$state}{qw(WORD GUESSES_LEFT GUESSED GAMENO WON TOTAL)}; my $newmac = MD5->hexhash(SECRET . MD5->hexhash(join '', SECRET, @fields)); return $state->{MAC} = $newmac if $action eq 'generate'; return $newmac eq $state->{MAC} if $action eq 'check'; return undef; } MAC() takes two arguments: the We now modify get_state() and save_state() to take advantage of the MAC information: # Retrieve an existing state sub get_state { my %cookie = cookie(COOKIE_NAME); return undef unless %cookie; authentication_error() unless MAC(\%cookie, 'check'); return \%cookie; } get_state() retrieves the cookie as before, but before returning it to the main part of the program, it passes the cookie to MAC() with an action code of # Save the current state sub save_state { my $state = shift; MAC($state, 'generate'); # add MAC to the state return CGI::Cookie->new(-name => COOKIE_NAME, -value => $state, -expires => '+1M'); }
Before save_state() turns the state variable into a cookie, it calls MAC() with an action code of The authentication_error() subroutine is called if the MAC check fails: # Authentication error page sub authentication_error { my $cookie = CGI::Cookie->new(-name => COOKIE_NAME, -expires => '-1d'); print header(-cookie => $cookie), start_html(-title => 'Authentication Error', -bgcolor =>'white'), img({-src => sprintf("%s/h%d.gif",ICONS,TRIES), -align => 'LEFT'}), h1(font({-color => 'red'}, 'Authentication Error')), p('This application was unable to confirm the integrity of the', 'cookie that holds your current score.', 'Please reload the page to start a fresh session.'), p('If the problem persists, contact the webmaster.'); exit 0; } This routine displays a little HTML page advising the user of the problem
(Figure 5-3) and exits. Before it does so, however,
it sends the user a new empty cookie named Figure 5-3. If the cookie fails to verify, the hangman3 script generates this error page. If you are following along with the working demo at www.modperl.com, you might want to try quitting your browser, opening up the cookie database file with a text editor, and making some changes to the cookie (try increasing your number of wins by a few notches). When you try to open the hangman script again, the program should bring you up short. With minor changes, you can easily adapt this technique for use with the hidden field version of the hangman script. There are a number of ways of calculating MACs; some are more suitable than others for particular applications. For a very good review of MAC functions, see Applied Cryptography, by Bruce Schneir (John Wiley & Sons, 1996). In addition, the Cryptobytes newsletter has published several excellent articles on MAC functions. Back issues are available online at http://www.rsa.com/rsalabs/pubs/cryptobytes/. Example 5-3. The Cookie-Based Hangman Game with a Message Authentication Check # file: hangman3.cgi # hangman game using cookies and a MAC to save state use IO::File (); use CGI qw(:standard); use CGI::Cookie (); use MD5 (); use strict; use constant WORDS => '/usr/games/lib/hangman-words'; use constant ICONS => '/icons/hangman'; use constant TRIES => 6; use constant COOKIE_NAME => 'Hangman3'; use constant SECRET => '0mn1um ex 0vum'; . . . everything in the middle remains the same . . .
# Check or generate the MAC authentication information
sub MAC {
my($state, $action) = @_;
return undef unless ref($state);
my(@fields) = @{$state}{qw(WORD GUESSES_LEFT GUESSED GAMENO WON TOTAL)}; my($newmac) = MD5->hexhash(SECRET . MD5->hexhash(join '', SECRET, @fields)); return $newmac eq $state->{MAC} if $action eq 'check'; return $state->{MAC} = $newmac if $action eq 'generate'; undef; } # Retrieve an existing state sub get_state { my %cookie = cookie(COOKIE_NAME); return undef unless %cookie; authentication_error() unless MAC(\%cookie, 'check'); return \%cookie; } # Save the current state sub save_state { my $state = shift; MAC($state, 'generate'); # add MAC to the state return CGI::Cookie->new(-name => COOKIE_NAME, -value => $state, -expires => '+1M'); } # Authentication error page sub authentication_error { my $cookie = CGI::Cookie->new(-name => COOKIE_NAME, -expires => '-1d'); print header(-cookie => $cookie), start_html(-title => 'Authentication Error', -bgcolor =>'#f5deb3'), img({-src => sprintf("%s/h%d.gif", ICONS, TRIES), -align => 'LEFT'}), h1(font({-color => 'red'}, 'Authentication Error')), p('This application was unable to confirm the integrity of the', 'cookie that holds your current score.', 'Please reload the page to start a fresh session.'), p('If the problem persists, contact the webmaster.'); exit 0; }Show Contents Go to Top Previous Page Next Page Copyright © 1999 by O'Reilly & Associates, Inc. |
HIVE: All information for read only. Please respect copyright! |