From: Jeremy Stanley Date: Fri, 4 Mar 2011 14:41:23 +0000 (+0000) Subject: PEP 8 conformance for password management library X-Git-Tag: 0.0.1~292 X-Git-Url: https://mudpy.org/gitweb?p=mudpy.git;a=commitdiff_plain;h=c6c355717beeda5c40390d1f6fdcfb1e9807d171 PEP 8 conformance for password management library * lib/mudpy/password.py: Conform to the PEP 8 style guide. --- diff --git a/lib/mudpy/password.py b/lib/mudpy/password.py index 38bfa82..94d690b 100644 --- a/lib/mudpy/password.py +++ b/lib/mudpy/password.py @@ -1,186 +1,193 @@ # -*- coding: utf-8 -*- u"""Password hashing functions and constants for the mudpy engine.""" -# Copyright (c) 2004-2010 Jeremy Stanley . Permission +# Copyright (c) 2004-2011 Jeremy Stanley . Permission # to use, copy, modify, and distribute this software is granted under # terms provided in the LICENSE file distributed with this software. # convenience constants for indexing the supported hashing algorithms, # guaranteed a stable part of the interface -MD5 = 0 # hashlib.md5 -SHA1 = 1 # hashlib.sha1 -SHA224 = 2 # hashlib.sha224 -SHA256 = 3 # hashlib.sha256 -SHA384 = 4 # hashlib.sha385 -SHA512 = 5 # hashlib.sha512 +MD5 = 0 # hashlib.md5 +SHA1 = 1 # hashlib.sha1 +SHA224 = 2 # hashlib.sha224 +SHA256 = 3 # hashlib.sha256 +SHA384 = 4 # hashlib.sha385 +SHA512 = 5 # hashlib.sha512 + def _pack_bytes(numbers): - """ - This is a wrapper around struct.pack, used to turn a list of integers - between 0 and 255 into a packed sequence akin to a C-style string. - """ - import struct - # this will need to be declared as b"" during 2to3 migration - packed = "" - for number in numbers: - number = int(number) - assert 0 <= number <= 255 - # need to use b"B" during 2to3 migration - packed += struct.pack("B", number) - return packed + """ + This is a wrapper around struct.pack, used to turn a list of integers + between 0 and 255 into a packed sequence akin to a C-style string. + """ + import struct + # this will need to be declared as b"" during 2to3 migration + packed = "" + for number in numbers: + number = int(number) + assert 0 <= number <= 255 + # need to use b"B" during 2to3 migration + packed += struct.pack("B", number) + return packed + def _bytes_to_text(byte_sequence): - """ - This is a wrapper around base64.b64encode with preferences - appropriate for encoding Unix-style passwd hash strings. - """ - import base64 - return base64.b64encode( - byte_sequence, - u"./".encode(u"ascii") - ).rstrip(u"=") + """ + This is a wrapper around base64.b64encode with preferences + appropriate for encoding Unix-style passwd hash strings. + """ + import base64 + return base64.b64encode( + byte_sequence, + u"./".encode(u"ascii") + ).rstrip(u"=") + def _generate_salt(salt_len=2): - """ - This simply generates a sequence of pseudo-random characters (with - 6-bits of effective entropy per character). Since it relies on base64 - encoding (which operates on 6-bit chunks of data), we only generate - 0.75 times as many bytes (rounded up) as the number of characters we - need and discard any excess characters over the specified length. - This ensures full distribution over each character of the salt. - """ - import math, random - salt = [] - for i in xrange(int(math.ceil(salt_len*0.75))): - salt.append( random.randint(0,255) ) - return _bytes_to_text( _pack_bytes(salt) )[:salt_len] + """ + This simply generates a sequence of pseudo-random characters (with + 6-bits of effective entropy per character). Since it relies on base64 + encoding (which operates on 6-bit chunks of data), we only generate + 0.75 times as many bytes (rounded up) as the number of characters we + need and discard any excess characters over the specified length. + This ensures full distribution over each character of the salt. + """ + import math + import random + salt = [] + for i in xrange(int(math.ceil(salt_len * 0.75))): + salt.append(random.randint(0, 255)) + return _bytes_to_text(_pack_bytes(salt))[:salt_len] + def upgrade_legacy_hash(legacy_hash, salt, sep=u"$"): - """ - This utility function is meant to provide a migration path for users - of mudpy's legacy account-name-salted MD5 hexdigest password hashes. - By passing the old passhash (as legacy_hash) and name (as salt) - facets to this function, a conforming new-style password hash will be - returned. - """ - import re - assert re.match(u"^[0-9a-f]{32}$", legacy_hash), "Not a valid MD5 hexdigest" - # this needs to be declared as b"" in 2to3 - collapsed = "" - for i in xrange(16): - # this needs to become a byte() call in 2to3 - collapsed += chr( int(legacy_hash[2*i:2*i+2], 16) ) - return u"%s%s%s%s%s%s%s%s" % ( - sep, - MD5, - sep, - 0, # 2**0 provides one round of hashing - sep, - salt, - sep, - _bytes_to_text(collapsed) - ) + """ + This utility function is meant to provide a migration path for users + of mudpy's legacy account-name-salted MD5 hexdigest password hashes. + By passing the old passhash (as legacy_hash) and name (as salt) + facets to this function, a conforming new-style password hash will be + returned. + """ + import re + assert re.match(u"^[0-9a-f]{32}$", + legacy_hash), "Not a valid MD5 hexdigest" + # this needs to be declared as b"" in 2to3 + collapsed = "" + for i in xrange(16): + # this needs to become a byte() call in 2to3 + collapsed += chr(int(legacy_hash[2 * i:2 * i + 2], 16)) + return u"%s%s%s%s%s%s%s%s" % ( + sep, + MD5, + sep, + 0, # 2**0 provides one round of hashing + sep, + salt, + sep, + _bytes_to_text(collapsed) + ) + def create( - password, - salt=None, - algorithm=SHA1, - rounds=4, - salt_len=2, - sep=u"$" + password, + salt=None, + algorithm=SHA1, + rounds=4, + salt_len=2, + sep=u"$" ): - """ - The meat of the module, this function takes a provided password and - generates a Unix-like passwd hash suitable for storage in portable, - text-based data files. The password is prepended with a salt (which - can also be specified explicitly, if the output needs to be - repeatable) and then hashed with the requested algorithm iterated as - many times as 2 raised to the power of the rounds parameter. - - The first character of the text returned by this function denotes the - separator character used to identify subsequent fields. The fields in - order are: - - 1. the decimal index number indicating which algorithm was used, - also mapped as convenience constants at the beginning of this - module - - 2. the number of times (as an exponent of 2) which the algorithm - was iterated, represented by a decimal value between 0 and 16 - inclusive (0 results in one round, 16 results in 65536 rounds, - and anything higher than that is a potential resource - consumption denial of service on the application anyway) - - 3. the plain-text salt with which the password was prepended - before hashing - - 4. the resulting password hash itself, base64-encoded using . and - / as the two non-alpha-numeric characters required to reach 64 - - The defaults provided should be safe for everyday use, but something - more heavy-duty may be in order for admin users, such as:: - - create(password, algorithm=SHA256, rounds=12, salt_len=16) - """ - import hashlib - - # if a specific salt wasn't specified, we need to generate one - if not salt: - salt = _generate_salt(salt_len=salt_len) - - # make sure the algorithm index number is coerced into integer form, - # since it could also be passed as text (in decimal) for convenience - algorithm = int(algorithm) - - # the list of algorithms supported by this function corresponds to - # the convenience constants defined at the beginning of the module - algorithms = { - MD5: hashlib.md5, - SHA1: hashlib.sha1, - SHA224: hashlib.sha224, - SHA256: hashlib.sha256, - SHA384: hashlib.sha384, - SHA512: hashlib.sha512, - } - - # make sure the rounds exponent is coerced into integer form, since - # it could also be passed as text (in decimal) for convenience - rounds = int(rounds) - - # to avoid a potential resource consumption denial of service attack, - # only consider values in the range of 0-16 - assert 0 <= rounds <= 16 - - # here is where the salt is prepended to the provided password text - hashed = salt+password - - # iterate the hashing algorithm over its own digest the specified - # number of times - for i in xrange(2**rounds): - hashed = algorithms[algorithm](hashed).digest() - - # concatenate the output fields, coercing into text form as needed - return u"%s%s%s%s%s%s%s%s" % ( - sep, algorithm, sep, rounds, sep, salt, sep, _bytes_to_text(hashed) - ) + """ + The meat of the module, this function takes a provided password and + generates a Unix-like passwd hash suitable for storage in portable, + text-based data files. The password is prepended with a salt (which + can also be specified explicitly, if the output needs to be + repeatable) and then hashed with the requested algorithm iterated as + many times as 2 raised to the power of the rounds parameter. + + The first character of the text returned by this function denotes the + separator character used to identify subsequent fields. The fields in + order are: + + 1. the decimal index number indicating which algorithm was used, + also mapped as convenience constants at the beginning of this + module + + 2. the number of times (as an exponent of 2) which the algorithm + was iterated, represented by a decimal value between 0 and 16 + inclusive (0 results in one round, 16 results in 65536 rounds, + and anything higher than that is a potential resource + consumption denial of service on the application anyway) + + 3. the plain-text salt with which the password was prepended + before hashing + + 4. the resulting password hash itself, base64-encoded using . and + / as the two non-alpha-numeric characters required to reach 64 + + The defaults provided should be safe for everyday use, but something + more heavy-duty may be in order for admin users, such as:: + + create(password, algorithm=SHA256, rounds=12, salt_len=16) + """ + import hashlib + + # if a specific salt wasn't specified, we need to generate one + if not salt: + salt = _generate_salt(salt_len=salt_len) + + # make sure the algorithm index number is coerced into integer form, + # since it could also be passed as text (in decimal) for convenience + algorithm = int(algorithm) + + # the list of algorithms supported by this function corresponds to + # the convenience constants defined at the beginning of the module + algorithms = { + MD5: hashlib.md5, + SHA1: hashlib.sha1, + SHA224: hashlib.sha224, + SHA256: hashlib.sha256, + SHA384: hashlib.sha384, + SHA512: hashlib.sha512, + } + + # make sure the rounds exponent is coerced into integer form, since + # it could also be passed as text (in decimal) for convenience + rounds = int(rounds) + + # to avoid a potential resource consumption denial of service attack, + # only consider values in the range of 0-16 + assert 0 <= rounds <= 16 + + # here is where the salt is prepended to the provided password text + hashed = salt + password + + # iterate the hashing algorithm over its own digest the specified + # number of times + for i in xrange(2 ** rounds): + hashed = algorithms[algorithm](hashed).digest() + + # concatenate the output fields, coercing into text form as needed + return u"%s%s%s%s%s%s%s%s" % ( + sep, algorithm, sep, rounds, sep, salt, sep, _bytes_to_text(hashed) + ) -def verify(password, encoded_hash): - """ - This simple function requires a text password and a mudpy-format - password hash (as generated by the create function). It returns True - if the password, hashed with the parameters from the encoded_hash, - comes out the same as the encoded_hash. - """ - sep = encoded_hash[0] - algorithm, rounds, salt, hashed = encoded_hash[1:].split(sep) - if encoded_hash == create( - password=password, - salt=salt, - sep=sep, - algorithm=algorithm, - rounds=rounds - ): - return True - else: - return False +def verify(password, encoded_hash): + """ + This simple function requires a text password and a mudpy-format + password hash (as generated by the create function). It returns True + if the password, hashed with the parameters from the encoded_hash, + comes out the same as the encoded_hash. + """ + sep = encoded_hash[0] + algorithm, rounds, salt, hashed = encoded_hash[1:].split(sep) + if encoded_hash == create( + password=password, + salt=salt, + sep=sep, + algorithm=algorithm, + rounds=rounds + ): + return True + else: + return False