Ben Bangert avatar Ben Bangert committed e1d343e

* Updated cookie-based session storage to use 256-bit AES-CTR mode with a
256-bit HMAC signature. Now requires PyCrypto to use for AES scheme.

Comments (0)

Files changed (10)

 =================
 
 0.9 (**tip**)
+* Updated cookie-based session storage to use 256-bit AES-CTR mode with a
+  256-bit HMAC signature. Now requires PyCrypto to use for AES scheme.
 * WARNING: Moved session and cache middleware to middleware, as per the old
   deprecation warnings had said was going to happen for 0.8.
 * Added cookie-only session storage with RC4 ciphered encryption, requires 

beaker/cerealizer.py

-# Cerealizer v0.6
-# Copyright (C) 2005-2007 Jean-Baptiste LAMY
-#
-# This program is free software.
-# It is available under the Python licence.
-
-"""Cerealizer -- A secure Pickle-like module
-
-The interface of the Cerealizer module is similar to Pickle, and it supports
-__getstate__, __setstate__, __getinitargs__ and __getnewargs__.
-
-Cerealizer supports int, long, float, bool, complex, string, unicode, tuple, list, set, frozenset,
-dict, old-style and new-style class instances. C-defined types are supported but saving the C-side
-data may require to write e.g. a specific Handler or a __getstate__ and __setstate__ pair.
-Objects with __slots__ are supported too.
-
-You have to register the class you want to serialize, by calling cerealizer.register(YourClass).
-Cerealizer can be considered as secure AS LONG AS the following methods of 'YourClass' are secure:
-  - __new__
-  - __del__
-  - __getstate__
-  - __setstate__
-  - __init__ (ONLY if __getinitargs__ is used for the class)
-
-These methods are the only one Cerealizer may call. For a higher security, Cerealizer maintains
-its own reference to these method (exepted __del__ that can only be called indirectly).
-
-Cerealizer doesn't aim at producing Human-readable files. About performances, Cerealizer is
-really fast and, when powered by Psyco, it may even beat cPickle! Although Cerealizer is
-implemented in less than 500 lines of pure-Python code (which is another reason for Cerealizer
-to be secure, since less code means less bugs :-).
-
-Compared to Pickle (cPickle):
- - Cerealizer is secure
- - Cerealizer achieves similar performances (using Psyco)
- - Cerealizer requires you to declare the serializable classes
-
-Compared to Jelly (from TwistedMatrix):
- - Cerealizer is faster
- - Cerealizer does a better job with object cycles, C-defined types and tuples (*)
- - Cerealizer files are not Human readable
-
-(*) Jelly handles them, but tuples and objects in a cycle are first created as _Tuple or
-_Dereference objects; this works for Python classes, but not with C-defined types which
-expects a precise type (e.g. tuple and not _Tuple).
-
-
-
-IMPLEMENTATION DETAILS
-
-GENERAL FILE FORMAT STRUCTURE
-
-Cerealizer format is simple but quite surprising. It uses a "double flat list" format.
-It looks like that :
-
-  <magic code (currently cereal1)>\\n
-  <number of objects>\\n
-  <classname of object #0>\\n
-  <optional data for creating object #0 (currently nothing except for tuples)>
-  <classname of object #1>\\n
-  <optional data for creating object #1 (currently nothing except for tuples)>
-  [...]
-  <data of object #0 (format depend of the type of object #0)>
-  <data of object #1 (format depend of the type of object #1)>
-  [...]
-  <reference to the 'root' object>
-
-As you can see, the information for a given object is splitted in two parts, the first one
-for object's class, and the second one for the object's data.
-
-To avoid problems, the order of the objects is the following:
-
-  <list, dict, set>
-  <object, instance>
-  <tuple, sorted by depth (=max number of folded tuples)>
-
-Objects are put after basic types (list,...), since object's __setstate__ might rely on
-a list, and thus the list must be fully loaded BEFORE calling the object's __setstate__.
-
-
-DATA (<data of object #n> above)
-
-The part <data of object #n> saves the data of object #n. It may contains reference to other data
-(see below, in Cerealizer references include reference to other objects but also raw data like int).
-
- - an object           is saved by :  <reference to the object state (the value returned by object.__getstate__() or object.__dict__)>
-                                      e.g. 'r7\\n' (object #7 being e.g. the __dict__).
-
- - a  list or a set    is saved by :  <number of item>\\n
-                                      <reference to item #0>
-                                      <reference to item #1>
-                                      [...]
-                                      e.g. '3\\ni0\\ni1\\ni2\\n' for [0, 1, 2]
-
- - a  dict             is saved by :  <number of item>\\n
-                                      <reference to value #0>
-                                      <reference to key #0>
-                                      <reference to value #1>
-                                      <reference to key #1>
-                                      [...]
-
-
-REFERENCES (<reference to XXX> above)
-
-In Cerealizer a reference can be either a reference to another object being serialized in the
-same file, or a raw value (e.g. an integer).
- - an int              is saved by e.g. 'i187\\n'
- - a  long             is saved by e.g. 'l10000000000\\n'
- - a  float            is saved by e.g. 'f1.07\\n'
- - a  bool             is saved by      'b0' or 'b1'
- - a  string           is saved by e.g. 's5\\nascii' (where 5 is the number of characters)
- - an unicode          is saved by e.g. 'u4\\nutf8'  (where 4 is the number of characters)
- - an object reference is saved by e.g. 'r3\\n'      (where 3 means reference to object #3)
- -    None             is saved by      'n'
-"""
-
-__alls__ = ["load", "dump", "loads", "dumps", "freeze_configuration", "register"]
-VERSION = "0.6"
-
-import logging
-logger = logging.getLogger(__name__)
-#logging.basicConfig(level=logging.INFO)
-
-from cStringIO import StringIO
-from new       import instance
-
-class NotCerealizerFileError(StandardError): pass
-class NonCerealizableObjectError(StandardError): pass
-
-def _priority_sorter(a, b): return cmp(a[0], b[0])
-
-class Dumper(object):
-  def __init__(self): self.init()
-  def init(self):
-    self.objs            = []
-    self.objs_id         = set()
-    self.priorities_objs = [] # [(priority1, obj1), (priority2, obj2),...]
-    self.obj2state       = {}
-    self.obj2newargs     = {}
-    self.id2id           = {}
-    self.id2obj          = None
-    
-  def dump(self, root_obj, s):
-    self.collect(root_obj)
-    self.priorities_objs.sort(_priority_sorter)
-    self.objs.extend([o for (priority, o) in self.priorities_objs])
-    
-    s.write("cereal1\n%s\n" % len(self.objs))
-    
-    i = 0
-    for obj in self.objs:
-      self.id2id[id(obj)] = i
-      i += 1
-    for obj in self.objs: _HANDLERS_[obj.__class__].dump_obj (obj, self, s)
-    for obj in self.objs: _HANDLERS_[obj.__class__].dump_data(obj, self, s)
-    
-    _HANDLERS_[root_obj.__class__].dump_ref(root_obj, self, s)
-    self.init()
-    
-  def undump(self, s):
-    if s.read(8) != "cereal1\n": raise NotCerealizerFileError("Not a cerealizer file!")
-    
-    nb = int(s.readline())
-    self.id2obj = [ None ] * nb  # DO NOT DO  self.id2obj = [comprehension list], since undump_ref may access id2obj during its construction
-    for i in range(nb):
-      classname = s.readline()
-      handler = _HANDLERS.get(classname)
-      if not handler: raise NonCerealizableObjectError("Object of class/type '%s' cannot be de-cerealized! Use cerealizer.register to extend Cerealizer support to other classes." % classname[:-1])
-      self.id2obj[i] = handler.undump_obj(self, s)
-    for obj in self.id2obj: _HANDLERS_[obj.__class__].undump_data(obj, self, s)
-    
-    r = self.undump_ref(s)
-    self.init()
-    return r
-  
-  def collect(self, obj):
-    """Dumper.collect(OBJ) -> bool
-
-Collects OBJ for serialization. Returns false is OBJ is already collected; else returns true."""
-    handler = _HANDLERS_.get(obj.__class__)
-    if not handler: raise NonCerealizableObjectError("Object of class/type '%s' cannot be cerealized! Use cerealizer.register to extend Cerealizer support to other classes." % obj.__class__)
-    handler.collect(obj, self)
-  
-  def dump_ref (self, obj, s):
-    """Dumper.dump_ref(OBJ, S)
-
-Writes a reference to OBJ in file S."""
-    _HANDLERS_[obj.__class__].dump_ref(obj, self, s)
-    
-  def undump_ref(self, s):
-    """Dumper.undump_ref(S) -> obj
-
-Reads a reference from file S."""
-    c = s.read(1)
-    if   c == "i": return int  (s.readline())
-    elif c == "f": return float(s.readline())
-    elif c == "s": return s.read(int(s.readline()))
-    elif c == "u": return s.read(int(s.readline())).decode("utf8")
-    elif c == "r": return self.id2obj[int(s.readline())]
-    elif c == "n": return None
-    elif c == "b": return bool(int(s.read(1)))
-    elif c == "l": return long(s.readline())
-    elif c == "c": return complex(s.readline())
-    raise ValueError("Unknown ref code '%s'!" % c)
-    
-  def immutable_depth(self, t):
-    depth = 0
-    for i in t:
-      i2 = self.obj2newargs.get(id(i))
-      if not i2 is None: i = i2
-      if isinstance(i, tuple) or isinstance(i, frozenset):
-        x = self.immutable_depth(i)
-        if x > depth: depth = x
-    return depth + 1
-  
-class Handler(object):
-  """Handler
-
-A customized handler for serialization and deserialization.
-You can subclass it to extend cerealization support to new object.
-See also ObjHandler."""
-  
-  def collect(self, obj, dumper):
-    """Handler.collect(obj, dumper) -> bool
-
-Collects all the objects referenced by OBJ.
-For each objects ROBJ referenced by OBJ, calls collect method of the Handler for ROBJ's class,
-i.e._HANDLERS_[ROBJ.__class__].collect(ROBJ, dumper).
-Returns false if OBJ is already referenced (and thus no collection should occur); else returns true.
-"""
-    i = id(obj)
-    if not i in dumper.objs_id:
-      dumper.objs.append(obj)
-      dumper.objs_id.add(i)
-      return 1
-    
-  def dump_obj (self, obj, dumper, s):
-    """Handler.dump_obj(obj, dumper, s)
-
-Dumps OBJ classname in file S."""
-    s.write(self.classname)
-    
-  def dump_data(self, obj, dumper, s):
-    """Handler.dump_data(obj, dumper, s)
-
-Dumps OBJ data in file S."""
-    
-  def dump_ref (self, obj, dumper, s):
-    """Handler.dump_ref(obj, dumper, s)
-
-Write a reference to OBJ in file S.
-You should not override dump_ref, since they is no corresponding 'undump_ref' that you
-can override."""
-    s.write("r%s\n" % dumper.id2id[id(obj)])
-  
-  def undump_obj(self, dumper, s):
-    """Handler.undump_obj(dumper, s)
-
-Returns a new uninitialized (=no __init__'ed) instance of the class.
-If you override undump_obj, DUMPER and file S can be used to read additional data
-saved by Handler.dump_obj()."""
-    
-  def undump_data(self, obj, dumper, s):
-    """Handler.undump_data(obj, dumper, s)
-
-Reads the data for OBJ, from DUMPER and file S.
-If you override undump_data, you should use DUMPER.undump_ref(S) to
-read a reference or a basic type (=a string, an int,...)."""
-  
-    
-class RefHandler(object):
-  def collect  (self, obj, dumper)   : pass
-  def dump_obj (self, obj, dumper, s): pass
-  def dump_data(self, obj, dumper, s): pass
-  
-class NoneHandler(RefHandler):
-  def dump_ref (self, obj, dumper, s): s.write("n")
-  
-class StrHandler(RefHandler):
-  def dump_ref (self, obj, dumper, s): s.write("s%s\n%s" % (len(obj), obj))
-  
-class UnicodeHandler(RefHandler):
-  def dump_ref (self, obj, dumper, s):
-    obj = obj.encode("utf8")
-    s.write("u%s\n%s" % (len(obj), obj))
-    
-class BoolHandler(RefHandler):
-  def dump_ref (self, obj, dumper, s): s.write("b%r" % int(obj))
-
-class IntHandler(RefHandler):
-  def dump_ref (self, obj, dumper, s): s.write("i%r\n" % obj)
-  
-class LongHandler(RefHandler):
-  def dump_ref (self, obj, dumper, s): s.write("l%r\n" % obj)
-  
-class FloatHandler(RefHandler):
-  def dump_ref (self, obj, dumper, s): s.write("f%r\n" % obj)
-  
-class ComplexHandler(RefHandler):
-  def dump_ref (self, obj, dumper, s):
-    c = repr(obj)
-    if c.startswith("("): c = c[1:-1] # complex("(1+2j)") doesn't work
-    s.write("c%s\n" % c)
-    
-
-class TupleHandler(Handler):
-  classname = "tuple\n"
-  def collect(self, obj, dumper):
-    if not id(obj) in dumper.objs_id:
-      dumper.priorities_objs.append((dumper.immutable_depth(obj), obj))
-      dumper.objs_id.add(id(obj))
-      
-      for i in obj: dumper.collect(i)
-      return 1
-    
-  def dump_obj(self, obj, dumper, s):
-    s.write("%s%s\n" % (self.classname, len(obj)))
-    for i in obj: _HANDLERS_[i.__class__].dump_ref(i, dumper, s)
-    
-  def undump_obj(self, dumper, s): return tuple([dumper.undump_ref(s) for i in range(int(s.readline()))])
-  
-class FrozensetHandler(TupleHandler):
-  classname = "frozenset\n"
-  def undump_obj(self, dumper, s): return frozenset([dumper.undump_ref(s) for i in range(int(s.readline()))])
-  
-  
-class ListHandler(Handler):
-  classname = "list\n"
-  def collect(self, obj, dumper):
-    if Handler.collect(self, obj, dumper):
-      for i in obj: dumper.collect(i)
-      return 1
-    
-  def dump_data(self, obj, dumper, s):
-    s.write("%s\n" % len(obj))
-    for i in obj: _HANDLERS_[i.__class__].dump_ref(i, dumper, s)
-      
-  def undump_obj(self, dumper, s): return []
-  
-  def undump_data(self, obj, dumper, s):
-    for i in range(int(s.readline())): obj.append(dumper.undump_ref(s))
-    
-class SetHandler(ListHandler):
-  classname = "set\n"
-  def undump_obj(self, dumper, s): return set()
-  def undump_data(self, obj, dumper, s):
-    for i in range(int(s.readline())): obj.add(dumper.undump_ref(s))
-    
-class DictHandler(Handler):
-  classname = "dict\n"
-  def collect(self, obj, dumper):
-    if Handler.collect(self, obj, dumper):
-      for i in obj.iterkeys  (): dumper.collect(i) # Collect is not ordered
-      for i in obj.itervalues(): dumper.collect(i)
-      return 1
-    
-  def dump_data(self, obj, dumper, s):
-    s.write("%s\n" % len(obj))
-    for k, v in obj.iteritems():
-      _HANDLERS_[v.__class__].dump_ref(v, dumper, s) # Value is saved fist
-      _HANDLERS_[k.__class__].dump_ref(k, dumper, s)
-      
-  def undump_obj(self, dumper, s): return {}
-  
-  def undump_data(self, obj, dumper, s):
-    for i in range(int(s.readline())):
-      obj[dumper.undump_ref(s)] = dumper.undump_ref(s) # Value is read fist
-      
-
-class ObjHandler(Handler):
-  """ObjHandler
-
-A Cerealizer Handler that can support any new-style class instances, old-style class instances
-as well as C-defined types (although it may not save the C-side data)."""
-  def __init__(self, Class, classname = ""):
-    self.Class          = Class
-    self.Class_new      = getattr(Class, "__new__"     , instance)
-    self.Class_getstate = getattr(Class, "__getstate__", None)  # Check for and store __getstate__ and __setstate__ now
-    self.Class_setstate = getattr(Class, "__setstate__", None)  # so we are are they are not modified in the class or the object
-    if classname: self.classname = "%s\n"    % classname
-    else:         self.classname = "%s.%s\n" % (Class.__module__, Class.__name__)
-    
-  def collect(self, obj, dumper):
-    i = id(obj)
-    if not i in dumper.objs_id:
-      dumper.priorities_objs.append((-1, obj))
-      dumper.objs_id.add(i)
-      
-      if self.Class_getstate: state = self.Class_getstate(obj)
-      else:                   state = obj.__dict__
-      dumper.obj2state[i] = state
-      dumper.collect(state)
-      return 1
-    
-  def dump_data(self, obj, dumper, s):
-    i = dumper.obj2state[id(obj)]
-    _HANDLERS_[i.__class__].dump_ref(i, dumper, s)
-    
-  def undump_obj(self, dumper, s): return self.Class_new(self.Class)
-  
-  def undump_data(self, obj, dumper, s):
-    if self.Class_setstate: self.Class_setstate(obj, dumper.undump_ref(s))
-    else:                   obj.__dict__ =           dumper.undump_ref(s)
-    
-class SlotedObjHandler(ObjHandler):
-  """SlotedObjHandler
-
-A Cerealizer Handler that can support new-style class instances with __slot__."""
-  def __init__(self, Class, classname = ""):
-    ObjHandler.__init__(self, Class, classname)
-    self.Class_slots = Class.__slots__
-    
-  def collect(self, obj, dumper):
-    i = id(obj)
-    if not i in dumper.objs_id:
-      dumper.priorities_objs.append((-1, obj))
-      dumper.objs_id.add(i)
-      
-      if self.Class_getstate: state = self.Class_getstate(obj)
-      else:                   state = dict([(slot, getattr(obj, slot, None)) for slot in self.Class_slots])
-      dumper.obj2state[i] = state
-      dumper.collect(state)
-      return 1
-    
-  def undump_data(self, obj, dumper, s):
-    if self.Class_setstate: self.Class_setstate(obj, dumper.undump_ref(s))
-    else:
-      state = dumper.undump_ref(s)
-      for slot in self.Class_slots: setattr(obj, slot, state[slot])
-      
-class InitArgsObjHandler(ObjHandler):
-  """InitArgsObjHandler
-
-A Cerealizer Handler that can support class instances with __getinitargs__."""
-  def __init__(self, Class, classname = ""):
-    ObjHandler.__init__(self, Class, classname)
-    self.Class_getinitargs = Class.__getinitargs__
-    self.Class_init        = Class.__init__
-    
-  def collect(self, obj, dumper):
-    i = id(obj)
-    if not i in dumper.objs_id:
-      dumper.priorities_objs.append((-1, obj))
-      dumper.objs_id.add(i)
-      
-      dumper.obj2state[i] = state = self.Class_getinitargs(obj)
-      dumper.collect(state)
-      return 1
-    
-  def undump_data(self, obj, dumper, s): self.Class_init(obj, *dumper.undump_ref(s))
-      
-class NewArgsObjHandler(ObjHandler):
-  """NewArgsObjHandler
-
-A Cerealizer Handler that can support class instances with __getnewargs__."""
-  def __init__(self, Class, classname = ""):
-    ObjHandler.__init__(self, Class, classname)
-    self.Class_getnewargs = Class.__getnewargs__
-    
-  def collect(self, obj, dumper):
-    i = id(obj)
-    if not i in dumper.objs_id:
-      dumper.obj2newargs[i] = newargs = self.Class_getnewargs(obj)
-      dumper.collect(newargs)
-      
-      dumper.priorities_objs.append((dumper.immutable_depth(newargs), obj))
-      dumper.objs_id.add(i)
-      
-      if self.Class_getstate: state = self.Class_getstate(obj)
-      else:                   state = obj.__dict__
-      dumper.obj2state[i] = state
-      dumper.collect(state)
-      return 1
-    
-  def dump_obj (self, obj, dumper, s):
-    s.write(self.classname)
-    newargs = dumper.obj2newargs[id(obj)]
-    _HANDLERS_[newargs.__class__].dump_ref(newargs, dumper, s)
-    
-  def undump_obj(self, dumper, s): return self.Class_new(self.Class, *dumper.undump_ref(s))
-  
-  
-_configurable = 1
-_HANDLERS  = {}
-_HANDLERS_ = {}
-def register(Class, handler = None, classname = ""):
-  """register(Class, handler = None, classname = "")
-
-Registers CLASS as a serializable and secure class.
-By calling register, YOU HAVE TO ASSUME THAT THE FOLLOWING METHODS ARE SECURE:
-  - CLASS.__new__
-  - CLASS.__del__
-  - CLASS.__getstate__
-  - CLASS.__setstate__
-  - CLASS.__getinitargs__
-  - CLASS.__init__ (only if CLASS.__getinitargs__ exists)
-
-HANDLER is the Cerealizer Handler object that handles serialization and deserialization for Class.
-If not given, Cerealizer create an instance of ObjHandler, which is suitable for old-style and
-new_style Python class, and also C-defined types (although if it has some C-side data, you may
-have to write a custom Handler or a __getstate__ and __setstate__ pair).
-
-CLASSNAME is the classname used in Cerealizer files. It defaults to the full classname (module.class)
-but you may choose something shorter -- as long as there is no risk of name clash."""
-  if not _configurable: raise StandardError("Cannot register new classes after freeze_configuration has been called!")
-  if "\n" in classname: raise ValueError("CLASSNAME cannot have \\n (Cerealizer automatically add a trailing \\n for performance reason)!")
-  if not handler:
-    if   hasattr(Class, "__getnewargs__" ): handler = NewArgsObjHandler (Class, classname)
-    elif hasattr(Class, "__getinitargs__"): handler = InitArgsObjHandler(Class, classname)
-    elif hasattr(Class, "__slots__"      ): handler = SlotedObjHandler  (Class, classname)
-    else:                                   handler = ObjHandler        (Class, classname)
-  if _HANDLERS_.has_key(Class): raise ValueError("Class %s has already been registred!" % Class)
-  if not isinstance(handler, RefHandler):
-    if _HANDLERS .has_key(handler.classname): raise ValueError("A class has already been registred under the name %s!" % handler.classname[:-1])
-    _HANDLERS [handler.classname] = handler
-    if handler.__class__ is ObjHandler:
-      logger.info("Registring class %s as '%s'" % (Class, handler.classname[:-1]))
-    else:
-      logger.info("Registring class %s as '%s' (using %s)" % (Class, handler.classname[:-1], handler.__class__.__name__))
-  else:
-    logger.info("Registring reference '%s'" % Class)
-    
-  _HANDLERS_[Class] = handler
-
-register_class = register # For backward compatibility
-
-def register_alias(Class, alias):
-  """register_alias(Class, alias)
-
-Registers ALIAS as an alias classname for CLASS.
-Usefull for keeping backward compatibility in files: e.g. if you have renamed OldClass to
-NewClass, just do:
-
-    cerealizer.register_alias(NewClass, "OldClass")
-
-and you'll be able to open old files containing OldClass serialized."""
-  handler = _HANDLERS_.get(Class)
-  if not handler:
-    raise ValueError("Cannot register alias '%s' to Class %s: the class is not yet registred!" % (alias, Class))
-  if _HANDLERS.has_key(alias):
-    raise ValueError("Cannot register alias '%s' to Class %s: another class is already registred under the alias name!" % (alias, Class))
-  logger.info("Registring alias '%s' for %s" % (alias, Class))
-  _HANDLERS[alias + "\n"] = handler
-
-
-def freeze_configuration():
-  """freeze_configuration()
-
-Ends Cerealizer configuration. When freeze_configuration() is called, it is no longer possible
-to register classes, using register().
-Calling freeze_configuration() is not mandatory, but it may enforce security, by forbidding
-unexpected calls to register()."""
-  global _configurable
-  _configurable = 0
-  logger.info("Configuration frozen")
-  
-register(type(None), NoneHandler     ())
-register(str       , StrHandler      ())
-register(unicode   , UnicodeHandler  ())
-register(bool      , BoolHandler     ())
-register(int       , IntHandler      ())
-register(long      , LongHandler     ())
-register(float     , FloatHandler    ())
-register(complex   , ComplexHandler  ())
-register(dict      , DictHandler     ())
-register(list      , ListHandler     ())
-register(set       , SetHandler      ())
-register(tuple     , TupleHandler    ())
-register(frozenset , FrozensetHandler())
-
-
-def dump(obj, file, protocol = 0):
-  """dump(obj, file, protocol = 0)
-
-Serializes object OBJ in FILE.
-PROTOCOL is unused, it exists only for compatibility with Pickle."""
-  Dumper().dump(obj, file)
-  
-def load(file):
-  """load(file) -> obj
-
-De-serializes an object from FILE."""
-  return Dumper().undump(file)
-
-def dumps(obj, protocol = 0):
-  """dumps(obj, protocol = 0) -> str
-
-Serializes object OBJ and returns the serialized string.
-PROTOCOL is unused, it exists only for compatibility with Pickle."""
-  s = StringIO()
-  Dumper().dump(obj, s)
-  return s.getvalue()
-
-def loads(string):
-  """loads(file) -> obj
-
-De-serializes an object from STRING."""
-  return Dumper().undump(StringIO(string))
-
-
-def dump_class_of_module(*modules):
-  """dump_class_of_module(*modules)
-
-Utility function; for each classes found in the given module, print the needed call to register."""
-  class D: pass
-  class O(object): pass
-  s = set([c for module in modules for c in module.__dict__.values() if isinstance(c, type(D)) or  isinstance(c, type(O))])
-  l = ['cerealizer.register(%s.%s)' % (c.__module__, c.__name__) for c in s]
-  l.sort()
-  for i in l: print i
-  

