Fix reload to use a copy of datafile keys
[mudpy.git] / lib / mudpy / password.py
index 94d690b..f6c1abc 100644 (file)
@@ -1,10 +1,17 @@
 # -*- coding: utf-8 -*-
 # -*- 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.
 
 # 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
 # convenience constants for indexing the supported hashing algorithms,
 # guaranteed a stable part of the interface
 MD5 = 0  # hashlib.md5
@@ -20,13 +27,10 @@ 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.
     """
     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
     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
 
         packed += struct.pack("B", number)
     return packed
 
@@ -36,11 +40,10 @@ def _bytes_to_text(byte_sequence):
     This is a wrapper around base64.b64encode with preferences
     appropriate for encoding Unix-style passwd hash strings.
     """
     This is a wrapper around base64.b64encode with preferences
     appropriate for encoding Unix-style passwd hash strings.
     """
-    import base64
     return base64.b64encode(
         byte_sequence,
     return base64.b64encode(
         byte_sequence,
-        u"./".encode(u"ascii")
-    ).rstrip(u"=")
+        b"./"
+    ).decode("ascii").rstrip("=")
 
 
 def _generate_salt(salt_len=2):
 
 
 def _generate_salt(salt_len=2):
@@ -52,15 +55,13 @@ 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.
     """
     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 = []
     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]
 
 
         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="$"):
     """
     This utility function is meant to provide a migration path for users
     of mudpy's legacy account-name-salted MD5 hexdigest password hashes.
     """
     This utility function is meant to provide a migration path for users
     of mudpy's legacy account-name-salted MD5 hexdigest password hashes.
@@ -68,15 +69,13 @@ def upgrade_legacy_hash(legacy_hash, salt, sep=u"$"):
     facets to this function, a conforming new-style password hash will be
     returned.
     """
     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"
                     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
         # 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,
         sep,
         MD5,
         sep,
@@ -94,7 +93,7 @@ def create(
     algorithm=SHA1,
     rounds=4,
     salt_len=2,
     algorithm=SHA1,
     rounds=4,
     salt_len=2,
-    sep=u"$"
+    sep="$"
 ):
     """
     The meat of the module, this function takes a provided password and
 ):
     """
     The meat of the module, this function takes a provided password and
@@ -129,7 +128,6 @@ def create(
 
        create(password, algorithm=SHA256, rounds=12, salt_len=16)
     """
 
        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:
 
     # if a specific salt wasn't specified, we need to generate one
     if not salt:
@@ -163,12 +161,14 @@ def create(
 
     # iterate the hashing algorithm over its own digest the specified
     # number of times
 
     # 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
 
     # 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"))
     )
 
 
     )
 
 
@@ -180,7 +180,7 @@ def verify(password, encoded_hash):
     comes out the same as the encoded_hash.
     """
     sep = encoded_hash[0]
     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,
     if encoded_hash == create(
        password=password,
        salt=salt,