~ubuntu-branches/ubuntu/vivid/moin/vivid

« back to all changes in this revision

Viewing changes to MoinMoin/support/passlib/handlers/fshp.py

  • Committer: Package Import Robot
  • Author(s): Matthias Klose
  • Date: 2014-01-07 21:33:21 UTC
  • mfrom: (0.1.34 sid)
  • Revision ID: package-import@ubuntu.com-20140107213321-574mr13z2oebjgms
Tags: 1.9.7-1ubuntu1
* Merge with Debian; remaining changes:
* debian/control:
  - remove python-xml from Suggests field, the package isn't in
    sys.path any more.
  - demote fckeditor from Recommends to Suggests; the code was previously
    embedded in moin, but it was also disabled, so there's no reason for us
    to pull this in by default currently. Note: fckeditor has a number of
    security problems and so this change probably needs to be carried
    indefinitely.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""passlib.handlers.fshp
 
2
"""
 
3
 
 
4
#=============================================================================
 
5
# imports
 
6
#=============================================================================
 
7
# core
 
8
from base64 import b64encode, b64decode
 
9
import re
 
10
import logging; log = logging.getLogger(__name__)
 
11
from warnings import warn
 
12
# site
 
13
# pkg
 
14
from passlib.utils import to_unicode
 
15
import passlib.utils.handlers as uh
 
16
from passlib.utils.compat import b, bytes, bascii_to_str, iteritems, u,\
 
17
                                 unicode
 
18
from passlib.utils.pbkdf2 import pbkdf1
 
19
# local
 
20
__all__ = [
 
21
    'fshp',
 
22
]
 
23
#=============================================================================
 
24
# sha1-crypt
 
25
#=============================================================================
 
26
class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
 
27
    """This class implements the FSHP password hash, and follows the :ref:`password-hash-api`.
 
28
 
 
29
    It supports a variable-length salt, and a variable number of rounds.
 
30
 
 
31
    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
 
32
 
 
33
    :param salt:
 
34
        Optional raw salt string.
 
35
        If not specified, one will be autogenerated (this is recommended).
 
36
 
 
37
    :param salt_size:
 
38
        Optional number of bytes to use when autogenerating new salts.
 
39
        Defaults to 16 bytes, but can be any non-negative value.
 
40
 
 
41
    :param rounds:
 
42
        Optional number of rounds to use.
 
43
        Defaults to 50000, must be between 1 and 4294967295, inclusive.
 
44
 
 
45
    :param variant:
 
46
        Optionally specifies variant of FSHP to use.
 
47
 
 
48
        * ``0`` - uses SHA-1 digest (deprecated).
 
49
        * ``1`` - uses SHA-2/256 digest (default).
 
50
        * ``2`` - uses SHA-2/384 digest.
 
51
        * ``3`` - uses SHA-2/512 digest.
 
52
 
 
53
    :type relaxed: bool
 
54
    :param relaxed:
 
55
        By default, providing an invalid value for one of the other
 
56
        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
 
57
        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
 
58
        will be issued instead. Correctable errors include ``rounds``
 
59
        that are too small or too large, and ``salt`` strings that are too long.
 
60
 
 
61
        .. versionadded:: 1.6
 