beaker/ciphersaber2.py

-"""A CipherSaber-2 implementation
-
-Updated Dec 12, 2007 by Ben Bangert <ben@groovie.org>
-    - Removed command line usage.
-    - Attempts to use os.urandom for the random IV as its more random
-      than the Python random module.
-Updated Feb 5, 2002 by Magnus Lie Hetland <magnus@hetland.org>
-    - Unlike the original, ASCII armour is used, and getpass is used to
-      read the password if it is not supplied on the commandline. 
-      Therefore explicit filenames are used instead of stdin and 
-      stdout.
-Original by: Ka-Ping Yee <ping@lfw.org>
-
-For more information about CipherSaber, see http://ciphersaber.gurus.com
-"""
-import os
-import sys
-import random
-import getpass
-
-
-def arcfour(input, key, n=20):
-    '''Perform the ARCFOUR algorithm on a given input list of bytes with a
-    key given as a list of bytes, and return the output as a list of bytes.'''
-    i, j, state = 0, 0, range(256)
-    for k in range(n):
-        for i in range(256):
-            j = (j + state[i] + key[i % len(key)]) % 256
-            state[i], state[j] = state[j], state[i]
-    i, j, output = 0, 0, []
-    for byte in input:
-        i = (i + 1) % 256
-        j = (j + state[i]) % 256
-        state[i], state[j] = state[j], state[i]
-        n = (state[i] + state[j]) % 256
-        output.append(byte ^ state[n])
-    return output
-
-def b2a(text):
-    'Given a string of binary data, return an "armoured" string.'
-    lines = []
-    words = ['%02x' % o for o in map(ord, text)]
-    while words:
-        lines.append(' '.join(words[:23]))
-        del words[:23]
-    return '\n'.join(lines)
-
-def a2b(text):
-    'Given an "armoured" string, return a string of binary data.'
-    return ''.join(map(chr, [int(w, 16) for w in text.split()]))
-
-def encipher(plaintext, key, iv=""):
-    'Given a plaintext string and key, return an enciphered string.'
-    if hasattr(os, 'urandom'):
-        iv = os.urandom(10)
-    else:
-        while len(iv) < 10: 
-            iv = iv + chr(random.randrange(256))
-    bytes = arcfour(map(ord, plaintext), map(ord, key + iv))
-    return iv + ''.join(map(chr, bytes))
-
-def decipher(ciphertext, key):
-    'Given a ciphertext string and key, return the deciphered string.'
-    iv, ciphertext = ciphertext[:10], ciphertext[10:]
-    bytes = arcfour(map(ord, ciphertext), map(ord, key + iv))
-    return ''.join(map(chr, bytes))

