You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					810 lines
				
				28 KiB
			
		
		
			
		
	
	
					810 lines
				
				28 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								"""passlib.pwd -- password generation helpers"""
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# imports
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								from __future__ import absolute_import, division, print_function, unicode_literals
							 | 
						||
| 
								 | 
							
								# core
							 | 
						||
| 
								 | 
							
								import codecs
							 | 
						||
| 
								 | 
							
								from collections import defaultdict
							 | 
						||
| 
								 | 
							
								try:
							 | 
						||
| 
								 | 
							
								    from collections.abc import MutableMapping
							 | 
						||
| 
								 | 
							
								except ImportError:
							 | 
						||
| 
								 | 
							
								    # py2 compat
							 | 
						||
| 
								 | 
							
								    from collections import MutableMapping
							 | 
						||
| 
								 | 
							
								from math import ceil, log as logf
							 | 
						||
| 
								 | 
							
								import logging; log = logging.getLogger(__name__)
							 | 
						||
| 
								 | 
							
								import pkg_resources
							 | 
						||
| 
								 | 
							
								import os
							 | 
						||
| 
								 | 
							
								# site
							 | 
						||
| 
								 | 
							
								# pkg
							 | 
						||
| 
								 | 
							
								from passlib import exc
							 | 
						||
| 
								 | 
							
								from passlib.utils.compat import PY2, irange, itervalues, int_types
							 | 
						||
| 
								 | 
							
								from passlib.utils import rng, getrandstr, to_unicode
							 | 
						||
| 
								 | 
							
								from passlib.utils.decor import memoized_property
							 | 
						||
| 
								 | 
							
								# local
							 | 
						||
| 
								 | 
							
								__all__ = [
							 | 
						||
| 
								 | 
							
								    "genword", "default_charsets",
							 | 
						||
| 
								 | 
							
								    "genphrase", "default_wordsets",
							 | 
						||
| 
								 | 
							
								]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# constants
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# XXX: rename / publically document this map?
							 | 
						||
