Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hi All, I have written a help desk script which I am about to release, the script has a login which uses cookies, and a mysql backend for the authentication. At the moment I am encrypting the password in the cookie and the one in the database by using the perl's crypt function, my use shown below:

$password = crypt($INPUT{'password'},'01');

Is this type of encryption suitable for my uses? I have tried to avoid using encryption modules as other systems its used on may not have them installed, and it will enhance portability.

Please advise me of any suggestions/comments, many thanks

Title edit by tye (one-word titles are land mines for simple searches)

Replies are listed 'Best First'.
(ichimunki) Re: crypt
by ichimunki (Priest) on Nov 08, 2001 at 01:35 UTC
    Without encrypting the HTTP session using SSL (i.e. HTTPS), there is no method of storing information in a cookie that is secure since the packets containing the cookie are sent in the clear over the net. I don't even need to decrypt the password to use such a cookie, if I can get my hands on the packets as they pass from the client to the server.

    That said, I'd think that for most many non-commerce uses such a system is sufficient if there is a call to a cookie destructor at some point. Either a short expiration date on the cookie both on the client side and on the server side(so that a hijacked cookie has a short viability) or a "log me out" button, so that it is up to the user (in an apparent and easy way) to clear that cookie from use (and make it so they have to login the next time around-- of course, every login presents a possible target for interception as well).
