1 # -*- coding: utf-8 -*- 2 """ 3 webapp2_extras.security 4 ======================= 5 6 Security related helpers such as secure password hashing tools and a 7 random token generator. 8 9 :copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details. 10 :license: BSD, see LICENSE for more details. 11 :copyright: (c) 2011 Yesudeep Mangalapilly <yesudeep (at] gmail.com> 12 :license: Apache Sotware License, see LICENSE for details. 13 """ 14 from __future__ import division 15 16 import hashlib 17 import hmac 18 import math 19 import random 20 import string 21 22 import webapp2 23 24 _rng = random.SystemRandom() 25 26 HEXADECIMAL_DIGITS = string.digits + 'abcdef' 27 DIGITS = string.digits 28 LOWERCASE_ALPHA = string.lowercase 29 UPPERCASE_ALPHA = string.uppercase 30 LOWERCASE_ALPHANUMERIC = string.lowercase + string.digits 31 UPPERCASE_ALPHANUMERIC = string.uppercase + string.digits 32 ALPHA = string.letters 33 ALPHANUMERIC = string.letters + string.digits 34 ASCII_PRINTABLE = string.letters + string.digits + string.punctuation 35 ALL_PRINTABLE = string.printable 36 PUNCTUATION = string.punctuation 37 38 39 def generate_random_string(length=None, entropy=None, pool=ALPHANUMERIC): 40 """Generates a random string using the given sequence pool. 41 42 To generate stronger passwords, use ASCII_PRINTABLE as pool. 43 44 Entropy is: 45 46 H = log2(N**L) 47 48 where: 49 50 - H is the entropy in bits. 51 - N is the possible symbol count 52 - L is length of string of symbols 53 54 Entropy chart:: 55 56 ----------------------------------------------------------------- 57 Symbol set Symbol Count (N) Entropy per symbol (H) 58 ----------------------------------------------------------------- 59 HEXADECIMAL_DIGITS 16 4.0000 bits 60 DIGITS 10 3.3219 bits 61 LOWERCASE_ALPHA 26 4.7004 bits 62 UPPERCASE_ALPHA 26 4.7004 bits 63 PUNCTUATION 32 5.0000 bits 64 LOWERCASE_ALPHANUMERIC 36 5.1699 bits 65 UPPERCASE_ALPHANUMERIC 36 5.1699 bits 66 ALPHA 52 5.7004 bits 67 ALPHANUMERIC 62 5.9542 bits 68 ASCII_PRINTABLE 94 6.5546 bits 69 ALL_PRINTABLE 100 6.6438 bits 70 71 :param length: 72 The length of the random sequence. Use this or `entropy`, not both. 73 :param entropy: 74 Desired entropy in bits. Use this or `length`, not both. 75 Use this to generate passwords based on entropy: 76 http://en.wikipedia.org/wiki/Password_strength 77 :param pool: 78 A sequence of characters from which random characters are chosen. 79 Default to case-sensitive alpha-numeric characters. 80 :returns: 81 A string with characters randomly chosen from the pool. 82 """ 83 pool = list(set(pool)) 84 85 if length and entropy: 86 raise ValueError('Use length or entropy, not both.') 87 88 if length <= 0 and entropy <= 0: 89 raise ValueError('Length or entropy must be greater than 0.') 90 91 if entropy: 92 log_of_2 = 0.6931471805599453 93 length = long(math.ceil((log_of_2 / math.log(len(pool))) * entropy)) 94 95 return ''.join(_rng.choice(pool) for _ in xrange(length)) 96 97 98 def generate_password_hash(password, method='sha1', length=22, pepper=None): 99 """Hashes a password. 100 101 The format of the string returned includes the method that was used 102 so that :func:`check_password_hash` can check the hash. 103 104 This method can **not** generate unsalted passwords but it is possible 105 to set the method to plain to enforce plaintext passwords. If a salt 106 is used, hmac is used internally to salt the password. 107 108 :param password: 109 The password to hash. 110 :param method: 111 The hash method to use (``'md5'`` or ``'sha1'``). 112 :param length: 113 Length of the salt to be created. 114 :param pepper: 115 A secret constant stored in the application code. 116 :returns: 117 A formatted hashed string that looks like this:: 118 119 method$salt$hash 120 121 This function was ported and adapted from `Werkzeug`_. 122 """ 123 salt = method != 'plain' and generate_random_string(length) or '' 124 hashval = hash_password(password, method, salt, pepper) 125 if hashval is None: 126 raise TypeError('Invalid method %r.' % method) 127 128 return '%s$%s$%s' % (hashval, method, salt) 129 130 131 def check_password_hash(password, pwhash, pepper=None): 132 """Checks a password against a given salted and hashed password value. 133 134 In order to support unsalted legacy passwords this method supports 135 plain text passwords, md5 and sha1 hashes (both salted and unsalted). 136 137 :param password: 138 The plaintext password to compare against the hash. 139 :param pwhash: 140 A hashed string like returned by :func:`generate_password_hash`. 141 :param pepper: 142 A secret constant stored in the application code. 143 :returns: 144 `True` if the password matched, `False` otherwise. 145 146 This function was ported and adapted from `Werkzeug`_. 147 """ 148 if pwhash.count('$') < 2: 149 return False 150 151 hashval, method, salt = pwhash.split('$', 2) 152 return hash_password(password, method, salt, pepper) == hashval 153 154 155 def hash_password(password, method, salt=None, pepper=None): 156 """Hashes a password. 157 158 Supports plaintext without salt, unsalted and salted passwords. In case 159 salted passwords are used hmac is used. 160 161 :param password: 162 The password to be hashed. 163 :param method: 164 A method from ``hashlib``, e.g., `sha1` or `md5`, or `plain`. 165 :param salt: 166 A random salt string. 167 :param pepper: 168 A secret constant stored in the application code. 169 :returns: 170 A hashed password. 171 172 This function was ported and adapted from `Werkzeug`_. 173 """ 174 password = webapp2._to_utf8(password) 175 if method == 'plain': 176 return password 177 178 method = getattr(hashlib, method, None) 179 if not method: 180 return None 181 182 if salt: 183 h = hmac.new(webapp2._to_utf8(salt), password, method) 184 else: 185 h = method(password) 186 187 if pepper: 188 h = hmac.new(webapp2._to_utf8(pepper), h.hexdigest(), method) 189 190 return h.hexdigest() 191 192 193 def compare_hashes(a, b): 194 """Checks if two hash strings are identical. 195 196 The intention is to make the running time be less dependant on the size of 197 the string. 198 199 :param a: 200 String 1. 201 :param b: 202 String 2. 203 :returns: 204 True if both strings are equal, False otherwise. 205 """ 206 if len(a) != len(b): 207 return False 208 209 result = 0 210 for x, y in zip(a, b): 211 result |= ord(x) ^ ord(y) 212 213 return result == 0 214 215 216 # Old names. 217 create_token = generate_random_string 218 create_password_hash = generate_password_hash 219