| 
								 | 
							
								entropy_aliases = dict(
							 | 
						||
| 
								 | 
							
								    # barest protection from throttled online attack
							 | 
						||
| 
								 | 
							
								    unsafe=12,
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # some protection from unthrottled online attack
							 | 
						||
| 
								 | 
							
								    weak=24,
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # some protection from offline attacks
							 | 
						||
| 
								 | 
							
								    fair=36,
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # reasonable protection from offline attacks
							 | 
						||
| 
								 | 
							
								    strong=48,
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # very good protection from offline attacks
							 | 
						||
| 
								 | 
							
								    secure=60,
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# internal helpers
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _superclasses(obj, cls):
							 | 
						||
| 
								 | 
							
								    """return remaining classes in object's MRO after cls"""
							 | 
						||
| 
								 | 
							
								    mro = type(obj).__mro__
							 | 
						||
| 
								 | 
							
								    return mro[mro.index(cls)+1:]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _self_info_rate(source):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    returns 'rate of self-information' --
							 | 
						||
| 
								 | 
							
								    i.e. average (per-symbol) entropy of the sequence **source**,
							 | 
						||
| 
								 | 
							
								    where probability of a given symbol occurring is calculated based on
							 | 
						||
| 
								 | 
							
								    the number of occurrences within the sequence itself.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if all elements of the source are unique, this should equal ``log(len(source), 2)``.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg source:
							 | 
						||
| 
								 | 
							
								        iterable containing 0+ symbols
							 | 
						||
| 
								 | 
							
								        (e.g. list of strings or ints, string of characters, etc).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns:
							 | 
						||
| 
								 | 
							
								        float bits of entropy
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        size = len(source)
							 | 
						||
| 
								 | 
							
								    except TypeError:
							 | 
						||
| 
								 | 
							
								        # if len() doesn't work, calculate size by summing counts later
							 | 
						||
| 
								 | 
							
								        size = None
							 | 
						||
| 
								 | 
							
								    counts = defaultdict(int)
							 | 
						||
| 
								 | 
							
								    for char in source:
							 | 
						||
| 
								 | 
							
								        counts[char] += 1
							 | 
						||
| 
								 | 
							
								    if size is None:
							 | 
						||
| 
								 | 
							
								        values = counts.values()
							 | 
						||
| 
								 | 
							
								        size = sum(values)
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        values = itervalues(counts)
							 | 
						||
| 
								 | 
							
								    if not size:
							 | 
						||
| 
								 | 
							
								        return 0
							 | 
						||
| 
								 | 
							
								    # NOTE: the following performs ``- sum(value / size * logf(value / size, 2) for value in values)``,
							 | 
						||
| 
								 | 
							
								    #       it just does so with as much pulled out of the sum() loop as possible...
							 | 
						||
| 
								 | 
							
								    return logf(size, 2) - sum(value * logf(value, 2) for value in values) / size
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# def _total_self_info(source):
							 | 
						||
| 
								 | 
							
								#     """
							 | 
						||
| 
								 | 
							
								#     return total self-entropy of a sequence
							 | 
						||
| 
								 | 
							
								#     (the average entropy per symbol * size of sequence)
							 | 
						||
| 
								 | 
							
								#     """
							 | 
						||
| 
								 | 
							
								#     return _self_info_rate(source) * len(source)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _open_asset_path(path, encoding=None):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    :param asset_path:
							 | 
						||
| 
								 | 
							
								        string containing absolute path to file,
							 | 
						||
| 
								 | 
							
								        or package-relative path using format
							 | 
						||
| 
								 | 
							
								        ``"python.module:relative/file/path"``.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns:
							 | 
						||
| 
								 | 
							
								        filehandle opened in 'rb' mode
							 | 
						||
| 
								 | 
							
								        (unless encoding explicitly specified)
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    if encoding:
							 | 
						||
| 
								 | 
							
								        return codecs.getreader(encoding)(_open_asset_path(path))
							 | 
						||
| 
								 | 
							
								    if os.path.isabs(path):
							 | 
						||
| 
								 | 
							
								        return open(path, "rb")
							 | 
						||
| 
								 | 
							
								    package, sep, subpath = path.partition(":")
							 | 
						||
| 
								 | 
							
								    if not sep:
							 | 
						||
| 
								 | 
							
								        raise ValueError("asset path must be absolute file path "
							 | 
						||
| 
								 | 
							
								                         "or use 'pkg.name:sub/path' format: %r" % (path,))
							 | 
						||
| 
								 | 
							
								    return pkg_resources.resource_stream(package, subpath)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: type aliases
							 | 
						||
| 
								 | 
							
								_sequence_types = (list, tuple)
							 | 
						||
| 
								 | 
							
								_set_types = (set, frozenset)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: set of elements that ensure_unique() has validated already.
							 | 
						||
| 
								 | 
							
								_ensure_unique_cache = set()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _ensure_unique(source, param="source"):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    helper for generators --
							 | 
						||
| 
								 | 
							
								    Throws ValueError if source elements aren't unique.
							 | 
						||
| 
								 | 
							
								    Error message will display (abbreviated) repr of the duplicates in a string/list
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    # check cache to speed things up for frozensets / tuples / strings
							 | 
						||
| 
								 | 
							
								    cache = _ensure_unique_cache
							 | 
						||
| 
								 | 
							
								    hashable = True
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        if source in cache:
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								    except TypeError:
							 | 
						||
| 
								 | 
							
								        hashable = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # check if it has dup elements
							 | 
						||
| 
								 | 
							
								    if isinstance(source, _set_types) or len(set(source)) == len(source):
							 | 
						||
| 
								 | 
							
								        if hashable:
							 | 
						||
| 
								 | 
							
								            try:
							 | 
						||
| 
								 | 
							
								                cache.add(source)
							 | 
						||
| 
								 | 
							
								            except TypeError:
							 | 
						||
| 
								 | 
							
								                # XXX: under pypy, "list() in set()" above doesn't throw TypeError,
							 | 
						||
| 
								 | 
							
								                #      but trying to add unhashable it to a set *does*.
							 | 
						||
| 
								 | 
							
								                pass
							 | 
						||
| 
								 | 
							
								        return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # build list of duplicate values
							 | 
						||
| 
								 | 
							
								    seen = set()
							 | 
						||
| 
								 | 
							
								    dups = set()
							 | 
						||
| 
								 | 
							
								    for elem in source:
							 | 
						||
| 
								 | 
							
								        (dups if elem in seen else seen).add(elem)
							 | 
						||
| 
								 | 
							
								    dups = sorted(dups)
							 | 
						||
| 
								 | 
							
								    trunc = 8
							 | 
						||
| 
								 | 
							
								    if len(dups) > trunc:
							 | 
						||
| 
								 | 
							
								        trunc = 5
							 | 
						||
| 
								 | 
							
								    dup_repr = ", ".join(repr(str(word)) for word in dups[:trunc])
							 | 
						||
| 
								 | 
							
								    if len(dups) > trunc:
							 | 
						||
| 
								 | 
							
								        dup_repr += ", ... plus %d others" % (len(dups) - trunc)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # throw error
							 | 
						||
| 
								 | 
							
								    raise ValueError("`%s` cannot contain duplicate elements: %s" %
							 | 
						||
| 
								 | 
							
								                     (param, dup_repr))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# base generator class
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								class SequenceGenerator(object):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Base class used by word & phrase generators.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    These objects take a series of options, corresponding
							 | 
						||
| 
								 | 
							
								    to those of the :func:`generate` function.
							 | 
						||
| 
								 | 
							
								    They act as callables which can be used to generate a password
							 | 
						||
| 
								 | 
							
								    or a list of 1+ passwords. They also expose some read-only
							 | 
						||
| 
								 | 
							
								    informational attributes.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Parameters
							 | 
						||
| 
								 | 
							
								    ----------
							 | 
						||
| 
								 | 
							
								    :param entropy:
							 | 
						||
| 
								 | 
							
								        Optionally specify the amount of entropy the resulting passwords
							 | 
						||
| 
								 | 
							
								        should contain (as measured with respect to the generator itself).
							 | 
						||
| 
								 | 
							
								        This will be used to auto-calculate the required password size.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param length:
							 | 
						||
| 
								 | 
							
								        Optionally specify the length of password to generate,
							 | 
						||
| 
								 | 
							
								        measured as count of whatever symbols the subclass uses (characters or words).
							 | 
						||
| 
								 | 
							
								        Note if ``entropy`` requires a larger minimum length,
							 | 
						||
| 
								 | 
							
								        that will be used instead.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param rng:
							 | 
						||
| 
								 | 
							
								        Optionally provide a custom RNG source to use.
							 | 
						||
| 
								 | 
							
								        Should be an instance of :class:`random.Random`,
							 | 
						||
| 
								 | 
							
								        defaults to :class:`random.SystemRandom`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Attributes
							 | 
						||
| 
								 | 
							
								    ----------
							 | 
						||
| 
								 | 
							
								    .. autoattribute:: length
							 | 
						||
| 
								 | 
							
								    .. autoattribute:: symbol_count
							 | 
						||
| 
								 | 
							
								    .. autoattribute:: entropy_per_symbol
							 | 
						||
| 
								 | 
							
								    .. autoattribute:: entropy
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Subclassing
							 | 
						||
| 
								 | 
							
								    -----------
							 | 
						||
| 
								 | 
							
								    Subclasses must implement the ``.__next__()`` method,
							 | 
						||
| 
								 | 
							
								    and set ``.symbol_count`` before calling base ``__init__`` method.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    # instance attrs
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #: requested size of final password
							 | 
						||
| 
								 | 
							
								    length = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #: requested entropy of final password
							 | 
						||
| 
								 | 
							
								    requested_entropy = "strong"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #: random number source to use
							 | 
						||
| 
								 | 
							
								    rng = rng
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #: number of potential symbols (must be filled in by subclass)
							 | 
						||
| 
								 | 
							
								    symbol_count = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    # init
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    def __init__(self, entropy=None, length=None, rng=None, **kwds):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # make sure subclass set things up correctly
							 | 
						||
| 
								 | 
							
								        assert self.symbol_count is not None, "subclass must set .symbol_count"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # init length & requested entropy
							 | 
						||
| 
								 | 
							
								        if entropy is not None or length is None:
							 | 
						||
| 
								 | 
							
								            if entropy is None:
							 | 
						||
| 
								 | 
							
								                entropy = self.requested_entropy
							 | 
						||
| 
								 | 
							
								            entropy = entropy_aliases.get(entropy, entropy)
							 | 
						||
| 
								 | 
							
								            if entropy <= 0:
							 | 
						||
| 
								 | 
							
								                raise ValueError("`entropy` must be positive number")
							 | 
						||
| 
								 | 
							
								            min_length = int(ceil(entropy / self.entropy_per_symbol))
							 | 
						||
| 
								 | 
							
								            if length is None or length < min_length:
							 | 
						||
| 
								 | 
							
								                length = min_length
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.requested_entropy = entropy
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if length < 1:
							 | 
						||
| 
								 | 
							
								            raise ValueError("`length` must be positive integer")
							 | 
						||
| 
								 | 
							
								        self.length = length
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # init other common options
							 | 
						||
| 
								 | 
							
								        if rng is not None:
							 | 
						||
| 
								 | 
							
								            self.rng = rng
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # hand off to parent
							 | 
						||
| 
								 | 
							
								        if kwds and _superclasses(self, SequenceGenerator) == (object,):
							 | 
						||
| 
								 | 
							
								            raise TypeError("Unexpected keyword(s): %s" % ", ".join(kwds.keys()))
							 | 
						||
| 
								 | 
							
								        super(SequenceGenerator, self).__init__(**kwds)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    # informational helpers
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @memoized_property
							 | 
						||
| 
								 | 
							
								    def entropy_per_symbol(self):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Average entropy per symbol (assuming all symbols have equal probability)
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return logf(self.symbol_count, 2)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @memoized_property
							 | 
						||
| 
								 | 
							
								    def entropy(self):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Effective entropy of generated passwords.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        This value will always be a multiple of :attr:`entropy_per_symbol`.
							 | 
						||
| 
								 | 
							
								        If entropy is specified in constructor, :attr:`length` will be chosen so
							 | 
						||
| 
								 | 
							
								        so that this value is the smallest multiple >= :attr:`requested_entropy`.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return self.length * self.entropy_per_symbol
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    # generation
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    def __next__(self):
							 | 
						||
| 
								 | 
							
								        """main generation function, should create one password/phrase"""
							 | 
						||
| 
								 | 
							
								        raise NotImplementedError("implement in subclass")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __call__(self, returns=None):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        frontend used by genword() / genphrase() to create passwords
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if returns is None:
							 | 
						||
| 
								 | 
							
								            return next(self)
							 | 
						||
| 
								 | 
							
								        elif isinstance(returns, int_types):
							 | 
						||
| 
								 | 
							
								            return [next(self) for _ in irange(returns)]
							 | 
						||
| 
								 | 
							
								        elif returns is iter:
							 | 
						||
| 
								 | 
							
								            return self
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            raise exc.ExpectedTypeError(returns, "<None>, int, or <iter>", "returns")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __iter__(self):
							 | 
						||
| 
								 | 
							
								        return self
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if PY2:
							 | 
						||
| 
								 | 
							
								        def next(self):
							 | 
						||
| 
								 | 
							
								            return self.__next__()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    # eoc
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# default charsets
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: global dict of predefined characters sets
							 | 
						||
| 
								 | 
							
								default_charsets = dict(
							 | 
						||
| 
								 | 
							
								    # ascii letters, digits, and some punctuation
							 | 
						||
| 
								 | 
							
								    ascii_72='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*?/',
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # ascii letters and digits
							 | 
						||
| 
								 | 
							
								    ascii_62='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # ascii_50, without visually similar '1IiLl', '0Oo', '5S', '8B'
							 | 
						||
| 
								 | 
							
								    ascii_50='234679abcdefghjkmnpqrstuvwxyzACDEFGHJKMNPQRTUVWXYZ',
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # lower case hexadecimal
							 | 
						||
| 
								 | 
							
								    hex='0123456789abcdef',
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# password generator
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class WordGenerator(SequenceGenerator):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Class which generates passwords by randomly choosing from a string of unique characters.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Parameters
							 | 
						||
| 
								 | 
							
								    ----------
							 | 
						||
| 
								 | 
							
								    :param chars:
							 | 
						||
| 
								 | 
							
								        custom character string to draw from.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param charset:
							 | 
						||
| 
								 | 
							
								        predefined charset to draw from.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param \\*\\*kwds:
							 | 
						||
| 
								 | 
							
								        all other keywords passed to the :class:`SequenceGenerator` parent class.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Attributes
							 | 
						||
| 
								 | 
							
								    ----------
							 | 
						||
| 
								 | 
							
								    .. autoattribute:: chars
							 | 
						||
| 
								 | 
							
								    .. autoattribute:: charset
							 | 
						||
| 
								 | 
							
								    .. autoattribute:: default_charsets
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    # instance attrs
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #: Predefined character set in use (set to None for instances using custom 'chars')
							 | 
						||
| 
								 | 
							
								    charset = "ascii_62"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #: string of chars to draw from -- usually filled in from charset
							 | 
						||
| 
								 | 
							
								    chars = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    # init
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    def __init__(self, chars=None, charset=None, **kwds):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # init chars and charset
							 | 
						||
| 
								 | 
							
								        if chars:
							 | 
						||
| 
								 | 
							
								            if charset:
							 | 
						||
| 
								 | 
							
								                raise TypeError("`chars` and `charset` are mutually exclusive")
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            if not charset:
							 | 
						||
| 
								 | 
							
								                charset = self.charset
							 | 
						||
| 
								 | 
							
								                assert charset
							 | 
						||
| 
								 | 
							
								            chars = default_charsets[charset]
							 | 
						||
| 
								 | 
							
								        self.charset = charset
							 | 
						||
| 
								 | 
							
								        chars = to_unicode(chars, param="chars")
							 | 
						||
| 
								 | 
							
								        _ensure_unique(chars, param="chars")
							 | 
						||
| 
								 | 
							
								        self.chars = chars
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # hand off to parent
							 | 
						||
| 
								 | 
							
								        super(WordGenerator, self).__init__(**kwds)
							 | 
						||
| 
								 | 
							
								        # log.debug("WordGenerator(): entropy/char=%r", self.entropy_per_symbol)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    # informational helpers
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @memoized_property
							 | 
						||
| 
								 | 
							
								    def symbol_count(self):
							 | 
						||
| 
								 | 
							
								        return len(self.chars)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    # generation
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __next__(self):
							 | 
						||
| 
								 | 
							
								        # XXX: could do things like optionally ensure certain character groups
							 | 
						||
| 
								 | 
							
								        #      (e.g. letters & punctuation) are included
							 | 
						||
| 
								 | 
							
								        return getrandstr(self.rng, self.chars, self.length)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    # eoc
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def genword(entropy=None, length=None, returns=None, **kwds):
							 | 
						||
| 
								 | 
							
								    """Generate one or more random passwords.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This function uses :mod:`random.SystemRandom` to generate
							 | 
						||
| 
								 | 
							
								    one or more passwords using various character sets.
							 | 
						||
| 
								 | 
							
								    The complexity of the password can be specified
							 | 
						||
| 
								 | 
							
								    by size, or by the desired amount of entropy.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Usage Example::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        >>> # generate a random alphanumeric string with 48 bits of entropy (the default)
							 | 
						||
| 
								 | 
							
								        >>> from passlib import pwd
							 | 
						||
| 
								 | 
							
								        >>> pwd.genword()
							 | 
						||
| 
								 | 
							
								        'DnBHvDjMK6'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        >>> # generate a random hexadecimal string with 52 bits of entropy
							 | 
						||
| 
								 | 
							
								        >>> pwd.genword(entropy=52, charset="hex")
							 | 
						||
| 
								 | 
							
								        '310f1a7ac793f'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param entropy:
							 | 
						||
| 
								 | 
							
								        Strength of resulting password, measured in 'guessing entropy' bits.
							 | 
						||
| 
								 | 
							
								        An appropriate **length** value will be calculated
							 | 
						||
| 
								 | 
							
								        based on the requested entropy amount, and the size of the character set.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        This can be a positive integer, or one of the following preset
							 | 
						||
| 
								 | 
							
								        strings: ``"weak"`` (24), ``"fair"`` (36),
							 | 
						||
| 
								 | 
							
								        ``"strong"`` (48), and ``"secure"`` (56).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        If neither this or **length** is specified, **entropy** will default
							 | 
						||
| 
								 | 
							
								        to ``"strong"`` (48).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param length:
							 | 
						||
| 
								 | 
							
								        Size of resulting password, measured in characters.
							 | 
						||
| 
								 | 
							
								        If omitted, the size is auto-calculated based on the **entropy** parameter.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        If both **entropy** and **length** are specified,
							 | 
						||
| 
								 | 
							
								        the stronger value will be used.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param returns:
							 | 
						||
| 
								 | 
							
								        Controls what this function returns:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        * If ``None`` (the default), this function will generate a single password.
							 | 
						||
| 
								 | 
							
								        * If an integer, this function will return a list containing that many passwords.
							 | 
						||
| 
								 | 
							
								        * If the ``iter`` constant, will return an iterator that yields passwords.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param chars:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Optionally specify custom string of characters to use when randomly
							 | 
						||
| 
								 | 
							
								        generating a password. This option cannot be combined with **charset**.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param charset:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        The predefined character set to draw from (if not specified by **chars**).
							 | 
						||
| 
								 | 
							
								        There are currently four presets available:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        * ``"ascii_62"`` (the default) -- all digits and ascii upper & lowercase letters.
							 | 
						||
| 
								 | 
							
								          Provides ~5.95 entropy per character.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        * ``"ascii_50"`` -- subset which excludes visually similar characters
							 | 
						||
| 
								 | 
							
								          (``1IiLl0Oo5S8B``). Provides ~5.64 entropy per character.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        * ``"ascii_72"`` -- all digits and ascii upper & lowercase letters,
							 | 
						||
| 
								 | 
							
								          as well as some punctuation. Provides ~6.17 entropy per character.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        * ``"hex"`` -- Lower case hexadecimal.  Providers 4 bits of entropy per character.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns:
							 | 
						||
| 
								 | 
							
								        :class:`!unicode` string containing randomly generated password;
							 | 
						||
| 
								 | 
							
								        or list of 1+ passwords if :samp:`returns={int}` is specified.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    gen = WordGenerator(length=length, entropy=entropy, **kwds)
							 | 
						||
| 
								 | 
							
								    return gen(returns)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# default wordsets
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _load_wordset(asset_path):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    load wordset from compressed datafile within package data.
							 | 
						||
| 
								 | 
							
								    file should be utf-8 encoded
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param asset_path:
							 | 
						||
| 
								 | 
							
								        string containing  absolute path to wordset file,
							 | 
						||
| 
								 | 
							
								        or "python.module:relative/file/path".
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns:
							 | 
						||
| 
								 | 
							
								        tuple of words, as loaded from specified words file.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    # open resource file, convert to tuple of words (strip blank lines & ws)
							 | 
						||
| 
								 | 
							
								    with _open_asset_path(asset_path, "utf-8") as fh:
							 | 
						||
| 
								 | 
							
								        gen = (word.strip() for word in fh)
							 | 
						||
| 
								 | 
							
								        words = tuple(word for word in gen if word)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # NOTE: works but not used
							 | 
						||
| 
								 | 
							
								    # # detect if file uses "<int> <word>" format, and strip numeric prefix
							 | 
						||
| 
								 | 
							
								    # def extract(row):
							 | 
						||
| 
								 | 
							
								    #     idx, word = row.replace("\t", " ").split(" ", 1)
							 | 
						||
| 
								 | 
							
								    #     if not idx.isdigit():
							 | 
						||
| 
								 | 
							
								    #         raise ValueError("row is not dice index + word")
							 | 
						||
| 
								 | 
							
								    #     return word
							 | 
						||
| 
								 | 
							
								    # try:
							 | 
						||
| 
								 | 
							
								    #     extract(words[-1])
							 | 
						||
| 
								 | 
							
								    # except ValueError:
							 | 
						||
| 
								 | 
							
								    #     pass
							 | 
						||
| 
								 | 
							
								    # else:
							 | 
						||
| 
								 | 
							
								    #     words = tuple(extract(word) for word in words)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    log.debug("loaded %d-element wordset from %r", len(words), asset_path)
							 | 
						||
| 
								 | 
							
								    return words
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class WordsetDict(MutableMapping):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Special mapping used to store dictionary of wordsets.
							 | 
						||
| 
								 | 
							
								    Different from a regular dict in that some wordsets
							 | 
						||
| 
								 | 
							
								    may be lazy-loaded from an asset path.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #: dict of key -> asset path
							 | 
						||
| 
								 | 
							
								    paths = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #: dict of key -> value
							 | 
						||
| 
								 | 
							
								    _loaded = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, *args, **kwds):
							 | 
						||
| 
								 | 
							
								        self.paths = {}
							 | 
						||
| 
								 | 
							
								        self._loaded = {}
							 | 
						||
| 
								 | 
							
								        super(WordsetDict, self).__init__(*args, **kwds)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __getitem__(self, key):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            return self._loaded[key]
							 | 
						||
| 
								 | 
							
								        except KeyError:
							 | 
						||
| 
								 | 
							
								            pass
							 | 
						||
| 
								 | 
							
								        path = self.paths[key]
							 | 
						||
| 
								 | 
							
								        value = self._loaded[key] = _load_wordset(path)
							 | 
						||
| 
								 | 
							
								        return value
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def set_path(self, key, path):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        set asset path to lazy-load wordset from.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        self.paths[key] = path
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __setitem__(self, key, value):
							 | 
						||
| 
								 | 
							
								        self._loaded[key] = value
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __delitem__(self, key):
							 | 
						||
| 
								 | 
							
								        if key in self:
							 | 
						||
| 
								 | 
							
								            del self._loaded[key]
							 | 
						||
| 
								 | 
							
								            self.paths.pop(key, None)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            del self.paths[key]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def _keyset(self):
							 | 
						||
| 
								 | 
							
								        keys = set(self._loaded)
							 | 
						||
| 
								 | 
							
								        keys.update(self.paths)
							 | 
						||
| 
								 | 
							
								        return keys
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __iter__(self):
							 | 
						||
| 
								 | 
							
								        return iter(self._keyset)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __len__(self):
							 | 
						||
| 
								 | 
							
								        return len(self._keyset)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # NOTE: speeds things up, and prevents contains from lazy-loading
							 | 
						||
| 
								 | 
							
								    def __contains__(self, key):
							 | 
						||
| 
								 | 
							
								        return key in self._loaded or key in self.paths
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: dict of predefined word sets.
							 | 
						||
| 
								 | 
							
								#: key is name of wordset, value should be sequence of words.
							 | 
						||
| 
								 | 
							
								default_wordsets = WordsetDict()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# register the wordsets built into passlib
							 | 
						||
| 
								 | 
							
								for name in "eff_long eff_short eff_prefixed bip39".split():
							 | 
						||
| 
								 | 
							
								    default_wordsets.set_path(name, "passlib:_data/wordsets/%s.txt" % name)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# passphrase generator
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								class PhraseGenerator(SequenceGenerator):
							 | 
						||
| 
								 | 
							
								    """class which generates passphrases by randomly choosing
							 | 
						||
| 
								 | 
							
								    from a list of unique words.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param wordset:
							 | 
						||
| 
								 | 
							
								        wordset to draw from.
							 | 
						||
| 
								 | 
							
								    :param preset:
							 | 
						||
| 
								 | 
							
								        name of preset wordlist to use instead of ``wordset``.
							 | 
						||
| 
								 | 
							
								    :param spaces:
							 | 
						||
| 
								 | 
							
								        whether to insert spaces between words in output (defaults to ``True``).
							 | 
						||
| 
								 | 
							
								    :param \\*\\*kwds:
							 | 
						||
| 
								 | 
							
								        all other keywords passed to the :class:`SequenceGenerator` parent class.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. autoattribute:: wordset
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    # instance attrs
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #: predefined wordset to use
							 | 
						||
| 
								 | 
							
								    wordset = "eff_long"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #: list of words to draw from
							 | 
						||
| 
								 | 
							
								    words = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #: separator to use when joining words
							 | 
						||
| 
								 | 
							
								    sep = " "
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    # init
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    def __init__(self, wordset=None, words=None, sep=None, **kwds):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # load wordset
							 | 
						||
| 
								 | 
							
								        if words is not None:
							 | 
						||
| 
								 | 
							
								            if wordset is not None:
							 | 
						||
| 
								 | 
							
								                raise TypeError("`words` and `wordset` are mutually exclusive")
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            if wordset is None:
							 | 
						||
| 
								 | 
							
								                wordset = self.wordset
							 | 
						||
| 
								 | 
							
								                assert wordset
							 | 
						||
| 
								 | 
							
								            words = default_wordsets[wordset]
							 | 
						||
| 
								 | 
							
								        self.wordset = wordset
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # init words
							 | 
						||
| 
								 | 
							
								        if not isinstance(words, _sequence_types):
							 | 
						||
| 
								 | 
							
								            words = tuple(words)
							 | 
						||
| 
								 | 
							
								        _ensure_unique(words, param="words")
							 | 
						||
| 
								 | 
							
								        self.words = words
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # init separator
							 | 
						||
| 
								 | 
							
								        if sep is None:
							 | 
						||
| 
								 | 
							
								            sep = self.sep
							 | 
						||
| 
								 | 
							
								        sep = to_unicode(sep, param="sep")
							 | 
						||
| 
								 | 
							
								        self.sep = sep
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # hand off to parent
							 | 
						||
| 
								 | 
							
								        super(PhraseGenerator, self).__init__(**kwds)
							 | 
						||
| 
								 | 
							
								        ##log.debug("PhraseGenerator(): entropy/word=%r entropy/char=%r min_chars=%r",
							 | 
						||
| 
								 | 
							
								        ##          self.entropy_per_symbol, self.entropy_per_char, self.min_chars)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    # informational helpers
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @memoized_property
							 | 
						||
| 
								 | 
							
								    def symbol_count(self):
							 | 
						||
| 
								 | 
							
								        return len(self.words)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    # generation
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __next__(self):
							 | 
						||
| 
								 | 
							
								        words = (self.rng.choice(self.words) for _ in irange(self.length))
							 | 
						||
| 
								 | 
							
								        return self.sep.join(words)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								    # eoc
							 | 
						||
| 
								 | 
							
								    #=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def genphrase(entropy=None, length=None, returns=None, **kwds):
							 | 
						||
| 
								 | 
							
								    """Generate one or more random password / passphrases.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This function uses :mod:`random.SystemRandom` to generate
							 | 
						||
| 
								 | 
							
								    one or more passwords; it can be configured to generate
							 | 
						||
| 
								 | 
							
								    alphanumeric passwords, or full english phrases.
							 | 
						||
| 
								 | 
							
								    The complexity of the password can be specified
							 | 
						||
| 
								 | 
							
								    by size, or by the desired amount of entropy.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Usage Example::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        >>> # generate random phrase with 48 bits of entropy
							 | 
						||
| 
								 | 
							
								        >>> from passlib import pwd
							 | 
						||
| 
								 | 
							
								        >>> pwd.genphrase()
							 | 
						||
| 
								 | 
							
								        'gangly robbing salt shove'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        >>> # generate a random phrase with 52 bits of entropy
							 | 
						||
| 
								 | 
							
								        >>> # using a particular wordset
							 | 
						||
| 
								 | 
							
								        >>> pwd.genword(entropy=52, wordset="bip39")
							 | 
						||
| 
								 | 
							
								        'wheat dilemma reward rescue diary'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param entropy:
							 | 
						||
| 
								 | 
							
								        Strength of resulting password, measured in 'guessing entropy' bits.
							 | 
						||
| 
								 | 
							
								        An appropriate **length** value will be calculated
							 | 
						||
| 
								 | 
							
								        based on the requested entropy amount, and the size of the word set.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        This can be a positive integer, or one of the following preset
							 | 
						||
| 
								 | 
							
								        strings: ``"weak"`` (24), ``"fair"`` (36),
							 | 
						||
| 
								 | 
							
								        ``"strong"`` (48), and ``"secure"`` (56).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        If neither this or **length** is specified, **entropy** will default
							 | 
						||
| 
								 | 
							
								        to ``"strong"`` (48).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param length:
							 | 
						||
| 
								 | 
							
								        Length of resulting password, measured in words.
							 | 
						||
| 
								 | 
							
								        If omitted, the size is auto-calculated based on the **entropy** parameter.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        If both **entropy** and **length** are specified,
							 | 
						||
| 
								 | 
							
								        the stronger value will be used.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param returns:
							 | 
						||
| 
								 | 
							
								        Controls what this function returns:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        * If ``None`` (the default), this function will generate a single password.
							 | 
						||
| 
								 | 
							
								        * If an integer, this function will return a list containing that many passwords.
							 | 
						||
| 
								 | 
							
								        * If the ``iter`` builtin, will return an iterator that yields passwords.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param words:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Optionally specifies a list/set of words to use when randomly generating a passphrase.
							 | 
						||
| 
								 | 
							
								        This option cannot be combined with **wordset**.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param wordset:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        The predefined word set to draw from (if not specified by **words**).
							 | 
						||
| 
								 | 
							
								        There are currently four presets available:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        ``"eff_long"`` (the default)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            Wordset containing 7776 english words of ~7 letters.
							 | 
						||
| 
								 | 
							
								            Constructed by the EFF, it offers ~12.9 bits of entropy per word.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            This wordset (and the other ``"eff_"`` wordsets)
							 | 
						||
| 
								 | 
							
								            were `created by the EFF <https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases>`_
							 | 
						||
| 
								 | 
							
								            to aid in generating passwords.  See their announcement page
							 | 
						||
| 
								 | 
							
								            for more details about the design & properties of these wordsets.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        ``"eff_short"``
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            Wordset containing 1296 english words of ~4.5 letters.
							 | 
						||
| 
								 | 
							
								            Constructed by the EFF, it offers ~10.3 bits of entropy per word.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        ``"eff_prefixed"``
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            Wordset containing 1296 english words of ~8 letters,
							 | 
						||
| 
								 | 
							
								            selected so that they each have a unique 3-character prefix.
							 | 
						||
| 
								 | 
							
								            Constructed by the EFF, it offers ~10.3 bits of entropy per word.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        ``"bip39"``
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            Wordset of 2048 english words of ~5 letters,
							 | 
						||
| 
								 | 
							
								            selected so that they each have a unique 4-character prefix.
							 | 
						||
| 
								 | 
							
								            Published as part of Bitcoin's `BIP 39 <https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt>`_,
							 | 
						||
| 
								 | 
							
								            this wordset has exactly 11 bits of entropy per word.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            This list offers words that are typically shorter than ``"eff_long"``
							 | 
						||
| 
								 | 
							
								            (at the cost of slightly less entropy); and much shorter than
							 | 
						||
| 
								 | 
							
								            ``"eff_prefixed"`` (at the cost of a longer unique prefix).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param sep:
							 | 
						||
| 
								 | 
							
								        Optional separator to use when joining words.
							 | 
						||
| 
								 | 
							
								        Defaults to ``" "`` (a space), but can be an empty string, a hyphen, etc.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns:
							 | 
						||
| 
								 | 
							
								        :class:`!unicode` string containing randomly generated passphrase;
							 | 
						||
| 
								 | 
							
								        or list of 1+ passphrases if :samp:`returns={int}` is specified.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    gen = PhraseGenerator(entropy=entropy, length=length, **kwds)
							 | 
						||
| 
								 | 
							
								    return gen(returns)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# strength measurement
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# NOTE:
							 | 
						||
| 
								 | 
							
								# for a little while, had rough draft of password strength measurement alg here.
							 | 
						||
| 
								 | 
							
								# but not sure if there's value in yet another measurement algorithm,
							 | 
						||
| 
								 | 
							
								# that's not just duplicating the effort of libraries like zxcbn.
							 | 
						||
| 
								 | 
							
								# may revive it later, but for now, leaving some refs to others out there:
							 | 
						||
| 
								 | 
							
								#    * NIST 800-63 has simple alg
							 | 
						||
| 
								 | 
							
								#    * zxcvbn (https://tech.dropbox.com/2012/04/zxcvbn-realistic-password-strength-estimation/)
							 | 
						||
| 
								 | 
							
								#      might also be good, and has approach similar to composite approach i was already thinking about,
							 | 
						||
| 
								 | 
							
								#      but much more well thought out.
							 | 
						||
| 
								 | 
							
								#    * passfault (https://github.com/c-a-m/passfault) looks thorough,
							 | 
						||
| 
								 | 
							
								#      but may have licensing issues, plus porting to python looks like very big job :(
							 | 
						||
| 
								 | 
							
								#    * give a look at running things through zlib - might be able to cheaply
							 | 
						||
| 
								 | 
							
								#      catch extra redundancies.
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# eof
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 |