62
    """
 
63
 
 
64
    #===================================================================
 
65
    # class attrs
 
66
    #===================================================================
 
67
    #--GenericHandler--
 
68
    name = "fshp"
 
69
    setting_kwds = ("salt", "salt_size", "rounds", "variant")
 
70
    checksum_chars = uh.PADDED_BASE64_CHARS
 
71
    ident = u("{FSHP")
 
72
    # checksum_size is property() that depends on variant
 
73
 
 
74
    #--HasRawSalt--
 
75
    default_salt_size = 16 # current passlib default, FSHP uses 8
 
76
    min_salt_size = 0
 
77
    max_salt_size = None
 
78
 
 
79
    #--HasRounds--
 
80
    # FIXME: should probably use different default rounds
 
81
    # based on the variant. setting for default variant (sha256) for now.
 
82
    default_rounds = 50000 # current passlib default, FSHP uses 4096
 
83
    min_rounds = 1 # set by FSHP
 
84
    max_rounds = 4294967295 # 32-bit integer limit - not set by FSHP
 
85
    rounds_cost = "linear"
 
86
 
 
87
    #--variants--
 
88
    default_variant = 1
 
89
    _variant_info = {
 
90
        # variant: (hash name, digest size)
 
91
        0: ("sha1",     20),
 
92
        1: ("sha256",   32),
 
93
        2: ("sha384",   48),
 
94
        3: ("sha512",   64),
 
95
        }
 
96
    _variant_aliases = dict(
 
97
        [(unicode(k),k) for k in _variant_info] +
 
98
        [(v[0],k) for k,v in iteritems(_variant_info)]
 
99
        )
 
100
 
 
101
    #===================================================================
 
102
    # instance attrs
 
103
    #===================================================================
 
104
    variant = None
 
105
 
 
106
    #===================================================================
 
107
    # init
 
108
    #===================================================================
 
109
    def __init__(self, variant=None, **kwds):
 
110
        # NOTE: variant must be set first, since it controls checksum size, etc.
 
111
        self.use_defaults = kwds.get("use_defaults") # load this early
 
112
        self.variant = self._norm_variant(variant)
 
113
        super(fshp, self).__init__(**kwds)
 
114
 
 
115
    def _norm_variant(self, variant):
 
116
        if variant is None:
 
117
            if not self.use_defaults:
 
118
                raise TypeError("no variant specified")
 
119
            variant = self.default_variant
 
120
        if isinstance(variant, bytes):
 
121
            variant = variant.decode("ascii")
 
122
        if isinstance(variant, unicode):
 
123
            try:
 
124
                variant = self._variant_aliases[variant]
 
125
            except KeyError:
 
126
                raise ValueError("invalid fshp variant")
 
127
        if not isinstance(variant, int):
 
128
            raise TypeError("fshp variant must be int or known alias")
 
129
        if variant not in self._variant_info:
 
130
            raise ValueError("invalid fshp variant")
 
131
        return variant
 
132
 
 
133
    @property
 
134
    def checksum_alg(self):
 
135
        return self._variant_info[self.variant][0]
 
136
 
 
137
    @property
 
138
    def checksum_size(self):
 
139
        return self._variant_info[self.variant][1]
 
140
 
 
141
    #===================================================================
 
142
    # formatting
 
143
    #===================================================================
 
144
 
 
145
    _hash_regex = re.compile(u(r"""
 
146
            ^
 
147
            \{FSHP
 
148
            (\d+)\| # variant
 
149
            (\d+)\| # salt size
 
150
            (\d+)\} # rounds
 
151
            ([a-zA-Z0-9+/]+={0,3}) # digest
 
152
            $"""), re.X)
 
153
 
 
154
    @classmethod
 
155
    def from_string(cls, hash):
 
156
        hash = to_unicode(hash, "ascii", "hash")
 
157
        m = cls._hash_regex.match(hash)
 
158
        if not m:
 
159
            raise uh.exc.InvalidHashError(cls)
 
160
        variant, salt_size, rounds, data = m.group(1,2,3,4)
 
161
        variant = int(variant)
 
162
        salt_size = int(salt_size)
 
163
        rounds = int(rounds)
 
164
        try:
 
165
            data = b64decode(data.encode("ascii"))
 
166
        except TypeError:
 
167
            raise uh.exc.MalformedHashError(cls)
 
168
        salt = data[:salt_size]
 
169
        chk = data[salt_size:]
 
170
        return cls(salt=salt, checksum=chk, rounds=rounds, variant=variant)
 
171
 
 
172
    @property
 
173
    def _stub_checksum(self):
 
174
        return b('\x00') * self.checksum_size
 
175
 
 
176
    def to_string(self):
 
177
        chk = self.checksum or self._stub_checksum
 
178
        salt = self.salt
 
179
        data = bascii_to_str(b64encode(salt+chk))
 
180
        return "{FSHP%d|%d|%d}%s" % (self.variant, len(salt), self.rounds, data)
 
181
 
 
182
    #===================================================================
 
183
    # backend
 
184
    #===================================================================
 
185
 
 
186
    def _calc_checksum(self, secret):
 
187
        if isinstance(secret, unicode):
 
188
            secret = secret.encode("utf-8")
 
189
        # NOTE: for some reason, FSHP uses pbkdf1 with password & salt reversed.
 
190
        #       this has only a minimal impact on security,
 
191
        #       but it is worth noting this deviation.
 
192
        return pbkdf1(
 
193
            secret=self.salt,
 
194
            salt=secret,
 
195
            rounds=self.rounds,
 
196
            keylen=self.checksum_size,
 
197
            hash=self.checksum_alg,
 
198
            )
 
199
 
 
200
    #===================================================================
 
201
    # eoc
 
202
    #===================================================================
 
203
 
 
204
#=============================================================================
 
205
# eof
 
206
#=============================================================================