| # -*- coding: utf-8 -*- |
| """ |
| webapp2_extras.security |
| ======================= |
| |
| Security related helpers such as secure password hashing tools and a |
| random token generator. |
| |
| :copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details. |
| :license: BSD, see LICENSE for more details. |
| :copyright: (c) 2011 Yesudeep Mangalapilly <yesudeep@gmail.com> |
| :license: Apache Sotware License, see LICENSE for details. |
| """ |
| from __future__ import division |
| |
| import hashlib |
| import hmac |
| import math |
| import random |
| import string |
| |
| import webapp2 |
| |
| _rng = random.SystemRandom() |
| |
| HEXADECIMAL_DIGITS = string.digits + 'abcdef' |
| DIGITS = string.digits |
| LOWERCASE_ALPHA = string.lowercase |
| UPPERCASE_ALPHA = string.uppercase |
| LOWERCASE_ALPHANUMERIC = string.lowercase + string.digits |
| UPPERCASE_ALPHANUMERIC = string.uppercase + string.digits |
| ALPHA = string.letters |
| ALPHANUMERIC = string.letters + string.digits |
| ASCII_PRINTABLE = string.letters + string.digits + string.punctuation |
| ALL_PRINTABLE = string.printable |
| PUNCTUATION = string.punctuation |
| |
| |
| def generate_random_string(length=None, entropy=None, 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 <= 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 xrange(length)) |
| |
| |
| 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) |
| |
| |
| 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 |
| |
| |
| 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`_. |
| """ |
| password = webapp2._to_utf8(password) |
| if method == 'plain': |
| return 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), h.hexdigest(), method) |
| |
| return h.hexdigest() |
| |
| |
| 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(a, b): |
| result |= ord(x) ^ ord(y) |
| |
| return result == 0 |
| |
| |
| # Old names. |
| create_token = generate_random_string |
| create_password_hash = generate_password_hash |