Clean up docstrings
[mudpy.git] / lib / mudpy / password.py
index 94d690b..96a14d8 100644 (file)
@@ -1,10 +1,17 @@
 # -*- 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 <fungi@yuggoth.org>. Permission
+# Copyright (c) 2004-2014 Jeremy Stanley <fungi@yuggoth.org>. 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,
-        u"./".encode(u"ascii")
-    ).rstrip(u"=")
+        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,31 +58,28 @@ 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=u"$"):
-    """
+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(u"^[0-9a-f]{32}$",
+    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))
-    return u"%s%s%s%s%s%s%s%s" % (
+        collapsed += bytes(legacy_hash[2 * i:2 * i + 2].decode("ascii"))
+    return "%s%s%s%s%s%s%s%s" % (
         sep,
         MD5,
         sep,
@@ -94,9 +97,10 @@ def create(
     algorithm=SHA1,
     rounds=4,
     salt_len=2,
-    sep=u"$"
+    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,27 @@ 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 u"%s%s%s%s%s%s%s%s" % (
-        sep, algorithm, sep, rounds, sep, salt, sep, _bytes_to_text(hashed)
+    return "%s%s%s%s%s%s%s%s" % (
+        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]
-    algorithm, rounds, salt, hashed = encoded_hash[1:].split(sep)
+    algorithm, rounds, salt, hashed = encoded_hash.split(sep)[1:]
     if encoded_hash == create(
        password=password,
        salt=salt,