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

While implementing a generic POP3/SMTP Web-Interface (see a German language version at http://webmail.zf2.de) I thought about storing sensible user data (like passwords) over the session. After dropping my first idea to store that data on the server (which is possible but unwanted by our customers), I decided to store those passwords in the Cookie.

As it may be possible (in this case even most probably) that a user will use that interface from a foreign computer (maybe in an internet cafe) I needed to ensure that nobody else could read this information (from the cookie).

The final approach is:

  • Encrypt the password with any Crypt::CBC enabled encryption scheme.
  • Store the encrypted password in the Cookie together with a session id and other values (POP3 server et. al.)
  • Store the encryption key together with the session id on the server.
  • In short: the user get the cipher, we get the key.

I think this procedure is fine to ensure client security and some server security by not storing the password server-side. I know that the script itself knows the password, but I don't consider this too risky.

I would like to have some comments on this approach. Did I miss something? Did I oversize the problem? Please give me some feedback

alex pleiner <alex@zeitform.de>
zeitform Internet Dienste

use Crypt::CBC; use CGI; my $q = new CGI; my $encryption_method = "Blowfish"; # get cookie my ($login, $password, $pophost, $smtphost, $session) = getcookie(); ## do something here such as fill template et. al. # set cookie my $cookie = setcookie($login, $password, $pophost, $smtphost, $sessio +n); print $q->header(-cookie=>$cookie, -expires => "now"); print $template->output; ############################################ sub setcookie { ############################################ my $login = shift; my $password = shift; my $pophost = shift; my $smtphost = shift; my $session = shift; my $key = $ENV{UNIQUE_ID}; my $ciphertext; my $cipher = new Crypt::CBC($key, $encryption_method); $ciphertext = $cipher->encrypt($password); open ID, ">$Conf::tmp_dir/$session" or print_error("write_error"); print ID $key; close ID; my $cookie = $q->cookie( -name => "zf_webmail", -value => { pop3 => $pophost, smtp => $smtphost, login => $login, password => $ciphertext, id => $session, }, -expires => '+10m'); return $cookie; } ########################################### sub getcookie { ########################################### my %cookies = $q->cookie(-name => "zf_webmail"); my $login = $cookies{login}; unless ($login) { # no cookie -> no session # print error } else { my $ciphertext = $cookies{password} or print_error("no_cook_passwo +rd"); my $pophost = $cookies{pop3} or print_error("no_cook_pop3") +; my $smtphost = $cookies{smtp} or print_error("no_cook_smtp") +; my $session = $cookies{id} or print_error("no_cook_sessio +n"); my $password; open ID, "$Conf::tmp_dir/$session" or print_error("read_error"); my $key = <ID>; close ID; my $cipher = new Crypt::CBC($key, $encryption_method); $password = $cipher->decrypt($ciphertext); return ($login, $password, $pophost, $smtphost, $session); } }

Replies are listed 'Best First'.
Re: Encrypted Storage of sensible Data in a Cookie
by davis (Vicar) on Oct 22, 2001 at 13:58 UTC
    You may also want to try a MAC (message authentication code), whereby you generate a one-way hash (MD5 or similar) of the contents of the cookie together with a "secret key", known only to the server.
    When you get the cookie back, you compare the MAC the client hands back with a freshly generated one.
    This is to ensure the client doesn't alter the cookie you hand them. Chapter 6 of "Writing Apache Modules with Perl and C" (O'Reilly) is probably useful.
    It also recommends using an MD5 hash of an MD5 hash of the data, for reasons I can't remember.
      According to the Eagle book, the reason for the double MD5 is that there is a remote possibility that an expoit in the algorithm could be used to break the MD5.

      One should always include a MAC when sending a cookie with any semi-useful or important data. Remember, NEVER TRUST THE CLIENT. :-)

      But then I have to store the password plaintext on the server, right? This is not what I wanted.

      But thanks for the hint, I just started reading that book and will hopefully gain some insights.

      alex pleiner <alex@zeitform.de>
      zeitform Internet Dienste

        Almost. You're not storing the users' passwords on the server, but the "secret key".

        Here's some actual (old) code:
        my $secret_key = "BLAHBLAHBLAH"; my $session_cookie = $query->cookie('SessionID'); umask 0066; if($session_cookie) { my $mac; if(($sessionid, $mac) = split("-", $session_cookie)) { ###Ok, the user has returned a cookie, ###let's make sure it's not been tampered with +. if($mac ne md5_hex($sessionid . md5_hex($sessi +onid.$secret_key))) { destroy_cookie($sessionid, "MODIFIED") +; ###Ack. Nasty people return; } else { ###Other checks. }
        This way you're not storing the password, you're just making sure the user doesn't modify the data. A reasonable golden rule is: "NEVER trust the data the user hands you".