diff options
| author | Eli Collins <elic@assurancetechnologies.com> | 2011-02-07 15:29:56 -0500 |
|---|---|---|
| committer | Eli Collins <elic@assurancetechnologies.com> | 2011-02-07 15:29:56 -0500 |
| commit | 05cb721a6acad2f54660d9da5c7c802a8d713021 (patch) | |
| tree | 35ff7f106eae8bf2a3ab0ba53f596ac1f2b43249 /passlib | |
| parent | ff21c20f41a3fe655825eb17d30d3f06eab6c72d (diff) | |
| download | passlib-05cb721a6acad2f54660d9da5c7c802a8d713021.tar.gz | |
CryptPolicy work
================
* (temporarily?) commented out frontend methods in toplevel passlib module
* bugfixes to CryptPolicy
* added min_verify_time to CryptPolicy
* added default policy config file
Diffstat (limited to 'passlib')
| -rw-r--r-- | passlib/__init__.py | 218 | ||||
| -rw-r--r-- | passlib/base.py | 95 | ||||
| -rw-r--r-- | passlib/default.cfg | 19 |
3 files changed, 210 insertions, 122 deletions
diff --git a/passlib/__init__.py b/passlib/__init__.py index 5004ee7..4ccc8fa 100644 --- a/passlib/__init__.py +++ b/passlib/__init__.py @@ -5,119 +5,119 @@ __version__ = "1.3" #========================================================= # #========================================================= -from passlib.base import CryptContext +##from passlib.base import CryptContext #========================================================= #quickstart interface #========================================================= -from passlib.unix import default_context - -def identify(hash, name=True): - """Identify algorithm which generated a password hash. - - :arg hash: - The hash string to identify. - :param name: - If ``True``, this function will return a name identifying the hash algorithm (the default). - If ``False``, it will return the handler object associated with that algorithm. - - The following algorithms are currently recognized: - - =================== ================================================ - Name Description - ------------------- ------------------------------------------------ - ``"des-crypt"`` the historical unix crypt algorithm based on DES - - ``"md5-crypt"`` the md5-crypt algorithm, usually identified - by the prefix ``$1$`` in unix shadow files. - - ``"bcrypt"`` the openbsd blowfish-crypt algorithm, - usually identified by the prefixes ``$2$`` or ``$2a$`` - in unix shadow files. - - ``"sha256-crypt"`` the 256-bit version of the sha-crypt algorithm, - usually identified by the prefix ``$5$`` - in unix shadow files. - - ``"sha512-crypt"`` the 512-bit version of the sha-crypt algorithm, - usually identified by the prefix ``$6$`` - in unix shadow files. - =================== ================================================ - - :returns: - The name of the hash, or ``None`` if the hash could not be identified. - (The return may be altered by the *resolve* keyword). - - .. note:: - This is a convience wrapper for ``pwhash.default_context.identify(hash)``. - """ - return default_context.identify(hash, name=name) - -def encrypt(secret, alg=None, **kwds): - """Encrypt secret using a password hash algorithm. - - :type secret: str - :arg secret: - String containing the secret to encrypt - - :type alg: str|None - :param alg: - Optionally specify the name of the algorithm to use. - If no algorithm is specified, an attempt is made - to guess from the hash string. If no hash string - is specified, sha512-crypt will be used. - See :func:`identify` for a list of algorithm names. - - All other keywords are passed on to the specific password algorithm - being used to encrypt the secret. - - :type rounds: int - :param rounds: - For the sha256-crypt and sha512-crypt algorithms, - this option lets you specify the number of rounds - of encryption to use. For the bcrypt algorithm, - this option lets you specify the log-base-2 of - the number of rounds of encryption to use. - - For all three of these algorithms, you can either - specify a positive integer, or one of the strings - "fast", "medium", "slow" to choose a preset number - of rounds corresponding to an appropriate level - of encryption. - - :returns: - The secret as encoded by the specified algorithm and options. - """ - return default_context.encrypt(secret, alg=alg, **kwds) - -def verify(secret, hash, alg=None): - """verify a secret against an existing hash. - - This checks if a secret matches against the one stored - inside the specified hash. By default this uses :func:`encrypt` - to re-crypt the secret, and compares it to the provided hash; - though some algorithms may implement this in a more efficient manner. - - :type secret: str - :arg secret: - A string containing the secret to check. - - :type hash: str - :param hash: - A string containing the hash to check against. - - :type alg: str|None - :param alg: - Optionally specify the name of the algorithm to use. - If no algorithm is specified, an attempt is made - to guess from the hash string. If it can't be - identified, a ValueError will be raised. - See :func:`identify` for a list of algorithm names. - - :returns: - ``True`` if the secret matches, otherwise ``False``. - """ - return default_context.verify(secret, hash, alg=alg) +##from passlib.unix import default_context +## +##def identify(hash, name=True): +## """Identify algorithm which generated a password hash. +## +## :arg hash: +## The hash string to identify. +## :param name: +## If ``True``, this function will return a name identifying the hash algorithm (the default). +## If ``False``, it will return the handler object associated with that algorithm. +## +## The following algorithms are currently recognized: +## +## =================== ================================================ +## Name Description +## ------------------- ------------------------------------------------ +## ``"des-crypt"`` the historical unix crypt algorithm based on DES +## +## ``"md5-crypt"`` the md5-crypt algorithm, usually identified +## by the prefix ``$1$`` in unix shadow files. +## +## ``"bcrypt"`` the openbsd blowfish-crypt algorithm, +## usually identified by the prefixes ``$2$`` or ``$2a$`` +## in unix shadow files. +## +## ``"sha256-crypt"`` the 256-bit version of the sha-crypt algorithm, +## usually identified by the prefix ``$5$`` +## in unix shadow files. +## +## ``"sha512-crypt"`` the 512-bit version of the sha-crypt algorithm, +## usually identified by the prefix ``$6$`` +## in unix shadow files. +## =================== ================================================ +## +## :returns: +## The name of the hash, or ``None`` if the hash could not be identified. +## (The return may be altered by the *resolve* keyword). +## +## .. note:: +## This is a convience wrapper for ``pwhash.default_context.identify(hash)``. +## """ +## return default_context.identify(hash, name=name) +## +##def encrypt(secret, alg=None, **kwds): +## """Encrypt secret using a password hash algorithm. +## +## :type secret: str +## :arg secret: +## String containing the secret to encrypt +## +## :type alg: str|None +## :param alg: +## Optionally specify the name of the algorithm to use. +## If no algorithm is specified, an attempt is made +## to guess from the hash string. If no hash string +## is specified, sha512-crypt will be used. +## See :func:`identify` for a list of algorithm names. +## +## All other keywords are passed on to the specific password algorithm +## being used to encrypt the secret. +## +## :type rounds: int +## :param rounds: +## For the sha256-crypt and sha512-crypt algorithms, +## this option lets you specify the number of rounds +## of encryption to use. For the bcrypt algorithm, +## this option lets you specify the log-base-2 of +## the number of rounds of encryption to use. +## +## For all three of these algorithms, you can either +## specify a positive integer, or one of the strings +## "fast", "medium", "slow" to choose a preset number +## of rounds corresponding to an appropriate level +## of encryption. +## +## :returns: +## The secret as encoded by the specified algorithm and options. +## """ +## return default_context.encrypt(secret, alg=alg, **kwds) +## +##def verify(secret, hash, alg=None): +## """verify a secret against an existing hash. +## +## This checks if a secret matches against the one stored +## inside the specified hash. By default this uses :func:`encrypt` +## to re-crypt the secret, and compares it to the provided hash; +## though some algorithms may implement this in a more efficient manner. +## +## :type secret: str +## :arg secret: +## A string containing the secret to check. +## +## :type hash: str +## :param hash: +## A string containing the hash to check against. +## +## :type alg: str|None +## :param alg: +## Optionally specify the name of the algorithm to use. +## If no algorithm is specified, an attempt is made +## to guess from the hash string. If it can't be +## identified, a ValueError will be raised. +## See :func:`identify` for a list of algorithm names. +## +## :returns: +## ``True`` if the secret matches, otherwise ``False``. +## """ +## return default_context.verify(secret, hash, alg=alg) #========================================================= #eof diff --git a/passlib/base.py b/passlib/base.py index 7dd8d5b..5724af2 100644 --- a/passlib/base.py +++ b/passlib/base.py @@ -14,6 +14,7 @@ Many schemes support their own options, such as min/max/default rounds. #========================================================= from __future__ import with_statement #core +from cStringIO import StringIO from ConfigParser import ConfigParser import inspect import re @@ -23,6 +24,7 @@ import time import os from warnings import warn #site +from pkg_resources import resource_string #libs import passlib.hash as _hmod from passlib.utils import abstractclassmethod, Undef, is_crypt_handler, splitcomma, rng @@ -130,13 +132,13 @@ def parse_policy_key(key): ##if isinstance(k, tuple) and len(k) == 3: ## cat, name, opt = k ##else: - orig = k - if '/' in k: #legacy format - k = k.replace("/",".") - elif '.' not in k and '__' in k: #lets user specifiy programmatically (since python doesn't allow '.') - k = k.replace("__", ".") - k = k.replace(" ","").replace("\t","") #strip out all whitespace from key - parts = k.split(".") + orig = key + if '/' in key: #legacy format + key = key.replace("/",".") + elif '.' not in key and '__' in key: #lets user specifiy programmatically (since python doesn't allow '.') + key = key.replace("__", ".") + key = key.replace(" ","").replace("\t","") #strip out all whitespace from key + parts = key.split(".") if len(parts) == 1: cat = None name = "context" @@ -166,8 +168,11 @@ def parse_policy_value(cat, name, opt, value): return set(splitcomma(value)) elif isinstance(value, (list,tuple)): return set(value) + elif opt == "min_verify_time": + return float(value) return value else: + #try to coerce everything to int try: return int(value) except ValueError: @@ -180,7 +185,7 @@ class CryptPolicy(object): #class methods #========================================================= @classmethod - def from_file(cls, path, section="passlib"): + def from_path(cls, path, section="passlib"): "create new policy from specified section of an ini file" p = ConfigParser() if not p.read([path]): @@ -188,13 +193,47 @@ class CryptPolicy(object): return cls(**dict(p.items(section))) @classmethod + def from_string(cls, source, section="passlib"): + p = ConfigParser() + b = StringIO(source) + p.readfp(b) + return cls(**dict(p.items(section))) + + @classmethod + def from_source(cls, source): + "helper which accepts CryptPolicy, filepath, raw string, and returns policy" + if isinstance(source, CryptPolicy): + return source + if '\n' in source: + return cls.from_string(source) + else: + return cls.from_path(source) + + @classmethod def from_sources(cls, sources): "create new policy from list of existing policy object" + if len(sources) == 0: + raise ValueError, "no sources specified" + first = sources[0] + if len(sources) == 1: + return CryptPolicy.from_source(first) + if isinstance(first, CryptPolicy): + target = CryptPolicy() + else: + sources = sources[1:] + target = cls.from_source(first) raise NotImplementedError + ##for source in sources: + ## source = cls.from_source(source) + ## #TODO: merge handlers. + ## target._fallback.update(source._fallback) #FIXME: do we want to override explicitly chosen fallbacks w/ default ones? + ## target._deprecated = set(source._deprecated) + return target #========================================================= #instance attrs #========================================================= + #NOTE: all category dictionaries below will have a minimum of 'None' as a key #:list of all handlers, in order they will be checked when identifying (reverse of order specified) _handlers = None #list of password hash handlers instances. @@ -205,6 +244,9 @@ class CryptPolicy(object): #:dict mapping category -> set of handler names which are deprecated for that category _deprecated = None + #:dict mapping category -> min verify time + _min_verify_time = None + #:dict mapping category -> dict mapping hash name -> dict of options for that hash # if a category is specified, particular hash names will be mapped ONLY if that category # has options which differ from the default options. @@ -237,9 +279,12 @@ class CryptPolicy(object): raise KeyError, "'salt' option is not allowed to be set via a policy object" #NOTE: doing this for security purposes, why would you ever want a fixed salt? v = parse_policy_value(cat, name, opt, v) - config = options.get(name) + copts = options.get(cat) + if copts is None: + copts = options[cat] = {} + config = copts.get(name) if config is None: - options[name] = {opt:v} + copts[name] = {opt:v} else: config[opt] = v @@ -272,6 +317,7 @@ class CryptPolicy(object): # dmap = self._deprecated = {} fmap = self._fallback = {} + mvmap = self._min_verify_time = {} for cat, config in options.iteritems(): kwds = config.pop("context", None) if not kwds: @@ -287,10 +333,15 @@ class CryptPolicy(object): if fb not in seen: raise ValueError, "unspecified scheme set as fallback: %r" % (fb,) fmap[cat] = self.lookup(fb, required=True) + value = kwds.get("min_verify_time") + if value: + mvmap[cat] = value if None not in dmap: dmap[None] = set() if None not in fmap and handlers: fmap[None] = handlers[0] + if None not in mvmap: + mvmap[None] = 0 #========================================================= #public interface (used by CryptContext) @@ -352,6 +403,12 @@ class CryptPolicy(object): return name in dmap[category] return name in dmap[None] + def get_min_verify_time(self, category=None): + mvmap = self._min_verify_time + if category and category in mvmap: + return mvmap[category] + return mvmap[None] + #========================================================= #serialization #========================================================= @@ -394,8 +451,8 @@ class CryptPolicy(object): #eoc #========================================================= - -default_policy = CryptPolicy() +##default_policy = CryptPolicy.from_string(resource_string("passlib", "default.cfg")) +default_policy = None #========================================================= # @@ -640,6 +697,10 @@ class CryptContext(object): if hash is None: return False + mvt = self.policy.get_min_verify_time(category) + if mvt: + start = time.time() + #locate handler if scheme: handler = self.lookup(scheme, required=True) @@ -652,7 +713,15 @@ class CryptContext(object): ## del context[k] #use handler to verify secret - return handler.verify(secret, hash, **context) + result = handler.verify(secret, hash, **context) + + if mvt: + #delta some amount of time if verify took less than mvt seconds + delta = time.time() - start - mvt + if delta > 0: + time.sleep(delta) + + return result #========================================================= #eoc diff --git a/passlib/default.cfg b/passlib/default.cfg new file mode 100644 index 0000000..492f66a --- /dev/null +++ b/passlib/default.cfg @@ -0,0 +1,19 @@ +[passlib] +# +# this is the PassLib default policy configuration, used by CryptContext +# objects which don't have an explicit base policy specified. +# the goal of this default configuration is not to set any preferred schemes, +# but provide sane defaults (eg rounds) for all the supported algorithms. +# + +#TODO: need to generate min rounds for specific cpu speed & verify time limitations + +ext_des_crypt.min_rounds = 30000 +phpass.min_rounds = 10 +bcrypt.min_rounds = 10 +sha256_crypt.min_rounds = 30000 +sha512_crypt.min_rounds = 30000 +sun_md5_crypt.min_rounds = 30000 +sha1_crypt.min_rounds = 30000 + +#TODO: specify default configuration for unix systems (mainly, deprecating des-crypt, ext-des-crypt) |
