# -*- coding: utf-8 -*- """ werkzeug.security ~~~~~~~~~~~~~~~~~ Security related helpers such as secure password hashing tools. :copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ import hmac import string from random import SystemRandom # because the API of hmac changed with the introduction of the # new hashlib module, we have to support both. This sets up a # mapping to the digest factory functions and the digest modules # (or factory functions with changed API) try: from hashlib import sha1, md5 _hash_funcs = _hash_mods = {'sha1': sha1, 'md5': md5} _sha1_mod = sha1 _md5_mod = md5 except ImportError: import sha as _sha1_mod, md5 as _md5_mod _hash_mods = {'sha1': _sha1_mod, 'md5': _md5_mod} _hash_funcs = {'sha1': _sha1_mod.new, 'md5': _md5_mod.new} SALT_CHARS = string.letters + string.digits _sys_rng = SystemRandom() def gen_salt(length): """Generate a random string of SALT_CHARS with specified ``length``.""" if length <= 0: raise ValueError('requested salt of length <= 0') return ''.join(_sys_rng.choice(SALT_CHARS) for _ in xrange(length)) def _hash_internal(method, salt, password): """Internal password hash helper. Supports plaintext without salt, unsalted and salted passwords. In case salted passwords are used hmac is used. """ if method == 'plain': return password if salt: if method not in _hash_mods: return None if isinstance(salt, unicode): salt = salt.encode('utf-8') h = hmac.new(salt, None, _hash_mods[method]) else: if method not in _hash_funcs: return None h = _hash_funcs[method]() if isinstance(password, unicode): password = password.encode('utf-8') h.update(password) return h.hexdigest() def generate_password_hash(password, method='sha1', salt_length=8): """Hash a password with the given method and salt with with a string of the given length. The format of the string returned includes the method that was used so that :func:`check_password_hash` can check the hash. The format for the hashed string looks like this:: method$salt$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 salt_length: the lengt of the salt in letters """ salt = method != 'plain' and gen_salt(salt_length) or '' h = _hash_internal(method, salt, password) if h is None: raise TypeError('invalid method %r' % method) return '%s$%s$%s' % (method, salt, h) def check_password_hash(pwhash, password): """check 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). Returns `True` if the password matched, `False` otherwise. :param pwhash: a hashed string like returned by :func:`generate_password_hash` :param password: the plaintext password to compare against the hash """ if pwhash.count('$') < 2: return False method, salt, hashval = pwhash.split('$', 2) return _hash_internal(method, salt, password) == hashval