http://www.perlmonks.org?node_id=200646


in reply to Cookies without CGI.pm

Incidentally you just said a Very Bad Thing. Normally your user passes in the authentication information and then you return a token - usually a hash of a server-side secret plus a session id. You then send over in the cookie both the hash and the session id. This provides for both uniqueness and security.

The essentials are:

server side secret: I generate a 2048 bit binary value and store it on the server. This is only used for input into the hash function. This must be kept secret (or regenerated) since this is your protection against forged session IDs. Keep in mind that you shouldn't just regenerate this willy nilly since you don't want to deplete your operating system's entropy pool. Normally I just create a secret.pl file which writes out another file Secret.pm with some binary value stored in a string sort of like  use constant SECRET => "\001sd3!@\007";.

open RANDOM, RANDOM_DEVICE or die "Cannot open " . RANDOM_DEVICE . ": +$!"; $rc = read RANDOM, $secret, $bytes; close RANDOM or die "Cannot close " . RANDOM_DEVICE . ": $!"; die "Nothing was read!" if 0 == length $rc; die "Mismatched read: $bytes vs " . length($rc) . "!" if $bytes != len +gth $rc; # quote the binary value for inclusion in a double-quote string $secret =~ s/[\x00-\xff]/sprintf '\\%o', ord $&/gex;

unique session id: Normally this just takes the form of an incrementing number ala 1 -> infinity (or wherever the number rolls over). This *must* be unique or you run the risk of colliding session keys. The best way of guaranteeing uniqueness is to just use a counter. Normally I just let my database handle this as a sequence. This is easy to attack so you augment it with a cryptographic hash.

my $sessionid = $dbh->selectrow_array ( "SELECT nextval('UserSessionSeq')" );

cryptographic hashing algorithm: Use MD5 or SHA1 (slower but more secure) to combine the sequence with the secret. Make sure you separate your two tokens to prevent them from running together. You can get collisions if you let them touch so just don't do that. I just separated the leading number from everything else by a single space. Easy.

my $sessiondigest = md5_hex(sprintf("%u %s", $sessionid, Voter->SECRET +)

The last trick is now you store both the session id and the session digest in the cookie you send to the client. Everytime the web browser requests something you have to check that the session id and session key match in your session records. You don't have to do anything complicated here - just a simple equality test will do.

$dbh->selectrow_array ( "SELECT UserID, Activeuser, Created, Modified" . " FROM ValidSession" . " WHERE SessionID = ?" . " AND SessionDigest = ?", undef, $sessionid, $sessiondigest ); # if a row was returned then it was a good match otherwise something + is wrong (the session might have just expired as well - views are us +eful for that)

There's an example of all this up at Voter @ greentechnologist.org.