diff options
| author | Eli Collins <elic@assurancetechnologies.com> | 2011-01-31 23:34:37 +0000 |
|---|---|---|
| committer | Eli Collins <elic@assurancetechnologies.com> | 2011-01-31 23:34:37 +0000 |
| commit | 0eafd59fcee89cf4769add43c057a11e62c49a3a (patch) | |
| tree | 19b476c6317f76ce39140900d1d671440aef34ae /passlib | |
| parent | 8903ccd8ecf8b26cd5e80466eecfdd8df140482b (diff) | |
| download | passlib-0eafd59fcee89cf4769add43c057a11e62c49a3a.tar.gz | |
updated docs
Diffstat (limited to 'passlib')
| -rw-r--r-- | passlib/base.py | 10 | ||||
| -rw-r--r-- | passlib/hash/apr_md5_crypt.py | 13 | ||||
| -rw-r--r-- | passlib/hash/postgres_md5.py | 24 | ||||
| -rw-r--r-- | passlib/utils/__init__.py | 64 | ||||
| -rw-r--r-- | passlib/utils/des.py | 14 | ||||
| -rw-r--r-- | passlib/utils/h64.py | 61 | ||||
| -rw-r--r-- | passlib/utils/handlers.py | 8 | ||||
| -rw-r--r-- | passlib/utils/md4.py | 23 |
8 files changed, 82 insertions, 135 deletions
diff --git a/passlib/base.py b/passlib/base.py index 09e5f89..53d48d4 100644 --- a/passlib/base.py +++ b/passlib/base.py @@ -21,10 +21,11 @@ import hashlib import logging; log = logging.getLogger(__name__) import time import os +from warnings import warn #site #libs import passlib.hash as _hmod -from passlib.utils import abstract_class_method, Undef, is_crypt_handler, splitcomma +from passlib.utils import abstractclassmethod, Undef, is_crypt_handler, splitcomma #pkg #local __all__ = [ @@ -85,8 +86,11 @@ def get_crypt_handler(name, default=Undef): "resolve crypt algorithm name" global _hmod - ###normalize name - ##name = name.replace("-","_").lower() + #normalize name + alt = name.replace("-","_").lower() + if alt != name: + warn("handler names be lower-case, and use underscores instead of hyphens: %r => %r" % (name, alt)) + name = alt #check if handler loaded handler = getattr(_hmod, name, None) diff --git a/passlib/hash/apr_md5_crypt.py b/passlib/hash/apr_md5_crypt.py index 3c1932c..5eb45fa 100644 --- a/passlib/hash/apr_md5_crypt.py +++ b/passlib/hash/apr_md5_crypt.py @@ -1,15 +1,4 @@ -"""passlib.hash.apr_md5_crypt - Apache variant of md5-crypt algorithm - -This format is primarily used by Apache in htpasswd files. - -.. note:: - This format would be identical to md5-crypt, - except for two things: it uses ``$apr1$`` as it's prefix - when encoded, and inserts that constant into the hash calculation - where md5-crypt would insert ``$1$``. - Thus, the formats aren't compatible, nor the checksums they contain. - Other than that, they have identical levels of security. -""" +"""passlib.hash.apr_md5_crypt - Apache variant of md5-crypt algorithm""" #========================================================= #imports #========================================================= diff --git a/passlib/hash/postgres_md5.py b/passlib/hash/postgres_md5.py index 0efefe6..faa27e5 100644 --- a/passlib/hash/postgres_md5.py +++ b/passlib/hash/postgres_md5.py @@ -1,26 +1,4 @@ -"""passlib.hash.postgres_md5 - MD5-based algorithm used by Postgres for pg_shadow table - -This implements the md5-based hash algorithm used by Postgres to store -passwords in the pg_shadow table. - -This algorithm shouldn't be used for any purpose besides Postgres interaction, -it's a weak unsalted algorithm which could be attacked with a rainbow table -built against common user names. - -.. warning:: - This algorithm is slightly different from most of the others, - in that both encrypt() and verify() require you pass in - the name of the user account via the required 'user' keyword, - since postgres uses this in place of a salt :( - -Usage Example:: - - >>> from passlib.hash import postgres_md5 as pm - >>> pm.encrypt("mypass", user="postgres") - 'md55fba2ea04fd36069d2574ea71c8efe9d' - >>> pm.verify("mypass", 'md55fba2ea04fd36069d2574ea71c8efe9d', user="postgres") - True -""" +"""passlib.hash.postgres_md5 - MD5-based algorithm used by Postgres for pg_shadow table""" #========================================================= #imports #========================================================= diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py index 37b25b2..0a88df9 100644 --- a/passlib/utils/__init__.py +++ b/passlib/utils/__init__.py @@ -20,7 +20,7 @@ __all__ = [ #decorators "classproperty", "abstractmethod", - "abstract_class_property", + "abstractclassmethod", #byte manipulation "bytes_to_list", @@ -36,7 +36,7 @@ __all__ = [ #decorators #================================================================================= class classproperty(object): - """Decorator which acts like a combination of classmethod+property (limited to read-only)""" + """Function decorator which acts like a combination of classmethod+property (limited to read-only properties)""" def __init__(self, func): self.im_func = func @@ -57,9 +57,9 @@ def abstractmethod(func): update_wrapper(wrapper, func) return wrapper -def abstract_class_method(func): +def abstractclassmethod(func): """Class Method decorator which indicates this is a placeholder method which - should be overridden by subclass. + should be overridden by subclass, and must be a classmethod. If called directly, this method will raise an :exc:`NotImplementedError`. """ @@ -76,7 +76,7 @@ Undef = object() #singleton used as default kwd value in some functions #protocol helpers #========================================================== def is_crypt_handler(obj): - "check if obj follows CryptHandler api" + "check if object follows the :ref:`crypt handler api <crypt-handler-api>`" return all(hasattr(obj, name) for name in ( "name", "setting_kwds", "context_kwds", @@ -96,7 +96,7 @@ def is_crypt_handler(obj): #string helpers #================================================================================= def splitcomma(source): - "split comma separated list into elements, stripping whitespace" + "split comma separated string into list elements, stripping whitespace and empty elements" return [ elem.strip() for elem in source.split(",") @@ -143,14 +143,14 @@ def splitcomma(source): ## return out def bytes_to_int(value): - "decode bytes as single big-endian integer" + "decode string of bytes as single big-endian integer" out = 0 for v in value: out = (out<<8) | ord(v) return out def int_to_bytes(value, count): - "encode integer into single big-endian byte string" + "encodes integer into single big-endian byte string" assert value < (1<<(8*count)), "value too large for %d bytes: %d" % (count, value) return ''.join( chr((value>>s) & 0xff) @@ -161,8 +161,8 @@ def int_to_bytes(value, count): def list_to_bytes(value, bytes=None, order="big"): """Returns a multi-character string corresponding to a list of byte values. - This is similar to :func:`int_to_bytes`, except that this a list of integers - instead of a single encoded integer. + This is similar to :func:`int_to_bytes`, except that this returns a list + of integers, where each integer corresponds to a single byte of the input. :arg value: The list of integers to encode. @@ -183,7 +183,7 @@ def list_to_bytes(value, bytes=None, order="big"): Usage Example:: - >>> from passlib.util import list_to_bytes, bytes_to_list + >>> from passlib.utils import list_to_bytes, bytes_to_list >>> list_to_bytes([4, 210], 4) '\\x00\\x00\\x04\\xd2' @@ -253,7 +253,7 @@ def bytes_to_list(value, order="big"): _join = "".join def xor_bytes(left, right): - "bitwise-xor two byte-strings together" + "perform bitwise-xor of two byte-strings" return _join(chr(ord(l) ^ ord(r)) for l, r in zip(left, right)) #================================================================================= @@ -308,10 +308,10 @@ rng = random.Random(genseed()) #----------------------------------------------------------------------- def getrandbytes(rng, count): - """return string of *count* number of random bytes, using specified rng""" + """return byte-string containing *count* number of randomly generated bytes, using specified rng""" #NOTE: would be nice if this was present in stdlib Random class - ###just in case rng provides this (eg our SystemRandom subclass above)... + ###just in case rng provides this... ##meth = getattr(rng, "getrandbytes", None) ##if meth: ## return meth(count) @@ -324,23 +324,23 @@ def getrandbytes(rng, count): value //= 0xff return buf.getvalue() -def getrandstr(rng, alphabet, count): - """return string of *size* number of chars, whose elements are drawn from specified alphabet""" +def getrandstr(rng, charset, count): + """return character string containg *count* number of chars, whose elements are drawn from specified charset, using specified rng""" #check alphabet & count if count < 0: raise ValueError, "count must be >= 0" - letters = len(alphabet) + letters = len(charset) if letters == 0: raise ValueError, "alphabet must not be empty" if letters == 1: - return alphabet * count + return charset * count #get random value, and write out to buffer #XXX: break into chunks for large number of letters? value = rng.randrange(0, letters**count) buf = StringIO() for i in xrange(count): - buf.write(alphabet[value % letters]) + buf.write(charset[value % letters]) value //= letters assert value == 0 return buf.getvalue() @@ -374,6 +374,7 @@ def norm_rounds(rounds, default_rounds, min_rounds, max_rounds, name="this crypt return rounds def gen_salt(count, charset=h64.CHARS): + "generate salt string of *count* chars using specified *charset*" global rng return getrandstr(rng, charset, count) @@ -389,11 +390,12 @@ def norm_salt(salt, min_chars, max_chars=None, charset=h64.CHARS, gen_charset=No :arg salt: user-provided salt :arg min_chars: minimum number of chars in salt :arg max_chars: maximum number of chars in salt (if omitted, same as min_chars) - :param charset: character set that salt MUST be subset of + :param charset: character set that salt MUST be subset of (defaults to :) :param gen_charset: optional character set to restrict to when generating new salts (defaults to charset) :param name: optional name of handler, for inserting into error messages :raises ValueError: + * if salt contains chars that aren't in salt_charset. * if salt contains less than min_salt_chars characters. @@ -422,27 +424,5 @@ def norm_salt(salt, min_chars, max_chars=None, charset=h64.CHARS, gen_charset=No return salt #================================================================================= -#errors -#================================================================================= - -###NOTE: not all handlers will raise these errors, -### the only thing currently guaranteed is that they -### will -## -##class HandlerError(ValueError): -## "helper class for various errors used by some CryptHandlers" -## message = None -## -## def __init__(self, msg=None): -## ValueError.__init__(self, msg or self.message) -## -##class NoChecksumError(HandlerError): -## "helper raised by CryptHandler.verify() when config string passed in instead of hash" -## #helper for common message raised by handlers -## -## message = "hash lacks checksum (did you pass a config string into verify?)" - - -#================================================================================= #eof #================================================================================= diff --git a/passlib/utils/des.py b/passlib/utils/des.py index 0851bb3..aea62d7 100644 --- a/passlib/utils/des.py +++ b/passlib/utils/des.py @@ -1,17 +1,3 @@ -"""passlib.utils.des -- DES encryption routines - -This module contains routines for encrypting blocks of data using the DES algorithm. - -They do not support multi-block operation or decryption, -since they are designed for use in password hash algorithms -such as ``lmhash`` and ``des-crypt``. - -.. function:: expand_des_key -.. function:: des_encrypt_block -.. function:: mdes_encrypt_int_block - -""" - """ History ======= diff --git a/passlib/utils/h64.py b/passlib/utils/h64.py index 9e91cb7..4d472c2 100644 --- a/passlib/utils/h64.py +++ b/passlib/utils/h64.py @@ -1,22 +1,4 @@ -"""passlib.utils.h64 - hash64 encoding helpers - -many of the password hash algorithms in passlib -use a encoding scheme very similar to, but not compatible with, -the standard base64 encoding scheme. the main differences are that -it uses ``.`` instead of ``+``, and assigns the -characters *completely* different numeric values. - -this encoding system appears to have originated with des-crypt hash, -but is used by md5-crypt, sha-256-crypt, and others. -within passlib, this encoding is referred as ``hash64`` encoding, -and this module contains various utilities functions for encoding -and decoding strings in that format. - -.. note:: - It may *look* like bcrypt uses this scheme, - when in fact bcrypt uses the standard base64 encoding scheme, - but with ``+`` replaced with ``.``. -""" +"""passlib.utils.h64 - hash64 encoding helpers""" #================================================================================= #imports #================================================================================= @@ -34,10 +16,9 @@ __all__ = [ "encode_2_offsets", "encode_1_offset", - "decode_int12", + "decode_int12", "encode_int12" "decode_int24", "encode_int24", - "encode_int64", - + "decode_int64", "encode_int64", ] #================================================================================= @@ -93,17 +74,17 @@ def encode_1_offset(buffer, o1): # int <-> b64 string, used by des_crypt, ext_des_crypt #================================================================================= -##def decode_int(value): -## "decode hash-64 format used by crypt into integer" -## #FORMAT: little-endian, each char contributes 6 bits, -## # char value = index in H64_CHARS string -## try: -## out = 0 -## for c in reversed(value): -## out = (out<<6) + b64_decode_6bit(c) -## return out -## except KeyError: -## raise ValueError, "invalid character in string" +def decode_int(value): + "decode hash-64 format used by crypt into integer" + #FORMAT: little-endian, each char contributes 6 bits, + # char value = index in H64_CHARS string + try: + out = 0 + for c in reversed(value): + out = (out<<6) + b64_decode_6bit(c) + return out + except KeyError: + raise ValueError, "invalid character in string" def decode_int12(value): "decode 2 chars of hash-64 format used by crypt, returning 12-bit integer" @@ -112,8 +93,12 @@ def decode_int12(value): except KeyError: raise ValueError, "invalid character" +def encode_int12(value): + "encode 2 chars of hash-64 format from a 12-bit integer" + return encode_6bit(value & 0x3f) + encode_6bit((value>>6) & 0x3f) + def decode_int24(value): - "decode 4 chars of hash-64 format used by crypt, returning 24-bit integer" + "decode 4 chars of hash-64 format, returning 24-bit integer" try: return decode_6bit(value[0]) +\ (decode_6bit(value[1])<<6)+\ @@ -123,7 +108,7 @@ def decode_int24(value): raise ValueError, "invalid character" def encode_int24(value): - "decode 2 chars of hash-64 format used by crypt, returning 12-bit integer" + "encode 4 chars of hash-64 format from a 24-bit integer" return encode_6bit(value & 0x3f) + \ encode_6bit((value>>6) & 0x3f) + \ encode_6bit((value>>12) & 0x3f) + \ @@ -131,8 +116,12 @@ def encode_int24(value): _RR9_1 = range(9,-1,-1) +def decode_int64(value): + "decode 64-bit integer from 11 chars of hash-64 format" + return decode_int(value) + def encode_int64(value): - "encode 64-bit integer to hash-64 format used by crypt, returning 11 chars" + "encode 64-bit integer to hash-64 format, returning 11 chars" out = [None] * 10 + [ encode_6bit((value<<2)&0x3f) ] value >>= 4 for i in _RR9_1: diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py index 6c09375..8e9fe1f 100644 --- a/passlib/utils/handlers.py +++ b/passlib/utils/handlers.py @@ -12,7 +12,7 @@ import time import os #site #libs -from passlib.utils import abstract_class_method, classproperty, h64, \ +from passlib.utils import abstractclassmethod, classproperty, h64, \ getrandstr, rng, Undef, is_crypt_handler #pkg #local @@ -67,7 +67,7 @@ class CryptHandler(object): #primary interface - primary methods implemented by each handler #========================================================= - @abstract_class_method + @abstractclassmethod def genhash(cls, secret, config, **context): """encrypt secret to hash @@ -355,7 +355,7 @@ class CryptHandler(object): ## #backend parsing routines - used by helpers below ## #========================================================= ## -## @abstract_class_method +## @abstractclassmethod ## def parse(cls, hash): ## """parse hash or config into dictionary. ## @@ -388,7 +388,7 @@ class CryptHandler(object): ## verify() method can work properly. ## """ ## -## @abstract_class_method +## @abstractclassmethod ## def render(cls, checksum=None, **settings): ## """render hash from checksum & settings (as returned by :meth:`parse`). ## diff --git a/passlib/utils/md4.py b/passlib/utils/md4.py index 550eff8..f01ecb0 100644 --- a/passlib/utils/md4.py +++ b/passlib/utils/md4.py @@ -24,7 +24,28 @@ def new(content=None): return md4(content) class md4(object): - "md4 hash algorithm" + """pep-247 compatible implementation of MD4 hash algorithm + + .. attribute:: digest_size + + size of md4 digest in bytes (16 bytes) + + .. method:: update + + update digest by appending additional content + + .. method:: copy + + create clone of digest object, including current state + + .. method:: digest + + return bytes representing md4 digest of current content + + .. method:: hexdigest + + return hexdecimal version of digest + """ #FIXME: make this follow hash object PEP better. #FIXME: this isn't threadsafe #XXX: should we monkeypatch ourselves into hashlib for general use? probably wouldn't be nice. |
