Source code for webapp2_extras.security

# -*- 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