X-Git-Url: https://mudpy.org/gitweb?a=blobdiff_plain;f=lib%2Fmudpy%2Fpassword.py;h=96a14d8752ba08069559662cb86d3f8f0f9efd1a;hb=afe9aa24f4f7cbc58b43faf18a7f0f35bf71eb7c;hp=20615b97f7e3c27e2e77f04d343132f22648439e;hpb=26963f7304e824d3781da7de4ef677d981412766;p=mudpy.git diff --git a/lib/mudpy/password.py b/lib/mudpy/password.py index 20615b9..96a14d8 100644 --- a/lib/mudpy/password.py +++ b/lib/mudpy/password.py @@ -1,10 +1,17 @@ # -*- coding: utf-8 -*- """Password hashing functions and constants for the mudpy engine.""" -# Copyright (c) 2004-2012 Jeremy Stanley . Permission +# Copyright (c) 2004-2014 Jeremy Stanley . Permission # to use, copy, modify, and distribute this software is granted under # terms provided in the LICENSE file distributed with this software. +import base64 +import hashlib +import math +import random +import re +import struct + # convenience constants for indexing the supported hashing algorithms, # guaranteed a stable part of the interface MD5 = 0 # hashlib.md5 @@ -16,35 +23,34 @@ SHA512 = 5 # hashlib.sha512 def _pack_bytes(numbers): - """ + """Make a packed byte sequence: + 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 = "" + packed = b"" 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): - """ + """Generate printable representation of 8-bit data: + This is a wrapper around base64.b64encode with preferences appropriate for encoding Unix-style passwd hash strings. """ - import base64 return base64.b64encode( byte_sequence, - "./".encode("ascii") - ).rstrip("=") + b"./" + ).decode("ascii").rstrip("=") def _generate_salt(salt_len=2): - """ + """Generate salt for a password hash: + 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 @@ -52,30 +58,27 @@ def _generate_salt(salt_len=2): 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))): + for i in range(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="$"): - """ + """Upgrade an older password hash: + 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("^[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): + collapsed = b"" + for i in range(16): # this needs to become a byte() call in 2to3 - collapsed += chr(int(legacy_hash[2 * i:2 * i + 2], 16)) + collapsed += bytes(legacy_hash[2 * i:2 * i + 2].decode("ascii")) return "%s%s%s%s%s%s%s%s" % ( sep, MD5, @@ -96,7 +99,8 @@ def create( salt_len=2, sep="$" ): - """ + """Generate a password hash: + 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 @@ -129,7 +133,6 @@ def create( 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: @@ -163,24 +166,26 @@ def create( # iterate the hashing algorithm over its own digest the specified # number of times - for i in xrange(2 ** rounds): - hashed = algorithms[algorithm](hashed).digest() + for i in range(2 ** rounds): + hashed = algorithms[algorithm](hashed.encode("utf-8")).digest() + hashed = "".join(format(x, "02x") for x in bytes(hashed)) # concatenate the output fields, coercing into text form as needed return "%s%s%s%s%s%s%s%s" % ( - sep, algorithm, sep, rounds, sep, salt, sep, _bytes_to_text(hashed) + sep, algorithm, sep, rounds, sep, salt, sep, + _bytes_to_text(hashed.encode("ascii")) ) def verify(password, encoded_hash): - """ + """Verify a password: + 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] - import mudpy.misc algorithm, rounds, salt, hashed = encoded_hash.split(sep)[1:] if encoded_hash == create( password=password,