X-Git-Url: https://mudpy.org/gitweb?p=mudpy.git;a=blobdiff_plain;f=lib%2Fmudpy%2Fpassword.py;h=a34dab9438f36b370b98596fe2548338e83d266f;hp=94d690bd49bbb420df7c0c25cac13ebd235e4cfe;hb=70d0f64a6079d83f8937a51c23d4c721cbb6672c;hpb=c6c355717beeda5c40390d1f6fdcfb1e9807d171 diff --git a/lib/mudpy/password.py b/lib/mudpy/password.py index 94d690b..a34dab9 100644 --- a/lib/mudpy/password.py +++ b/lib/mudpy/password.py @@ -1,193 +1,19 @@ -# -*- coding: utf-8 -*- -u"""Password hashing functions and constants for the mudpy engine.""" +"""Password hashing functions and constants for the mudpy engine.""" -# Copyright (c) 2004-2011 Jeremy Stanley . Permission +# Copyright (c) 2004-2015 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 +import passlib.context +_CONTEXT = passlib.context.CryptContext( + all__vary_rounds=0.1, default="pbkdf2_sha512", + pbkdf2_sha512__default_rounds=1000, schemes=["pbkdf2_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 - -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"=") - - -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 - 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) - ) - - -def create( - 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) - ) +def create(password): + return _CONTEXT.encrypt(password) 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 + return _CONTEXT.verify(password, encoded_hash)