Skip site navigation (1)Skip section navigation (2)

FreeBSD Manual Pages


home | help
Session::Storage::SecuUser)Contributed Perl DocumenSession::Storage::Secure(3)

       Session::Storage::Secure	- Encrypted, expiring, compressed, serialized
       session data with integrity

       version 1.000

	 my $store = Session::Storage::Secure->new(
	   secret_key	=> "your pass phrase here",
	   default_duration => 86400 * 7,

	 my $encoded = $store->encode( $data, $expires );

	 my $decoded = $store->decode( $encoded	);

       This module implements a	secure way to encode session data.  It is
       primarily intended for storing session data in browser cookies, but
       could be	used with other	backend	storage	where security of stored
       session data is important.

       Features	include:

       o   Data	serialization and compression using Sereal

       o   Data	encryption using AES with a unique derived key per encoded

       o   Enforced expiration timestamp (optional)

       o   Integrity protected with a message authentication code (MAC)

       The storage protocol used in this module	is based heavily on A Secure
       Cookie Protocol
       by Alex Liu and others.	Liu proposes a session cookie value as



	   | denotes concatenation with	a separator character
	   E(p,q) is a symmetric encryption of p with key q
	   HMAC(p,q) is	a keyed	message	hash of	p with key q
	   k is	HMAC(user|expiration, sk)
	   sk is a secret key shared by	all servers
	   ssl-key is an SSL session key

       Because SSL session keys	are not	readily	available (and SSL termination
       may happen prior	to the application server), we omit "ssl-key".	This
       weakens protection against replay attacks if an attacker	can break the
       SSL session key and intercept messages.

       Using "user" and	"expiration" to	generate the encryption	and MAC	keys
       was a method proposed to	ensure unique keys to defeat volume attacks
       against the secret key.	Rather than rely on those for uniqueness (with
       the unfortunate side effect of revealing	user names and prohibiting
       anonymous sessions), we replace "user" with a cryptographically-strong
       random salt value.

       The original proposal also calculates a MAC based on unencrypted	data.
       We instead calculate the	MAC based on the encrypted data.  This avoids
       an extra	step decrypting	invalid	messages.  Because the salt is already
       encoded into the	key, we	omit it	from the MAC input.

       Therefore, the session storage protocol used by this module is as



	   | denotes concatenation with	a separator character
	   E(p,q) is a symmetric encryption of p with key q
	   HMAC(p,q) is	a keyed	message	hash of	p with key q
	   k is	HMAC(salt, sk)
	   sk is a secret key shared by	all servers

       The salt	value is generated using Math::Random::ISAAC::XS, seeded from

       The HMAC	algorithm is "hmac_sha256" from	Digest::SHA.  Encryption is
       done by Crypt::CBC using	Crypt::Rijndael	(AES).	The ciphertext and
       MAC's in	the cookie are Base64 encoded by MIME::Base64 by default.

       During session retrieval, if the	MAC does not authenticate or if	the
       expiration is set and in	the past, the session will be discarded.

   secret_key (required)
       This is used to secure the session data.	 The encryption	and message
       authentication key is derived from this using a one-way function.
       Changing	it will	invalidate all sessions.

       Number of seconds for which the session may be considered valid.	 If an
       expiration is not provided to "encode", this is used instead to expire
       the session after a period of time.  It is unset	by default, meaning
       that session expiration is not capped.

       An optional array reference of strings containing old secret keys no
       longer used for encryption but still supported for decrypting session

       A character used	to separate fields.  It	defaults to "~".

       A hash reference	with constructor arguments for Sereal::Encoder.
       Defaults	to "{ snappy =>	1, croak_on_bless => 1 }".

       A hash reference	with constructor arguments for Sereal::Decoder.
       Defaults	to "{ refuse_objects =>	1, validate_utf8  => 1 }".

       A code reference	to convert binary data elements	(the encrypted data
       and the MAC) into a transport-safe form.	 Defaults to
       MIME::Base64::encode_base64url.	The output must	not include the
       "separator" attribute used to delimit fields.

       A code reference	to extract binary data (the encrypted data and the
       MAC) from a transport-safe form.	 It must be the	complement to
       "encode".  Defaults to MIME::Base64::decode_base64url.

       An integer representing the protocol used by
       "Session::Storage::Secure".  Protocol 1 was the initial version,	which
       used a now-deprecated mode of Crypt::CBC.  Protocol 2 is	the current

	 my $string = $store->encode( $data, $expires );

       The $data argument should be a reference	to a data structure.  By
       default,	it must	not contain objects.  (See "Objects not	stored by
       default"	for rationale and alternatives.) If it is undefined, an	empty
       hash reference will be encoded instead.

       The optional $expires argument should be	the session expiration time
       expressed as epoch seconds.  If the $expires time is in the past, the
       $data argument is cleared and an	empty hash reference is	encoded	and
       returned.  If no	$expires is given, then	if the "default_duration"
       attribute is set, it will be used to calculate an expiration time.

       The method returns a string that	securely encodes the session data.
       All binary components are protected via the "transport_encoder"

       An exception is thrown on any errors.

	 my $data = $store->decode( $string );

       The $string argument must be the	output of "encode".

       If the message integrity	check fails or if expiration exists and	is in
       the past, the method returns undef or an	empty list (depending on

       An exception is thrown on any errors.

   Secret key
       You must	protect	the secret key,	of course.  Rekeying periodically
       would improve security.	Rekeying also invalidates all existing
       sessions	unless the "old_secrets" attribute contains old	encryption
       keys still used for decryption.	In a multi-node	application, all nodes
       must share the same secret key.

   Session size
       If storing the encoded session in a cookie, keep	in mind	that cookies
       must fit	within 4k, so don't store too much data.  This module uses
       Sereal for serialization	and enables the	"snappy" compression option.
       Sereal plus Snappy appears to be	one of the fastest and most compact
       serialization options for Perl, according to the	Sereal benchmarks
       <>	page.

       However,	nothing	prevents the encoded output from exceeding 4k.
       Applications must check for this	condition and handle it	appropriately
       with an error or	by splitting the value across multiple cookies.

   Objects not stored by default
       The default Sereal options do not allow storing objects because object
       deserialization can have	undesirable side effects, including
       potentially fatal errors	if a class is not available at deserialization
       time or if internal class structures changed from when the session data
       was serialized to when it was deserialized.  Applications should	take
       steps to	deflate/inflate	objects	before storing them in session data.

       Alternatively, applications can change "sereal_encoder_options" and
       "sereal_decoder_options"	to allow object	serialization or other object
       transformations and accept the risks of doing so.

       Storing encrypted session data within a browser cookie avoids latency
       and overhead of backend session storage,	but has	several	additional
       security	considerations.

   Transport security
       If using	cookies	to store session data, an attacker could intercept
       cookies and replay them to impersonate a	valid user regardless of
       encryption.  SSL	encryption of the transport channel is strongly

   Cookie replay
       Because all session state is maintained in the session cookie, an
       attacker	or malicious user could	replay an old cookie to	return to a
       previous	state.	Cookie-based sessions should not be used for recording
       incremental steps in a transaction or to	record "negative rights".

       Because cookie expiration happens on the	client-side, an	attacker or
       malicious user could replay a cookie after its scheduled	expiration
       date.  It is strongly recommended to set	"cookie_duration" or
       "default_duration" to limit the window of opportunity for such replay

   Session authentication
       A compromised secret key	could be used to construct valid messages
       appearing to be from any	user.  Applications should take	extra steps in
       their use of session data to ensure that	sessions are authenticated to
       the user.

       One simple approach could be to store a hash of the user's hashed
       password	in the session on login	and to verify it on each request.

	 # on login
	 my $hashed_pw = bcrypt( $password, $salt );
	 if ( $hashed_pw eq $hashed_pw_from_db ) {
	   session user	=> $user;
	   session auth	=> bcrypt( $hashed_pw, $salt ) );

	 # on each request
	 if ( bcrypt( $hashed_pw_from_db, $salt	) ne session("auth") ) {

       The downside of this is that if there is	a read-only attack against the
       database	(SQL injection or leaked backup	dump) and the secret key is
       compromised, then an attacker can forge a cookie	to impersonate any

       A more secure approach suggested	by Stephen Murdoch in Hardened
       Stateless Session Cookies
       <> is to
       store an	iterated hash of the hashed password in	the database and use
       the hashed password itself within the session.

	 # on login
	 my $hashed_pw = bcrypt( $password, $salt );
	 if ( bcrypt( $hashed_pw, $salt	) eq $double_hashed_pw_from_db ) {
	   session user	=> $user;
	   session auth	=> $hashed_pw;

	 # on each request
	 if ( $double_hashed_pw_from_db	ne bcrypt( session("auth"), $salt ) ) {

       This latter approach means that even a compromise of the	secret key and
       the database contents can't be used to impersonate a user because doing
       so would	requiring reversing a one-way hash to determine	the correct
       authenticator to	put into the forged cookie.

       Both methods require an additional database read	per request. This
       diminishes some of the scalability benefits of storing session data in
       a cookie, but the read could be cached and there	is still no database
       write needed to store session data.

       Papers on secure	cookies	and cookie session storage:

       o   Liu,	Alex X., et al., A Secure Cookie Protocol

       o   Murdoch, Stephen J.,	Hardened Stateless Session Cookies

       o   Fu, Kevin, et al., Dos and Don'ts of	Client Authentication on the
	   Web <>

       CPAN modules implementing cookie	session	storage:

       o   Catalyst::Plugin::CookiedSession -- encryption only

       o   Dancer::Session::Cookie -- Dancer 1,	encryption only

       o   Dancer::SessionFactory::Cookie -- Dancer 2, forthcoming, based on
	   this	module

       o   HTTP::CryptoCookie -- encryption only

       o   Mojolicious::Sessions -- MAC	only

       o   Plack::Middleware::Session::Cookie -- MAC only

       o   Plack::Middleware::Session::SerializedCookie	-- really just a
	   framework and you provide the guts with callbacks

       Related CPAN modules that offer frameworks for serializing and
       encrypting data,	but without features relevant for sessions like
       expiration and unique keying.

       o   Crypt::Util

       o   Data::Serializer

       David Golden <>

       o   Petr	PAsaA <>

       o   Tom Hukins <>

       This software is	Copyright (c) 2013 by David Golden.

       This is free software, licensed under:

	 The Apache License, Version 2.0, January 2004

perl v5.32.1			  2021-03-23	   Session::Storage::Secure(3)


Want to link to this manual page? Use this URL:

home | help