- """
- 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 denoting 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 for
- 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)
- )
+ """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
+ 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)
+ """
+
+ # 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 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.encode("ascii"))
+ )