beaker/crypto/CTRCipher.py

+#!/usr/bin/python
+# -*- coding: ascii -*-
+###########################################################################
+# CTRCipher.py - Make PyCrypto CTR-mode properly as stream ciphers
+#
+# Copyright (C) 2007 Dwayne C. Litzenberger <dlitz@dlitz.net>
+# All rights reserved.
+# 
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose and without fee is hereby granted,
+# provided that the above copyright notice appear in all copies and that
+# both that copyright notice and this permission notice appear in
+# supporting documentation.
+# 
+# THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR 
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# Country of origin: Canada
+#
+###########################################################################
+# History:
+#
+#  2007-07-28 Dwayne C. Litzenberger <dlitz@dlitz.net>
+#   - Pre-release (v0.0)
+#
+###########################################################################
+
+from struct import pack
+
+class CTRCounter(object):
+    def __init__(self, nonce):
+        if len(nonce) != 8:
+            raise ValueError("nonce must be 8 bytes")
+        self.nonce = nonce
+        self.counter = 0
+        self._ctr_mask = ((1<<64)-1)
+    def __call__(self):
+        self.counter = (self.counter + 1) & self._ctr_mask
+        if self.counter == 0:
+            raise OverflowError("Counter overflowed")
+        return self.nonce + pack("!Q", self.counter)
+
+    def setNext(self, counter):
+        self.counter = counter - 1
+
+class CTRCipher(object):
+    
+    block_size = 0      # behaves like a stream cipher
+
+    def __init__(self, key, nonce, ciphermodule):
+        self._counter = CTRCounter(nonce)
+        self._cipher = ciphermodule.new(key, ciphermodule.MODE_CTR, counter=self._counter)
+        self._offset = 0
+
+    def encrypt(self, plaintext):
+        assert(self._offset >= 0)
+        if len(plaintext) == 0:
+            return ""
+        if self._offset != 0:
+            self._counter.setNext(self._counter.counter)
+        beforePad = self._offset + len(plaintext)
+        pad_size = self._cipher.block_size - (self._offset + len(plaintext)) % self._cipher.block_size
+        if pad_size == self._cipher.block_size:
+            pad_size = 0
+        ciphertext = self._cipher.encrypt("\0" * self._offset + plaintext + "\0" * pad_size)
+        ciphertext = ciphertext[self._offset:beforePad]
+        self._offset = beforePad % self._cipher.block_size
+        return ciphertext
+
+    def decrypt(self, ciphertext):
+        return self.encrypt(ciphertext)
+
+# vim:set ts=4 sw=4 sts=4 expandtab:

