summaryrefslogtreecommitdiff
path: root/passlib
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2011-02-07 15:29:56 -0500
committerEli Collins <elic@assurancetechnologies.com>2011-02-07 15:29:56 -0500
commit05cb721a6acad2f54660d9da5c7c802a8d713021 (patch)
tree35ff7f106eae8bf2a3ab0ba53f596ac1f2b43249 /passlib
parentff21c20f41a3fe655825eb17d30d3f06eab6c72d (diff)
downloadpasslib-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__.py218
-rw-r--r--passlib/base.py95
-rw-r--r--passlib/default.cfg19
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)