Storing encrypted session information in a cookie
Our session system is due for an upgrade. Currently all PHP sessions are stored in the database, and some things are getting a bit slow. There have been a couple of approaches I've been considering, one of which is simply storing all the information in a browser cookie.
First I want to make clear I don't necessarily condone this. The reason I'm writing this post, is because I'm hoping for some more community feedback. Is this a really bad idea? I would love to know.
The benefits
If all the session data is stored in the browser, it means that I don't need to store it on the server. I actually don't care all that much for having the data on the server (unless it's the only secure way), it's mostly a gigantic map with session tokens and user id's (along with some other info).
I also feel it's more natural for HTTP, as it makes it a bit more stateless.
Sample code
<?php
class BrowserSession {
public $secret = 'this will need to be a cryptographic random number';
public $currentUser = null;
// Sessions time out after 10 minutes
public $timeout = 600;
function init() {
if (!isset($_COOKIE['MYSESSION'])) {
echo "No session cookie found\n";
return;
}
list($userId, $time, $signature) = explode(':',$_COOKIE['MYSESSION']);
// The cookie is old
if ($time> time() + $this->timeout) {
echo "The session cookie timed out\n";
}
if ($signature !== $this->generateSignature($userId,$time)) {
echo "The secret was incorrect\n";
}
$this->currentUser = $userId;
echo "Logged in as user: $userId\n";
}
function login($userId) {
$this->userId = $userId;
$time = time();
$cookie = $this->userId . ':' . time() . ':' . $this->generateSignature($userId,$time);
setcookie('MYSESSION',$cookie,$time+$this->timeout,null,null,null,true);
echo "Set cookie: $cookie\n";
}
function generateSignature($userId,$time) {
$stringToSign =
$userId . "\n" .
$time . "\n" .
$_SERVER['HTTP_USER_AGENT'] . "\n" .
$_SERVER['REMOTE_ADDR'];
return hash_hmac('SHA1',$stringToSign,$this->secret);
}
}
ob_start();
$session = new BrowserSession();
$session->init();
if (isset($_GET['login'])) $session->login($_GET['login']);
else {
echo '<br /><a href="?login=1234">Log in as user 1234</a>';
}
?>
A few notes:
- The preceeding code was just intended as a proof of concept, it's missing some validation.
- Currently the secret would be the same for every user. I was thinking of appending some per-user information to the secret. If somebody does guess or bruteforce the secret, they would only have access to a single users' information.
- If a user changes their password, existing sessions should expire. To do this the signature should also include a sequence number that changes when the password changes.
- Currently this only stores a user id. It could be extended to contain more data, but this is all I need.
So, is there anything fundamentally wrong with this approach? In general the client should never be trusted, but for setups where the security requirements aren't as high (highly subjective, I know) I feel this might be strong enough. OAuth, OpenID and Amazon AWS all seem to trust HMAC+SHA1, but those applications do work differently.
Credit where it's due
I first asked this question on stack overflow. The users there already gave some great suggestions and pointed out some of the flaws. Thank you!