|
1 | | -'''Wrapper to the POSIX crypt library call and associated functionality. |
2 | | -''' |
| 1 | +"""Wrapper to the POSIX crypt library call and associated functionality.""" |
3 | 2 |
|
4 | 3 | import _crypt |
| 4 | +import string |
| 5 | +from random import choice |
| 6 | +from collections import namedtuple |
5 | 7 |
|
6 | | -saltchars = 'abcdefghijklmnopqrstuvwxyz' |
7 | | -saltchars += saltchars.upper() |
8 | | -saltchars += '0123456789./' |
9 | 8 |
|
| 9 | +_saltchars = string.ascii_letters + string.digits + './' |
10 | 10 |
|
11 | | -class _MethodClass: |
12 | | - '''Class representing a salt method per the Modular Crypt Format or the |
13 | | - legacy 2-character crypt method.''' |
14 | | - def __init__(self, name, ident, salt_chars, total_size): |
15 | | - self.name = name |
16 | | - self.ident = ident |
17 | | - self.salt_chars = salt_chars |
18 | | - self.total_size = total_size |
19 | 11 |
|
20 | | - def __repr__(self): |
21 | | - return '<crypt.METHOD_%s>' % self.name |
| 12 | +class _Method(namedtuple('_Method', 'name ident salt_chars total_size')): |
22 | 13 |
|
| 14 | + """Class representing a salt method per the Modular Crypt Format or the |
| 15 | + legacy 2-character crypt method.""" |
23 | 16 |
|
24 | | -# available salting/crypto methods |
25 | | -METHOD_CRYPT = _MethodClass('CRYPT', None, 2, 13) |
26 | | -METHOD_MD5 = _MethodClass('MD5', '1', 8, 34) |
27 | | -METHOD_SHA256 = _MethodClass('SHA256', '5', 16, 63) |
28 | | -METHOD_SHA512 = _MethodClass('SHA512', '6', 16, 106) |
| 17 | + def __repr__(self): |
| 18 | + return '<crypt.METHOD_{}>'.format(self.name) |
29 | 19 |
|
30 | 20 |
|
31 | | -def methods(): |
32 | | - '''Return a list of methods that are available in the platform ``crypt()`` |
33 | | - library, sorted from strongest to weakest. This is guaranteed to always |
34 | | - return at least ``[METHOD_CRYPT]``''' |
35 | | - method_list = [ METHOD_SHA512, METHOD_SHA256, METHOD_MD5 ] |
36 | | - ret = [ method for method in method_list |
37 | | - if len(crypt('', method)) == method.total_size ] |
38 | | - ret.append(METHOD_CRYPT) |
39 | | - return ret |
40 | 21 |
|
| 22 | +def mksalt(method=None): |
| 23 | + """Generate a salt for the specified method. |
41 | 24 |
|
42 | | -def mksalt(method = None): |
43 | | - '''Generate a salt for the specified method. If not specified, the |
44 | | - strongest available method will be used.''' |
45 | | - import random |
| 25 | + If not specified, the strongest available method will be used. |
46 | 26 |
|
47 | | - if method == None: method = methods()[0] |
48 | | - s = '$%s$' % method.ident if method.ident else '' |
49 | | - s += ''.join([ random.choice(saltchars) for x in range(method.salt_chars) ]) |
50 | | - return(s) |
| 27 | + """ |
| 28 | + if method is None: |
| 29 | + method = methods[0] |
| 30 | + s = '${}$'.format(method.ident) if method.ident else '' |
| 31 | + s += ''.join(choice(_saltchars) for _ in range(method.salt_chars)) |
| 32 | + return s |
51 | 33 |
|
52 | 34 |
|
53 | | -def crypt(word, salt = None): |
54 | | - '''Return a string representing the one-way hash of a password, preturbed |
55 | | - by a salt. If ``salt`` is not specified or is ``None``, the strongest |
| 35 | +def crypt(word, salt=None): |
| 36 | + """Return a string representing the one-way hash of a password, with a salt |
| 37 | + prepended. |
| 38 | +
|
| 39 | + If ``salt`` is not specified or is ``None``, the strongest |
56 | 40 | available method will be selected and a salt generated. Otherwise, |
57 | 41 | ``salt`` may be one of the ``crypt.METHOD_*`` values, or a string as |
58 | | - returned by ``crypt.mksalt()``.''' |
59 | | - if salt == None: salt = mksalt() |
60 | | - elif isinstance(salt, _MethodClass): salt = mksalt(salt) |
61 | | - return(_crypt.crypt(word, salt)) |
| 42 | + returned by ``crypt.mksalt()``. |
| 43 | +
|
| 44 | + """ |
| 45 | + if salt is None or isinstance(salt, _Method): |
| 46 | + salt = mksalt(salt) |
| 47 | + return _crypt.crypt(word, salt) |
| 48 | + |
| 49 | + |
| 50 | +# available salting/crypto methods |
| 51 | +METHOD_CRYPT = _Method('CRYPT', None, 2, 13) |
| 52 | +METHOD_MD5 = _Method('MD5', '1', 8, 34) |
| 53 | +METHOD_SHA256 = _Method('SHA256', '5', 16, 63) |
| 54 | +METHOD_SHA512 = _Method('SHA512', '6', 16, 106) |
| 55 | + |
| 56 | +methods = [METHOD_SHA512, METHOD_SHA256, METHOD_MD5, METHOD_CRYPT] |
| 57 | +methods[:-1] = [m for m in methods[:-1] if len(crypt('', m)) == m.total_size] |
0 commit comments