beaker/crypto/PBKDF2.py

+#!/usr/bin/python
+# -*- coding: ascii -*-
+###########################################################################
+# PBKDF2.py - PKCS#5 v2.0 Password-Based Key Derivation
+#
+# Copyright (C) 2007 Dwayne C. Litzenberger <dlitz@dlitz.net>
+# All rights reserved.
+# 
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose and without fee is hereby granted,
+# provided that the above copyright notice appear in all copies and that
+# both that copyright notice and this permission notice appear in
+# supporting documentation.
+# 
+# THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR 
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# Country of origin: Canada
+#
+###########################################################################
+# Sample PBKDF2 usage:
+#   from Crypto.Cipher import AES
+#   from PBKDF2 import PBKDF2
+#   import os
+#
+#   salt = os.urandom(8)    # 64-bit salt
+#   key = PBKDF2("This passphrase is a secret.", salt).read(32) # 256-bit key
+#   iv = os.urandom(16)     # 128-bit IV
+#   cipher = AES.new(key, AES.MODE_CBC, iv)
+#     ...
+#
+# Sample crypt() usage:
+#   from PBKDF2 import crypt
+#   pwhash = crypt("secret")
+#   alleged_pw = raw_input("Enter password: ")
+#   if pwhash == crypt(alleged_pw, pwhash):
+#       print "Password good"
+#   else:
+#       print "Invalid password"
+#
+###########################################################################
+# History:
+#
+#  2007-07-27 Dwayne C. Litzenberger <dlitz@dlitz.net>
+#   - Initial Release (v1.0)
+#
+#  2007-07-31 Dwayne C. Litzenberger <dlitz@dlitz.net>
+#   - Bugfix release (v1.1)
+#   - SECURITY: The PyCrypto XOR cipher (used, if available, in the _strxor
+#   function in the previous release) silently truncates all keys to 64
+#   bytes.  The way it was used in the previous release, this would only be
+#   problem if the pseudorandom function that returned values larger than
+#   64 bytes (so SHA1, SHA256 and SHA512 are fine), but I don't like
+#   anything that silently reduces the security margin from what is
+#   expected.
+#
+###########################################################################
+
+__version__ = "1.1"
+
+from struct import pack
+from binascii import b2a_hex
+from base64 import b64encode
+from random import randint
+
+try:
+    # Use PyCrypto (if available)
+    from Crypto.Hash import HMAC, SHA as SHA1
+
+except ImportError:
+    # PyCrypto not available.  Use the Python standard library.
+    import hmac as HMAC
+    import sha as SHA1
+
+def strxor(a, b):
+    return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])
+
+class PBKDF2(object):
+    """PBKDF2.py : PKCS#5 v2.0 Password-Based Key Derivation
+    
+    This implementation takes a passphrase and a salt (and optionally an
+    iteration count, a digest module, and a MAC module) and provides a
+    file-like object from which an arbitrarily-sized key can be read.
+
+    If the passphrase and/or salt are unicode objects, they are encoded as
+    UTF-8 before they are processed.
+
+    The idea behind PBKDF2 is to derive a cryptographic key from a
+    passphrase and a salt.
+    
+    PBKDF2 may also be used as a strong salted password hash.  The
+    'crypt' function is provided for that purpose.
+    
+    Remember: Keys generated using PBKDF2 are only as strong as the
+    passphrases they are derived from.
+    """
+
+    def __init__(self, passphrase, salt, iterations=1000,
+                 digestmodule=SHA1, macmodule=HMAC):
+        self.__macmodule = macmodule
+        self.__digestmodule = digestmodule
+        self._setup(passphrase, salt, iterations, self._pseudorandom)
+
+    def _pseudorandom(self, key, msg):
+        """Pseudorandom function.  e.g. HMAC-SHA1"""
+        return self.__macmodule.new(key=key, msg=msg,
+            digestmod=self.__digestmodule).digest()
+    
+    def read(self, bytes):
+        """Read the specified number of key bytes."""
+        if self.closed:
+            raise ValueError("file-like object is closed")
+
+        size = len(self.__buf)
+        blocks = [self.__buf]
+        i = self.__blockNum
+        while size < bytes:
+            i += 1
+            if i > 0xffffffff:
+                # We could return "" here, but 
+                raise OverflowError("derived key too long")
+            block = self.__f(i)
+            blocks.append(block)
+            size += len(block)
+        buf = "".join(blocks)
+        retval = buf[:bytes]
+        self.__buf = buf[bytes:]
+        self.__blockNum = i
+        return retval
+    
+    def __f(self, i):
+        # i must fit within 32 bits
+        assert (1 <= i and i <= 0xffffffff)
+        U = self.__prf(self.__passphrase, self.__salt + pack("!L", i))
+        result = U
+        for j in xrange(2, 1+self.__iterations):
+            U = self.__prf(self.__passphrase, U)
+            result = strxor(result, U)
+        return result
+    
+    def hexread(self, octets):
+        """Read the specified number of octets. Return them as hexadecimal.
+
+        Note that len(obj.hexread(n)) == 2*n.
+        """
+        return b2a_hex(self.read(octets))
+
+    def _setup(self, passphrase, salt, iterations, prf):
+        # Sanity checks:
+        
+        # passphrase and salt must be str or unicode (in the latter
+        # case, we convert to UTF-8)
+        if isinstance(passphrase, unicode):
+            passphrase = passphrase.encode("UTF-8")
+        if not isinstance(passphrase, str):
+            raise TypeError("passphrase must be str or unicode")
+        if isinstance(salt, unicode):
+            salt = salt.encode("UTF-8")
+        if not isinstance(salt, str):
+            raise TypeError("salt must be str or unicode")
+
+        # iterations must be an integer >= 1
+        if not isinstance(iterations, (int, long)):
+            raise TypeError("iterations must be an integer")
+        if iterations < 1:
+            raise ValueError("iterations must be at least 1")
+        
+        # prf must be callable
+        if not callable(prf):
+            raise TypeError("prf must be callable")
+
+        self.__passphrase = passphrase
+        self.__salt = salt
+        self.__iterations = iterations
+        self.__prf = prf
+        self.__blockNum = 0
+        self.__buf = ""
+        self.closed = False
+    
+    def close(self):
+        """Close the stream."""
+        if not self.closed:
+            del self.__passphrase
+            del self.__salt
+            del self.__iterations
+            del self.__prf
+            del self.__blockNum
+            del self.__buf
+            self.closed = True
+
+def crypt(word, salt=None, iterations=None):
+    """PBKDF2-based unix crypt(3) replacement.
+    
+    The number of iterations specified in the salt overrides the 'iterations'
+    parameter.
+
+    The effective hash length is 192 bits.
+    """
+    
+    # Generate a (pseudo-)random salt if the user hasn't provided one.
+    if salt is None:
+        salt = _makesalt()
+
+    # salt must be a string or the us-ascii subset of unicode
+    if isinstance(salt, unicode):
+        salt = salt.encode("us-ascii")
+    if not isinstance(salt, str):
+        raise TypeError("salt must be a string")
+
+    # word must be a string or unicode (in the latter case, we convert to UTF-8)
+    if isinstance(word, unicode):
+        word = word.encode("UTF-8")
+    if not isinstance(word, str):
+        raise TypeError("word must be a string or unicode")
+
+    # Try to extract the real salt and iteration count from the salt
+    if salt.startswith("$p5k2$"):
+        (iterations, salt, dummy) = salt.split("$")[2:5]
+        if iterations == "":
+            iterations = 400
+        else:
+            converted = int(iterations, 16)
+            if iterations != "%x" % converted:  # lowercase hex, minimum digits
+                raise ValueError("Invalid salt")
+            iterations = converted
+            if not (iterations >= 1):
+                raise ValueError("Invalid salt")
+    
+    # Make sure the salt matches the allowed character set
+    allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
+    for ch in salt:
+        if ch not in allowed:
+            raise ValueError("Illegal character %r in salt" % (ch,))
+
+    if iterations is None or iterations == 400:
+        iterations = 400
+        salt = "$p5k2$$" + salt
+    else:
+        salt = "$p5k2$%x$%s" % (iterations, salt)
+    rawhash = PBKDF2(word, salt, iterations).read(24)
+    return salt + "$" + b64encode(rawhash, "./")
+
+# Add crypt as a static method of the PBKDF2 class
+# This makes it easier to do "from PBKDF2 import PBKDF2" and still use
+# crypt.
+PBKDF2.crypt = staticmethod(crypt)
+
+def _makesalt():
+    """Return a 48-bit pseudorandom salt for crypt().
+    
+    This function is not suitable for generating cryptographic secrets.
+    """
+    binarysalt = "".join([pack("@H", randint(0, 0xffff)) for i in range(3)])
+    return b64encode(binarysalt, "./")
+
+def test_pbkdf2():
+    """Module self-test"""
+    from binascii import a2b_hex
+    
+    #
+    # Test vectors from RFC 3962
+    #
+
+    # Test 1
+    result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1).read(16)
+    expected = a2b_hex("cdedb5281bb2f801565a1122b2563515")
+    if result != expected:
+        raise RuntimeError("self-test failed")
+
+    # Test 2
+    result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1200).hexread(32)
+    expected = ("5c08eb61fdf71e4e4ec3cf6ba1f5512b"
+                "a7e52ddbc5e5142f708a31e2e62b1e13")
+    if result != expected:
+        raise RuntimeError("self-test failed")
+
+    # Test 3
+    result = PBKDF2("X"*64, "pass phrase equals block size", 1200).hexread(32)
+    expected = ("139c30c0966bc32ba55fdbf212530ac9"
+                "c5ec59f1a452f5cc9ad940fea0598ed1")
+    if result != expected:
+        raise RuntimeError("self-test failed")
+    
+    # Test 4
+    result = PBKDF2("X"*65, "pass phrase exceeds block size", 1200).hexread(32)
+    expected = ("9ccad6d468770cd51b10e6a68721be61"
+                "1a8b4d282601db3b36be9246915ec82a")
+    if result != expected:
+        raise RuntimeError("self-test failed")
+    
+    #
+    # Other test vectors
+    #
+    
+    # Chunked read
+    f = PBKDF2("kickstart", "workbench", 256)
+    result = f.read(17)
+    result += f.read(17)
+    result += f.read(1)
+    result += f.read(2)
+    result += f.read(3)
+    expected = PBKDF2("kickstart", "workbench", 256).read(40)
+    if result != expected:
+        raise RuntimeError("self-test failed")
+    
+    #
+    # crypt() test vectors
+    #
+
+    # crypt 1
+    result = crypt("cloadm", "exec")
+    expected = '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql'
+    if result != expected:
+        raise RuntimeError("self-test failed")
+    
+    # crypt 2
+    result = crypt("gnu", '$p5k2$c$u9HvcT4d$.....')
+    expected = '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g'
+    if result != expected:
+        raise RuntimeError("self-test failed")
+
+    # crypt 3
+    result = crypt("dcl", "tUsch7fU", iterations=13)
+    expected = "$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL"
+    if result != expected:
+        raise RuntimeError("self-test failed")
+    
+    # crypt 4 (unicode)
+    result = crypt(u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2',
+        '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ')
+    expected = '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'
+    if result != expected:
+        raise RuntimeError("self-test failed")
+
+if __name__ == '__main__':
+    test_pbkdf2()
+
+# vim:set ts=4 sw=4 sts=4 expandtab:

