crypt.py 1.76 KB
Newer Older
1
"""Wrapper to the POSIX crypt library call and associated functionality."""
2 3

import _crypt
4 5 6
import string
from random import choice
from collections import namedtuple
7 8


9
_saltchars = string.ascii_letters + string.digits + './'
10 11


12
class _Method(namedtuple('_Method', 'name ident salt_chars total_size')):
13

14 15
    """Class representing a salt method per the Modular Crypt Format or the
    legacy 2-character crypt method."""
16

17 18
    def __repr__(self):
        return '<crypt.METHOD_{}>'.format(self.name)
19 20 21



22 23
def mksalt(method=None):
    """Generate a salt for the specified method.
24

25
    If not specified, the strongest available method will be used.
26

27 28 29 30 31 32
    """
    if method is None:
        method = methods[0]
    s = '${}$'.format(method.ident) if method.ident else ''
    s += ''.join(choice(_saltchars) for _ in range(method.salt_chars))
    return s
33 34


35 36 37 38 39
def crypt(word, salt=None):
    """Return a string representing the one-way hash of a password, with a salt
    prepended.

    If ``salt`` is not specified or is ``None``, the strongest
40 41
    available method will be selected and a salt generated.  Otherwise,
    ``salt`` may be one of the ``crypt.METHOD_*`` values, or a string as
42 43 44 45 46 47 48 49 50 51 52 53 54 55
    returned by ``crypt.mksalt()``.

    """
    if salt is None or isinstance(salt, _Method):
        salt = mksalt(salt)
    return _crypt.crypt(word, salt)


#  available salting/crypto methods
METHOD_CRYPT = _Method('CRYPT', None, 2, 13)
METHOD_MD5 = _Method('MD5', '1', 8, 34)
METHOD_SHA256 = _Method('SHA256', '5', 16, 63)
METHOD_SHA512 = _Method('SHA512', '6', 16, 106)

56 57 58 59 60 61 62
methods = []
for _method in (METHOD_SHA512, METHOD_SHA256, METHOD_MD5):
    _result = crypt('', _method)
    if _result and len(_result) == _method.total_size:
        methods.append(_method)
methods.append(METHOD_CRYPT)
del _result, _method