# -*- coding: utf-8 -*-
# Copyright 2011 webapp2 AUTHORS.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
webapp2_extras.security
=======================
Security related helpers such as secure password hashing tools and a
random token generator.
"""
from __future__ import division
import hashlib
import hmac
import math
import random
import string
import six
import webapp2
_rng = random.SystemRandom()
HEXADECIMAL_DIGITS = string.digits + 'abcdef'
DIGITS = string.digits
LOWERCASE_ALPHA = string.ascii_lowercase
UPPERCASE_ALPHA = string.ascii_uppercase
LOWERCASE_ALPHANUMERIC = string.ascii_lowercase + string.digits
UPPERCASE_ALPHANUMERIC = string.ascii_uppercase + string.digits
ALPHA = string.ascii_letters
ALPHANUMERIC = string.ascii_letters + string.digits
ASCII_PRINTABLE = string.ascii_letters + string.digits + string.punctuation
ALL_PRINTABLE = string.printable
PUNCTUATION = string.punctuation
if six.PY3:
long = int
[docs]def generate_random_string(length=0, entropy=0, pool=ALPHANUMERIC):
"""Generates a random string using the given sequence pool.
To generate stronger passwords, use ASCII_PRINTABLE as pool.
Entropy is:
H = log2(N**L)
where:
- H is the entropy in bits.
- N is the possible symbol count
- L is length of string of symbols
Entropy chart::
-----------------------------------------------------------------
Symbol set Symbol Count (N) Entropy per symbol (H)
-----------------------------------------------------------------
HEXADECIMAL_DIGITS 16 4.0000 bits
DIGITS 10 3.3219 bits
LOWERCASE_ALPHA 26 4.7004 bits
UPPERCASE_ALPHA 26 4.7004 bits
PUNCTUATION 32 5.0000 bits
LOWERCASE_ALPHANUMERIC 36 5.1699 bits
UPPERCASE_ALPHANUMERIC 36 5.1699 bits
ALPHA 52 5.7004 bits
ALPHANUMERIC 62 5.9542 bits
ASCII_PRINTABLE 94 6.5546 bits
ALL_PRINTABLE 100 6.6438 bits
:param length:
The length of the random sequence. Use this or `entropy`, not both.
:param entropy:
Desired entropy in bits. Use this or `length`, not both.
Use this to generate passwords based on entropy:
http://en.wikipedia.org/wiki/Password_strength
:param pool:
A sequence of characters from which random characters are chosen.
Default to case-sensitive alpha-numeric characters.
:returns:
A string with characters randomly chosen from the pool.
"""
pool = list(set(pool))
if length and entropy:
raise ValueError('Use length or entropy, not both.')
if (length and entropy) is None:
raise ValueError('Use digit value for length and entropy, not None.')
if length <= 0 and entropy <= 0:
raise ValueError('Length or entropy must be greater than 0.')
if entropy:
log_of_2 = 0.6931471805599453
length = long(math.ceil((log_of_2 / math.log(len(pool))) * entropy))
return ''.join(_rng.choice(pool) for _ in six.moves.range(length))
[docs]def generate_password_hash(password, method='sha1', length=22, pepper=None):
"""Hashes a password.
The format of the string returned includes the method that was used
so that :func:`check_password_hash` can check the hash.
This method can **not** generate unsalted passwords but it is possible
to set the method to plain to enforce plaintext passwords. If a salt
is used, hmac is used internally to salt the password.
:param password:
The password to hash.
:param method:
The hash method to use (``'md5'`` or ``'sha1'``).
:param length:
Length of the salt to be created.
:param pepper:
A secret constant stored in the application code.
:returns:
A formatted hashed string that looks like this::
method$salt$hash
This function was ported and adapted from `Werkzeug`_.
"""
salt = method != 'plain' and generate_random_string(length) or ''
hashval = hash_password(password, method, salt, pepper)
if hashval is None:
raise TypeError('Invalid method %r.' % method)
return '%s$%s$%s' % (hashval, method, salt)
[docs]def check_password_hash(password, pwhash, pepper=None):
"""Checks a password against a given salted and hashed password value.
In order to support unsalted legacy passwords this method supports
plain text passwords, md5 and sha1 hashes (both salted and unsalted).
:param password:
The plaintext password to compare against the hash.
:param pwhash:
A hashed string like returned by :func:`generate_password_hash`.
:param pepper:
A secret constant stored in the application code.
:returns:
`True` if the password matched, `False` otherwise.
This function was ported and adapted from `Werkzeug`_.
"""
if pwhash.count('$') < 2:
return False
hashval, method, salt = pwhash.split('$', 2)
return hash_password(password, method, salt, pepper) == hashval
[docs]def hash_password(password, method, salt=None, pepper=None):
"""Hashes a password.
Supports plaintext without salt, unsalted and salted passwords. In case
salted passwords are used hmac is used.
:param password:
The password to be hashed.
:param method:
A method from ``hashlib``, e.g., `sha1` or `md5`, or `plain`.
:param salt:
A random salt string.
:param pepper:
A secret constant stored in the application code.
:returns:
A hashed password.
This function was ported and adapted from `Werkzeug`_.
"""
if method == 'plain':
return password
password = webapp2._to_utf8(password)
method = getattr(hashlib, method, None)
if not method:
return None
if salt:
h = hmac.new(webapp2._to_utf8(salt), password, method)
else:
h = method(password)
if pepper:
h = hmac.new(
webapp2._to_utf8(pepper), webapp2._to_utf8(h.hexdigest()), method)
return h.hexdigest()
[docs]def compare_hashes(a, b):
"""Checks if two hash strings are identical.
The intention is to make the running time be less dependant on the size of
the string.
:param a:
String 1.
:param b:
String 2.
:returns:
True if both strings are equal, False otherwise.
"""
if len(a) != len(b):
return False
result = 0
for x, y in zip(
webapp2._to_basestring(a),
webapp2._to_basestring(b)):
result |= ord(x) ^ ord(y)
return result == 0
# Old names.
create_token = generate_random_string
create_password_hash = generate_password_hash