Re: crypt
by kschwab (Vicar) on Nov 08, 2001 at 02:04 UTC
    Aside from the packet sniffing attack, what you are doing is very vulnerable to a dictionary attack.

    You could mitigate this risk significantly by using a random salt instead of the constant "01".

    Basically, if anyone finds out that the salt is "01", they only have to make one pass to encrypt every word in a dictionary and guess passwords. Randomize the salt, and they have to make a lot more passes.

    One way to do that:

    my @chars=(a..z,A..Z,0..9,'.','/'); my $salt= $chars[rand(@chars)] . $chars[rand(@chars)];
    Update: Another option would be to use Crypt::PasswdMD5, which has the same basic interface as good old crypt, but it supposed to be more resistant to dictionary attacks. (YMMV, I'm not a cryptographer)
      With the understanding that no non-core modules can be used we probably cannot get by with anything but crypt(), so the suggestion to add a random salt is a good one (although don't forget to store it in the database!).

      If going with a non-core module, I'd rather see that the password itself isn't even put in the cookie, maybe the MD5 or SHA1 hash, but even then to what end? We may as well use a semi-random or other mostly unique key in our cookie and prevent an attacker from having any clue how the passwords are stored in the database. That way we limit cracks to sniffing and to bad password selections on the part of users.

      And to prevent cracking, I'd suggest a limit to the number of tries a user gets-- although this brings up a DOS problem, which under the circumstances is likely to be a less invasive "crack" than unauthorized access to the system.

      {Just my additional two cents on this}
        ichimunki++

        That is the right idea. Rather than constantly passing the password back and forth, generate a random session ID when the user has successfully logged in. I suggest your session manager also tie a session to the IP it originally was started from, and that sessions be expired pretty quickly after some inactivity (when the user hasn't sent a request for, say, 30 minutes - though your specific application may make longer idle delays necessary).
      Or if you don't like extra (read: temporary) vars, the canonical:

      my $crypt = crypt( $password, join( '', ('.', '/', 0..9, 'A'..'Z', 'a' +..'z')[rand 64, rand 64] ) );

      --
      Snazzy tagline here
      I'd use

      $enc_password = crypt($plain_password, $plain_password);

      But this method has the problem that abcdefghi will result the same as abcdefghij or any password beginning with the same 8 characters.
      You could use the first and the last character as salt to avoid this problem (the salt is cutted to two characters anyway).

      colli
        I'm not clear on why you would do this. Once the "bad guy" figures out what you've done:
        open(DICT,"/usr/dict/words"); while(<DICT>) { chomp; my $guess=crypt($_,$_); # insert some LWP code here to attack # the web page if ($it_worked) { print "$user: crypted password is $guess\n"; } } close DICT;
        On the other hand, if you randomize the salt, the loop above becomes an inner loop. Then the "bad guy" has to add an outer loop that runs up to 64*64 times.
Re: Is this use of crypt() appropriate?
by Nomis52 (Friar) on Nov 08, 2001 at 18:16 UTC
    I'm doing a similar thing but using a session id.
    On sucessful login a session id is created using the following:
    User name
    HTTP User Agent
    IP address <- can change paticularly with aol users and proxy servers
    Day-of-the-year
    and a "secret" constant string

    This is fed to MD5 which computes the checksum of it and stores it in a cookie along with the users name.

    Everytime a script is requested the session id is checked by re-creating the session id and comparing it to the one in the cookie.

    For someone to fake a session id they need all of the above information including the "secret" string and what order i joined them together.

    The logout is simple, just delete the session id from the cookie.

    More secure IMOHO than sending any form of the password over the net to store in a cookie. (Remembering it was sent once when the user logged on but for that you should use ssl).

    I found this site very usuful when putting this togeather. Good luck
    Nomis52

      Thanks Nomis, that is very helpfull.. would you be able to post some example code of the session in action? It would be very appriciated, many thanks
        Ok this is how I did it. Note I'm very new to perl programming so this probably isn't the best way.
        Assuming you have authenticated the user (from a database or text file or where-ever), and $user is the user's id
        use MD5 ; my $md5 = new MD5 ; $md5->reset ; my $yday = (localtime)[7]; # create certificate / session id my $certif = $user . $yday . "do4k.g0" . $ENV{'HTTP_USER_AGENT'} . +$ENV{'REMOTE_ADDR'} ; # encrypt certificate $md5->add($certif); my $enc_cert = $md5->hexdigest() ; # set cookie print "Set-Cookie: SESSION=$enc_cert; path=/\n" ; print "Set-Cookie: NAME=$user; path=/\n" ; # and continue print "Content-type: text/html\n\n" ; print "Your logged In!" ;
        Then everytime the script is called get the certificate out the cookie and recreate a certificate and compare the two.
        # $session and $user came from cookie use MD5 ; my $md5 = new MD5 ; $md5->reset ; #create ceritficate my $yday = (localtime)[7]; my $certif = $username . $yday . do4k.g0 . $ENV{'HTTP_USER_AGENT'} . + $ENV{'REMOTE_ADDR'} ; # encrypt Certificate $md5->add($certif); my $enc_cert = $md5->hexdigest() ; #compare if($enc_cert eq $session) { # we're logged in - run script ; } else { # we're not logged in - disp error msg }
        And a logout can simply be done with a
        print<<"END" ; Set-Cookie: SESSION=; path=\ Set-Cookie: NAME=; path=\ Content-type: text/html Your logged out now END
        It would probably be wise to set expiration times for the cookies. Using the $yday means each certificate will expire at midnight which could be a problem.

        Anyway I hope this helps
        Nomis52

Re: Is this use of crypt() appropriate?
by Anonymous Monk on Nov 08, 2001 at 15:35 UTC
    Another post by me, I keep re-reading the posts above and thinking of new ways to approach this. I'll explain how I am doing my login management at the moment: When someone signs up their password is encrypted in a MySQL database, when they login their password is encrypted again and stored in a cookie, each request checks that the password in the cookie matches that in the database.

    The theory above using the below code is the one that appeals to me the most
    my @chars=(a..z,A..Z,0..9,'.','/'); my $salt= $chars[rand(@chars)] . $chars[rand(@chars)];

    The only problem with this is that it will encrypt the password randomly, so I would need to decrypt it in other areas of the script and store the mysql password in plain text, rather than also encrypted.

    My understanding of crypt() is fairly limited and I have learnt a fair ammount from the posts above, I would greatly appreciate it if anyone could provide some advice based on what I have posted here..
    Thanks very much
Re: Is this use of crypt() appropriate?
by Anonymous Monk on Nov 08, 2001 at 14:20 UTC
    Thanks all, the idea of using sessions does sound more appropriate, however, I am unsure how to approach this. Any suggestion on how i would go about ahieving this?
      Sorry to post twice, re-reading all the above posts, the use of a random salt does sound easier to implement..how would I use a 'salt' when encryptin a password ? simply by replacing '01' with '$salt' ?
      Thanks
      Creating a session ID is simple, and probably better than worrying about how to encrypt a password and decrypt it reliably. If you have the MD5 module available, why not just hash the password against the time (or using crypt you could salt the password or the userid with the last two digits of time() or a random key and send this back in the cookie-- you then store this session ID in a field in the DB and do a simple compare each time. For storing the session ID you could go the extra step of using the MySQL crypt() function to keep people with DB access from easily reading it there.

      As I said before, if I can intercept the transmission of the cookie I can spoof all day long unless you also include an IP check (and I might be able to spoof IP too, just not as easily) or auto-expire.

      As was pointed out, the most vulnerable area is going to the dictionary crack, since it relies on the strength of the user passwords themselves (and users are notorious for choosing poor passwords). All that is required to execute a dictionary crack is a script that emulates the input from your login form and runs through the dictionary trying sensible combinations of words, etc etc. So I wouldn't worry too much about what's in your cookies except that it not easily reveal the password itself and that it not be easily predicted.
      There is an alternative to random session keys which also avoids cookies (for those who don't like cookies):

      Have the user log in once with username and password and generate a long random string to use as the session key. Record the user, ip, current time, and session key in a database of active sessions.

      All URL requests for web pages, scripts, must be preceeded by the session key:

      http://yoursite.com/get.cgi?bdjaiwmcvndjqidm+the_page.html

      Everytime a page is requested, you check the session key, ip, and the current time against those in the database of active sessions. If the match fails, or the current time is longer than the lifespan you have chosen for the key, you request that the user log in.

      Using this approach, the password is only transmitted once, at the start of each session, and the session key is of limited use if it is intercepted because it has a short lifespan and must match with the ip.

      Note: The user never has to type in the key themselves, because you serve them up pages with the key already included in the links. This is not a major security problem for the reasons stated above.

      Joe.

Re: Is this use of crypt() appropriate?
by ehdonhon (Curate) on Nov 08, 2001 at 22:25 UTC

    It looks like other people have already suggested alternatives to crypt, but if you are set on using crypt, here's two suggestions

    1. You should always send the user's entire encrypted password as the salt, not just the first two characters:
    $password = crypt($INPUT{'password'},$encrypted_password);

    2. If you are storing the passwords in mysql, there might be an easier, non-perl solution to authentication for you using the mysql builtin function 'PASSWORD':

    my $user = $dbh->quote( $INPUT{'user'} ); my $input_pass = $dbh->quote( $INPUT{'password'} ); my $sth = $dbh->prepare ( "SELECT pass as encrypted_password, PASSWORD( $input_pass , pass ) as input_password WHERE user like $user FROM passwd_table " );

Re: Is this use of crypt() appropriate?
by BMaximus (Chaplain) on Nov 09, 2001 at 01:35 UTC
    ichimunki++ from me too. However you can secure a cookie without SSL. See my module that I use for mod_perl to encrypt the contents of a cookie, Apache::Cookie::Encrypted. Very easily done with Crypt::Blowfish and it even has a pure Perl implementation.

    I'm most cases. If a user doesn't have the module and has SSH access to their account they usualy can install the module into their workspace. If they don't have access, there usualy is a way to finagle it in if its a pure Perl module. It doesn't hurt to ask the Admin if they would install any modules needed. If they don't want to install it and they cite a good reason for it, fine. If the reason is bad it usualy is a sign of a lazy admin, you're better off taking your business elsewhere.

    The others have more than explained the proper use of Crypt.

    BMaximus
      Cool module. But I still think it leaves the cookie vulnerable to sniffing, which is all that is needed. If I can replicate your cookie, encrypted or not, I can pass it to the server as if I were you and more likely than not the server will believe everything is fine. That's the reason we have to encrypt the transmission itself and not merely the contents of the cookie. That way an attacker has almost no chance to guess which parts of the transmission are the cookie and re-use them.
        Good point. But I doubt that a person who is sniffing on the net would get the whole thing. It would take a person being on the same LAN to get the whole cookie with a sniffer. As I was thinking that a way to combat this would be to add the IP address of the computer the cookie is being sent to into the encrypted contents. However something like that would cause a problem with anyone who is using a proxy (like AOL). If I were doing E-Commerce I would most definatly use SSL. Any way of securing a cookie without SSL? Taking an MD5 of the cookie won't do it since the cookie is not changed. Where does being carefull cross over to being overly paranoid?

        BMaximus
Re: Is this use of crypt() appropriate?
by DrManhattan (Chaplain) on Nov 21, 2001 at 18:10 UTC

    I issue session keys to my users with a symmetrical encryption algorithm rather than a one-way hash. I concatenate the user's username, ip address, a timestamp, a lifetime, and a random number together, then encrypt them with Crypt::CBC and send them out in the form of a cookie. The server validates the cookie by decrypting it, verifying the connecting client's ip address, and ensuring that the timestamp + lifetime is greater than the current time.

    Storing the ip address + timestamp/lifetime in the cookie mitigates the risk of replay attacks (i.e. someone else attempting to access the site with an identical cookie). Storing the random number makes a known plaintext attack on the encryption key much more difficult.

    -Matt

Other methods
by Stevie_G (Initiate) on Nov 08, 2001 at 22:23 UTC
    From reading what you're looking at you may want to consider using either a mod-perl enabled server for authorization (possibly using non-mod_perl servers for other tasks that only require a valid user). If mod_perl is an option, I would suggest Apache::AuthCookieDBI.

    Failing to have a mod_perl server, I would recommend that you use a similar system to the Apache::AuthCookieDBI module. Have a string of username, ip address, expiration time (this needs to be short in order to avoid replay attacks), and possibly a session id. MD5 these with a 'secret key' that is constant across all sites and you've got a better system. If you want to be even trickier, have the expiration time in the session and in the cookie, and check to see they're the same.

    Don't put the password anywhere near the browser if possible! See Apache::Session for a non-apache based session system.