beaker/crypto/__init__.py

+import os
+
+from Crypto.Hash import SHA256
+
+from PBKDF2 import PBKDF2, strxor
+
+def generateCryptoKeys(master_key, salt, iterations):
+    # NB: We XOR parts of the keystream into the randomly-generated parts, just
+    # in case os.urandom() isn't as random as it should be.  Note that if
+    # os.urandom() returns truly random data, this will have no effect on the
+    # overall security.
+    keystream = PBKDF2(master_key, salt, iterations=iterations, digestmodule=SHA256)
+    cipher_key = keystream.read(32)     # 256-bit AES key (for the payload)
+    cipher_nonce = strxor(keystream.read(8), os.urandom(8))     # 64-bit nonce
+    return (cipher_key, cipher_nonce)

beaker/middleware.py

+import sys
+
 try:
     from paste.registry import StackedObjectProxy
     beaker_session = StackedObjectProxy(name="Beaker Session")

beaker/session.py

 import hmac
 import md5
 import os
+import cPickle
 import random
 import re
 import sys
 except:
     beaker_session = None
 
-# Wrapped in case we're pre-Python 2.4
+# Determine if PyCrypto is available
+crypto_ok = False
 try:
-    import beaker.cerealizer as cerealizer
+    from Crypto.Cipher import AES
+    from Crypto.Hash import SHA256
+    import Crypto
+    if Crypto.__version__ >= '2.0.1':
+        crypto_ok = True
 except:
     pass
 
