Encrypted session handler

[UPDATE]  Enrico Zimuel has a better version of this[/UPDATE]

A little while ago I had come upon the problem of having to store sensitive data in a user session.  The solution that I (and several others came upon) was creating a mechanism for storing encrypted data in a session.  But what we wanted to do was build something that didn’t have a single point of failure.  We also wanted to build something portable.  What we built was a simple Zend Framework session handler for storing sensitive data.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
class EncryptedSession extends Zend_Session_Namespace {
 
    const CIPHER = MCRYPT_3DES;
    const MODE = MCRYPT_MODE_CBC;
 
    private $_iv;
 
    public function __construct($namespace = 'Default', $singleInstance = false)
    {
        parent::__construct($namespace, $singleInstance); // Must be true because of iv
 
        $storeKey = __CLASS__ . '_Data' . '_' . $namespace;
 
        if (!isset($_COOKIE[$storeKey]) || !isset($this->secretKey)) {
                $this->unsetAll();
 
                $maxKeySize = mcrypt_get_key_size(self::CIPHER, self::MODE);
 
                $secretKey = '';
                while( strlen($secretKey)<$maxKeySize) {
                    $secretKey .= dechex(uniqid(mt_rand(), true);
                }
                $this->secretKey = substr($secretKey, 0, $maxKeySize);
 
                $iv_size = mcrypt_get_iv_size(self::CIPHER, self::MODE);
 
                $this->_iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
 
            $hmac = hash_hmac('md5', $this->_iv, $this->secretKey);
            $unique = base64_encode($this->_iv);
            // The cookie has the same parameters as the session cookie
            $cookie_param = session_get_cookie_params();
            setcookie(
                $storeKey,
                $hmac.$unique,
                $cookie_param['lifetime'],
                $cookie_param['path'],
                $cookie_param['domain'],
                   $cookie_param['secure'],
                   $cookie_param['httponly']
            );
        } else {
            $hmac = substr($_COOKIE[$storeKey],0,32);
            $unique = base64_decode(substr($_COOKIE[$storeKey],32));
            $check = hash_hmac('md5',$unique,$this->secretKey);
            if($hmac !== $check) {
                 throw new Zend_Session_Exception('Invalid Session Data');
            }
            $this->_iv = $unique;
 
        }
    }
 
    public function setEncrypted($key, $value)
    {
        $this->$key = bin2hex(
            mcrypt_encrypt(
                self::CIPHER,
                $this->secretKey,
                $value,
                self::MODE,
                $this->_iv
            )
        );
    }
 
    public function getEncrypted($key)
    {
        if (isset($this->$key)) {
            $decrypt = mcrypt_decrypt(
                self::CIPHER,
                $this->secretKey,
                pack(
                    'H*',
                    $this->$key
                ),
                self::MODE,
                $this->_iv
            );
            return rtrim($decrypt, "�"); // remove null characters off of the end
        }
        return null;
    }
}

 

 

What this does is allow you to transparently store encrypted data in a session.  Because it’s encrypted, someone hacking in to the server, say via an include vulnerability, would be able to read the session data, but not decrypt it.  That’s because the initialization vector is stored in a cookie on the browser.  So in order to decrypt the session an attacker would need to do both an XSS and a remote code injection attack.

15 Thoughts on “Encrypted session handler

  1. Josh Johnston on November 17, 2010 at 3:47 pm said:

    You do realize that you are storing everything needed to decrypt in SESSION as well right? cookie is just the hex version of the iv binary. Shouldn’t you only be storing the iv on the client?

    Cookie:
    string ’29a498dd9057b334′ (length=16)

    Session:
    array
    ‘secretKey’ = string ‘325951551157010490207714’ (length=24)
    ‘iv’ = string ‘)��ݐW�4′ (length=8)

  2. I also pasted the wrong version of the class. I’ve got a bunch of debug stuff in there. Looking for my “good” version…

  3. Josh Johnston on November 17, 2010 at 3:56 pm said:

    It is a really good idea though! I am going to hack your good version so that encryption is transparent via __get and __set. I’ll post it somewhere when done.

  4. I found my actual version. I’ll have it up for you in a few minutes.

  5. ok, check now (Thanks for the catch, BTW).

  6. Josh Johnston on November 17, 2010 at 4:21 pm said:

    Here, now it should work just like the normal session class; encrypting and decrypting transparently while maintaining backwards compatibility

    [code]
    private function _encrypt($value)
    {
    return bin2hex(
    mcrypt_encrypt(
    self::CIPHER,
    $this-secretKey,
    $value,
    self::MODE,
    $this-_iv
    )
    );
    }

    private function _decrypt($key)
    {
    if ($var) {
    $decrypt = mcrypt_decrypt(
    self::CIPHER,
    $this-secretKey,
    pack(
    ‘H*’,
    $var
    ),
    self::MODE,
    $this-_iv
    );
    $var = rtrim($decrypt, “”); // remove null characters off of the end
    }

    return $var;
    }

    public function setEncrypted($key, $value)
    {
    $this-$key = $this-_encrypt($value);
    }

    public function getEncrypted($key)
    {
    if (isset($this-$key)) {
    return $this-_decrypt($this-$key);
    }

    return null;
    }

    public function __set($key, $value)
    {
    parent::__set($key, $this-_encrypt($value));
    }

    public function & __get($key)
    {
    return $this-_decrypt(parent::__get($key));
    }
    [/code]

  7. The server knows the ciphertext and the encryption key, right? Did you try decrypting a long ciphertext with an all-zeroes IV? What did you get?

  8. I got

    Fatal error: Uncaught exception ‘Zend_Session_Exception’ with message ‘Invalid Session Data’ in C:workspaceJoomlatest.php:53 Stack trace: #0 C:workspaceJoomlatest.php(92): EncryptedSession-__construct() #1 {main} thrown in C:workspaceJoomlatest.php on line 53

    which is what I was expecting. What were you expecting?

  9. Pingback: Encrypt session data in PHP « DBGLORY 4 YOU

  10. Pingback: Encrypt session data in PHP – HackIX: Small Hacks for a Large World

  11. Pingback: Encrypt session data in PHP - Zimuel's blog

  12. Pingback: Dados criptografados na sessão PHP | F2 - Sistemas

  13. Pingback: Unite Blog » Dados criptografados na sessão PHP

  14. Pingback: Power Php - Dicas Atuais sobre o mundo php!

  15. Pingback: Encrypt session data in PHP | Enrico Zimuel

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Post Navigation

Web Analytics