-from beaker.ciphersaber2 import encipher, decipher
+from beaker.crypto import generateCryptoKeys
+from beaker.crypto.CTRCipher import CTRCipher
+from beaker.crypto.PBKDF2 import strxor
 from beaker.container import namespace_registry
 from beaker.exceptions import BeakerException
 from beaker.util import coerce_session_params
         How long session data is considered valid. This is used 
         regardless of the cookie being present or not to determine
         whether session data is still valid.
-    ``secret``
-        The secret key to use for the session encryption.
+    ``encrypt_key``
+        The key to use for the session encryption.
+    ``validate_key``
+        The key used to sign the encrypted session
     ``cookie_domain``
         Domain to use for the cookie.
     
     
     """
     def __init__(self, request, key='beaker.session.id', timeout=None,
-                 cookie_expires=True, cookie_domain=None, secret=None, 
-                 **kwargs):
+                 cookie_expires=True, cookie_domain=None, encrypt_key=None,
+                 validate_key=None, **kwargs):
+        if not crypto_ok:
+            raise BeakerException("PyCrypto is not installed, can't use cookie-only Session.")
+        
         self.request = request
         self.key = key
         self.timeout = timeout
         self.cookie_expires = cookie_expires
         self.cookie_domain = cookie_domain
-        self.secret = secret
+        self.encrypt_key = encrypt_key
+        self.validate_key = validate_key
         self.request['set_cookie'] = False
         
         try:
         except KeyError:
             cookieheader = ''
         
-        if secret is None:
-            raise BeakerException("No secret specified for Cookie only Session.")
+        if encrypt_key is None:
+            raise BeakerException("No encrypt_key specified for Cookie only Session.")
+        if validate_key is None:
+            raise BeakerException("No validate_key specified for Cookie only Session.")
         
-        self.cookie = Cookie.SimpleCookie(input=cookieheader)
+        try:
+            self.cookie = SignedCookie(validate_key, input=cookieheader)
+        except Cookie.CookieError:
+            self.cookie = SignedCookie(validate_key, input=None)
         
         self.dict = {}
         self.is_new = True
         
         # If we have a cookie, load it
-        if self.key in self.cookie:
+        if self.key in self.cookie and self.cookie[self.key].value is not None:
             self.is_new = False
             try:
-                self.dict = self._decrypt_data(self.cookie[self.key].value)
-            except:
+                self.dict = self._decrypt_data()
+            except BeakerException:
                 self.dict = {}
+                log.debug("Unable to decrypt!")
             if self.timeout is not None and time.time() - self.dict['_accessed_time'] > self.timeout:
                 self.dict = {}
             self._create_cookie()
     
     created = property(lambda self: self.dict['_creation_time'])
-
+    
     def _encrypt_data(self):
         """Cerealize, encipher, and base64 the session dict"""
-        return base64.b64encode(
-            encipher(cerealizer.dumps(self.dict), self.secret))
+        encrypt_key, nonce = generateCryptoKeys(self.encrypt_key, self.validate_key, 1)
+        nonce = base64.b64encode(nonce)[:8]
+        ctrcipher = CTRCipher(encrypt_key, nonce, AES)
+        data = cPickle.dumps(self.dict, protocol=2)
+        return nonce + base64.b64encode(ctrcipher.encrypt(data))
     
-    def _decrypt_data(self, data):
+    def _decrypt_data(self):
         """Bas64, decipher, then un-cerealize the data for the session dict"""
-        return cerealizer.loads(decipher(base64.b64decode(data), self.secret))
+        nonce = self.cookie[self.key].value[:8]
+        encrypt_key, salt = generateCryptoKeys(self.encrypt_key, self.validate_key, 1)
+        ctrcipher = CTRCipher(encrypt_key, nonce, AES)
+        payload = base64.b64decode(self.cookie[self.key].value[8:])
+        data = ctrcipher.decrypt(payload)
+        return cPickle.loads(data.strip('\0'))
     
     def save(self):
         "saves the data for this session to persistent storage"
             self.dict['_creation_time'] = time.time()
         self.dict['_accessed_time'] = time.time()
         val = self._encrypt_data()
-        if len(val) > 4084:
+        if len(val) > 4064:
             raise BeakerException("Cookie value is too long to store")
         
         self.cookie[self.key] = val
         ('id', (str,), "Session id must be a string."),
         ('key', (str,), "Session key must be a string."),
         ('secret', (str, types.NoneType), "Session secret must be a string."),
+        ('validate_key', (str, types.NoneType), "Session encrypt_key must be a string."),
+        ('encrypt_key', (str, types.NoneType), "Session validate_key must be a string."),
         ('timeout', (int, types.NoneType), "Session timeout must be an integer."),
     ]
     return verify_rules(params, rules)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.