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

« back to all changes in this revision

Viewing changes to MoinMoin/support/passlib/handlers/des_crypt.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.des_crypt - traditional unix (DES) crypt and variants"""
 
2
#=============================================================================
 
3
# imports
 
4
#=============================================================================
 
5
# core
 
6
import re
 
7
import logging; log = logging.getLogger(__name__)
 
8
from warnings import warn
 
9
# site
 
10
# pkg
 
11
from passlib.utils import classproperty, h64, h64big, safe_crypt, test_crypt, to_unicode
 
12
from passlib.utils.compat import b, bytes, byte_elem_value, u, uascii_to_str, unicode
 
13
from passlib.utils.des import des_encrypt_int_block
 
14
import passlib.utils.handlers as uh
 
15
# local
 
16
__all__ = [
 
17
    "des_crypt",
 
18
    "bsdi_crypt",
 
19
    "bigcrypt",
 
20
    "crypt16",
 
21
]
 
22
 
 
23
#=============================================================================
 
24
# pure-python backend for des_crypt family
 
25
#=============================================================================
 
26
_BNULL = b('\x00')
 
27
 
 
28
def _crypt_secret_to_key(secret):
 
29
    """convert secret to 64-bit DES key.
 
30
 
 
31
    this only uses the first 8 bytes of the secret,
 
32
    and discards the high 8th bit of each byte at that.
 
33
    a null parity bit is inserted after every 7th bit of the output.
 
34
    """
 
35
    # NOTE: this would set the parity bits correctly,
 
36
    # but des_encrypt_int_block() would just ignore them...
 
37
    ##return sum(expand_7bit(byte_elem_value(c) & 0x7f) << (56-i*8)
 
38
    ##           for i, c in enumerate(secret[:8]))
 
39
    return sum((byte_elem_value(c) & 0x7f) << (57-i*8)
 
40
               for i, c in enumerate(secret[:8]))
 
41
 
 
42
def _raw_des_crypt(secret, salt):
 
43
    "pure-python backed for des_crypt"
 
44
    assert len(salt) == 2
 
45
 
 
46
    # NOTE: some OSes will accept non-HASH64 characters in the salt,
 
47
    # but what value they assign these characters varies wildy,
 
48
    # so just rejecting them outright.
 
49
    # NOTE: the same goes for single-character salts...
 
50
    # some OSes duplicate the char, some insert a '.' char,
 
51
    # and openbsd does something which creates an invalid hash.
 
52
    try:
 
53
        salt_value = h64.decode_int12(salt)
 
54
    except ValueError: # pragma: no cover - always caught by class
 
55
        raise ValueError("invalid chars in salt")
 
56
 
 
57
    # gotta do something - no official policy since this predates unicode
 
58
    if isinstance(secret, unicode):
 
59
        secret = secret.encode("utf-8")
 
60
    assert isinstance(secret, bytes)
 
61
 
 
62
    # forbidding NULL char because underlying crypt() rejects them too.
 
63
    if _BNULL in secret:
 
64
        raise uh.exc.NullPasswordError(des_crypt)
 
65
 
 
66
    # convert first 8 bytes of secret string into an integer
 
67
    key_value = _crypt_secret_to_key(secret)
 
68
 
 
69
    # run data through des using input of 0
 
70
    result = des_encrypt_int_block(key_value, 0, salt_value, 25)
 
71
 
 
72
    # run h64 encode on result
 
73
    return h64big.encode_int64(result)
 
74
 
 
75
def _bsdi_secret_to_key(secret):
 
76
    "covert secret to DES key used by bsdi_crypt"
 
77
    key_value = _crypt_secret_to_key(secret)
 
78
    idx = 8
 
79
    end = len(secret)
 
80
    while idx < end:
 
81
        next = idx+8
 
82
        tmp_value = _crypt_secret_to_key(secret[idx:next])
 
83
        key_value = des_encrypt_int_block(key_value, key_value) ^ tmp_value
 
84
        idx = next
 
85
    return key_value
 
86
 
 
87
def _raw_bsdi_crypt(secret, rounds, salt):
 
88
    "pure-python backend for bsdi_crypt"
 
89
 
 
90
    # decode salt
 
91
    try:
 
92
        salt_value = h64.decode_int24(salt)
 
93
    except ValueError: # pragma: no cover - always caught by class
 
94
        raise ValueError("invalid salt")
 
95
 
 
96
    # gotta do something - no official policy since this predates unicode
 
97
    if isinstance(secret, unicode):
 
98
        secret = secret.encode("utf-8")
 
99
    assert isinstance(secret, bytes)
 
100
 
 
101
    # forbidding NULL char because underlying crypt() rejects them too.
 
102
    if _BNULL in secret:
 
103
        raise uh.exc.NullPasswordError(bsdi_crypt)
 
104
 
 
105
    # convert secret string into an integer
 
106
    key_value = _bsdi_secret_to_key(secret)
 
107
 
 
108
    # run data through des using input of 0
 
109
    result = des_encrypt_int_block(key_value, 0, salt_value, rounds)
 
110
 
 
111
    # run h64 encode on result
 
112
    return h64big.encode_int64(result)
 
113
 
 
114
#=============================================================================
 
115
# handlers
 
116
#=============================================================================
 
117
class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
 
118
    """This class implements the des-crypt password hash, and follows the :ref:`password-hash-api`.
 
119
 
 
120
    It supports a fixed-length salt.
 
121
 
 
122
    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
 
123
 
 
124
    :type salt: str
 
125
    :param salt:
 
126
        Optional salt string.
 
127
        If not specified, one will be autogenerated (this is recommended).
 
128
        If specified, it must be 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
 
129
 
 
130
    :type relaxed: bool
 
131
    :param relaxed:
 
132
        By default, providing an invalid value for one of the other
 
133
        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
 
134
        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
 
135
        will be issued instead. Correctable errors include
 
136
        ``salt`` strings that are too long.
 
137
 
 
138
        .. versionadded:: 1.6
 
139
    """
 
140
    #===================================================================
 
141
    # class attrs
 
142
    #===================================================================
 
143
    #--GenericHandler--
 
144
    name = "des_crypt"
 
145
    setting_kwds = ("salt",)
 
146
    checksum_chars = uh.HASH64_CHARS
 
147
    checksum_size = 11
 
148
 
 
149
    #--HasSalt--
 
150
    min_salt_size = max_salt_size = 2
 
151
    salt_chars = uh.HASH64_CHARS
 
152
 
 
153
    #===================================================================
 
154
    # formatting
 
155
    #===================================================================
 
156
    # FORMAT: 2 chars of H64-encoded salt + 11 chars of H64-encoded checksum
 
157
 
 
158
    _hash_regex = re.compile(u(r"""
 
159
        ^
 
160
        (?P<salt>[./a-z0-9]{2})
 
161
        (?P<chk>[./a-z0-9]{11})?
 
162
        $"""), re.X|re.I)
 
163
 
 
164
    @classmethod
 
165
    def from_string(cls, hash):
 
166
        hash = to_unicode(hash, "ascii", "hash")
 
167
        salt, chk = hash[:2], hash[2:]
 
168
        return cls(salt=salt, checksum=chk or None)
 
169
 
 
170
    def to_string(self):
 
171
        hash = u("%s%s") % (self.salt, self.checksum or u(''))
 
172
        return uascii_to_str(hash)
 
173
 
 
174
    #===================================================================
 
175
    # backend
 
176
    #===================================================================
 
177
    backends = ("os_crypt", "builtin")
 
178
 
 
179
    _has_backend_builtin = True
 
180
 
 
181
    @classproperty
 
182
    def _has_backend_os_crypt(cls):
 
183
        return test_crypt("test", 'abgOeLfPimXQo')
 
184
 
 
185
    def _calc_checksum_builtin(self, secret):
 
186
        return _raw_des_crypt(secret, self.salt.encode("ascii")).decode("ascii")
 
187
 
 
188
    def _calc_checksum_os_crypt(self, secret):
 
189
        # NOTE: safe_crypt encodes unicode secret -> utf8
 
190
        # no official policy since des-crypt predates unicode
 
191
        hash = safe_crypt(secret, self.salt)
 
192
        if hash:
 
193
            assert hash.startswith(self.salt) and len(hash) == 13
 
194
            return hash[2:]
 
195
        else:
 
196
            return self._calc_checksum_builtin(secret)
 
197
 
 
198
    #===================================================================
 
199
    # eoc
 
200
    #===================================================================
 
201
 
 
202
class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
 
203
    """This class implements the BSDi-Crypt password hash, and follows the :ref:`password-hash-api`.
 
204
 
 
205
    It supports a fixed-length salt, and a variable number of rounds.
 
206
 
 
207
    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
 
208
 
 
209
    :type salt: str
 
210
    :param salt:
 
211
        Optional salt string.
 
212
        If not specified, one will be autogenerated (this is recommended).
 
213
        If specified, it must be 4 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
 
214
 
 
215
    :type rounds: int
 
216
    :param rounds:
 
217
        Optional number of rounds to use.
 
218
        Defaults to 5001, must be between 1 and 16777215, inclusive.
 
219
 
 
220
    :type relaxed: bool
 
221
    :param relaxed:
 
222
        By default, providing an invalid value for one of the other
 
223
        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
 
224
        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
 
225
        will be issued instead. Correctable errors include ``rounds``
 
226
        that are too small or too large, and ``salt`` strings that are too long.
 
227
 
 
228
        .. versionadded:: 1.6
 
229
 
 
230
    .. versionchanged:: 1.6
 
231
        :meth:`encrypt` will now issue a warning if an even number of rounds is used
 
232
        (see :ref:`bsdi-crypt-security-issues` regarding weak DES keys).
 
233
    """
 
234
    #===================================================================
 
235
    # class attrs
 
236
    #===================================================================
 
237
    #--GenericHandler--
 
238
    name = "bsdi_crypt"
 
239
    setting_kwds = ("salt", "rounds")
 
240
    checksum_size = 11
 
241
    checksum_chars = uh.HASH64_CHARS
 
242
 
 
243
    #--HasSalt--
 
244
    min_salt_size = max_salt_size = 4
 
245
    salt_chars = uh.HASH64_CHARS
 
246
 
 
247
    #--HasRounds--
 
248
    default_rounds = 5001
 
249
    min_rounds = 1
 
250
    max_rounds = 16777215 # (1<<24)-1
 
251
    rounds_cost = "linear"
 
252
 
 
253
    # NOTE: OpenBSD login.conf reports 7250 as minimum allowed rounds,
 
254
    # but that seems to be an OS policy, not a algorithm limitation.
 
255
 
 
256
    #===================================================================
 
257
    # parsing
 
258
    #===================================================================
 
259
    _hash_regex = re.compile(u(r"""
 
260
        ^
 
261
        _
 
262
        (?P<rounds>[./a-z0-9]{4})
 
263
        (?P<salt>[./a-z0-9]{4})
 
264
        (?P<chk>[./a-z0-9]{11})?
 
265
        $"""), re.X|re.I)
 
266
 
 
267
    @classmethod
 
268
    def from_string(cls, hash):
 
269
        hash = to_unicode(hash, "ascii", "hash")
 
270
        m = cls._hash_regex.match(hash)
 
271
        if not m:
 
272
            raise uh.exc.InvalidHashError(cls)
 
273
        rounds, salt, chk = m.group("rounds", "salt", "chk")
 
274
        return cls(
 
275
            rounds=h64.decode_int24(rounds.encode("ascii")),
 
276
            salt=salt,
 
277
            checksum=chk,
 
278
        )
 
279
 
 
280
    def to_string(self):
 
281
        hash = u("_%s%s%s") % (h64.encode_int24(self.rounds).decode("ascii"),
 
282
                             self.salt, self.checksum or u(''))
 
283
        return uascii_to_str(hash)
 
284
 
 
285
    #===================================================================
 
286
    # validation
 
287
    #===================================================================
 
288
 
 
289
    # flag so CryptContext won't generate even rounds.
 
290
    _avoid_even_rounds = True
 
291
 
 
292
    def _norm_rounds(self, rounds):
 
293
        rounds = super(bsdi_crypt, self)._norm_rounds(rounds)
 
294
        # issue warning if app provided an even rounds value
 
295
        if self.use_defaults and not rounds & 1:
 
296
            warn("bsdi_crypt rounds should be odd, "
 
297
                 "as even rounds may reveal weak DES keys",
 
298
                 uh.exc.PasslibSecurityWarning)
 
299
        return rounds
 
300
 
 
301
    @classmethod
 
302
    def _bind_needs_update(cls, **settings):
 
303
        return cls._needs_update
 
304
 
 
305
    @classmethod
 
306
    def _needs_update(cls, hash, secret):
 
307
        # mark bsdi_crypt hashes as deprecated if they have even rounds.
 
308
        assert cls.identify(hash)
 
309
        if isinstance(hash, unicode):
 
310
            hash = hash.encode("ascii")
 
311
        rounds = h64.decode_int24(hash[1:5])
 
312
        return not rounds & 1
 
313
 
 
314
    #===================================================================
 
315
    # backends
 
316
    #===================================================================
 
317
    backends = ("os_crypt", "builtin")
 
318
 
 
319
    _has_backend_builtin = True
 
320
 
 
321
    @classproperty
 
322
    def _has_backend_os_crypt(cls):
 
323
        return test_crypt("test", '_/...lLDAxARksGCHin.')
 
324
 
 
325
    def _calc_checksum_builtin(self, secret):
 
326
        return _raw_bsdi_crypt(secret, self.rounds, self.salt.encode("ascii")).decode("ascii")
 
327
 
 
328
    def _calc_checksum_os_crypt(self, secret):
 
329
        config = self.to_string()
 
330
        hash = safe_crypt(secret, config)
 
331
        if hash:
 
332
            assert hash.startswith(config[:9]) and len(hash) == 20
 
333
            return hash[-11:]
 
334
        else:
 
335
            return self._calc_checksum_builtin(secret)
 
336
 
 
337
    #===================================================================
 
338
    # eoc
 
339
    #===================================================================
 
340
 
 
341
class bigcrypt(uh.HasSalt, uh.GenericHandler):
 
342
    """This class implements the BigCrypt password hash, and follows the :ref:`password-hash-api`.
 
343
 
 
344
    It supports a fixed-length salt.
 
345
 
 
346
    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
 
347
 
 
348
    :type salt: str
 
349
    :param salt:
 
350
        Optional salt string.
 
351
        If not specified, one will be autogenerated (this is recommended).
 
352
        If specified, it must be 22 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
 
353
 
 
354
    :type relaxed: bool
 
355
    :param relaxed:
 
356
        By default, providing an invalid value for one of the other
 
357
        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
 
358
        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
 
359
        will be issued instead. Correctable errors include
 
360
        ``salt`` strings that are too long.
 
361
 
 
362
        .. versionadded:: 1.6
 
363
    """
 
364
    #===================================================================
 
365
    # class attrs
 
366
    #===================================================================
 
367
    #--GenericHandler--
 
368
    name = "bigcrypt"
 
369
    setting_kwds = ("salt",)
 
370
    checksum_chars = uh.HASH64_CHARS
 
371
    # NOTE: checksum chars must be multiple of 11
 
372
 
 
373
    #--HasSalt--
 
374
    min_salt_size = max_salt_size = 2
 
375
    salt_chars = uh.HASH64_CHARS
 
376
 
 
377
    #===================================================================
 
378
    # internal helpers
 
379
    #===================================================================
 
380
    _hash_regex = re.compile(u(r"""
 
381
        ^
 
382
        (?P<salt>[./a-z0-9]{2})
 
383
        (?P<chk>([./a-z0-9]{11})+)?
 
384
        $"""), re.X|re.I)
 
385
 
 
386
    @classmethod
 
387
    def from_string(cls, hash):
 
388
        hash = to_unicode(hash, "ascii", "hash")
 
389
        m = cls._hash_regex.match(hash)
 
390
        if not m:
 
391
            raise uh.exc.InvalidHashError(cls)
 
392
        salt, chk = m.group("salt", "chk")
 
393
        return cls(salt=salt, checksum=chk)
 
394
 
 
395
    def to_string(self):
 
396
        hash = u("%s%s") % (self.salt, self.checksum or u(''))
 
397
        return uascii_to_str(hash)
 
398
 
 
399
    def _norm_checksum(self, value):
 
400
        value = super(bigcrypt, self)._norm_checksum(value)
 
401
        if value and len(value) % 11:
 
402
            raise uh.exc.InvalidHashError(self)
 
403
        return value
 
404
 
 
405
    #===================================================================
 
406
    # backend
 
407
    #===================================================================
 
408
    def _calc_checksum(self, secret):
 
409
        if isinstance(secret, unicode):
 
410
            secret = secret.encode("utf-8")
 
411
        chk = _raw_des_crypt(secret, self.salt.encode("ascii"))
 
412
        idx = 8
 
413
        end = len(secret)
 
414
        while idx < end:
 
415
            next = idx + 8
 
416
            chk += _raw_des_crypt(secret[idx:next], chk[-11:-9])
 
417
            idx = next
 
418
        return chk.decode("ascii")
 
419
 
 
420
    #===================================================================
 
421
    # eoc
 
422
    #===================================================================
 
423
 
 
424
class crypt16(uh.HasSalt, uh.GenericHandler):
 
425
    """This class implements the crypt16 password hash, and follows the :ref:`password-hash-api`.
 
426
 
 
427
    It supports a fixed-length salt.
 
428
 
 
429
    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
 
430
 
 
431
    :type salt: str
 
432
    :param salt:
 
433
        Optional salt string.
 
434
        If not specified, one will be autogenerated (this is recommended).
 
435
        If specified, it must be 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
 
436
 
 
437
    :type relaxed: bool
 
438
    :param relaxed:
 
439
        By default, providing an invalid value for one of the other
 
440
        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
 
441
        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
 
442
        will be issued instead. Correctable errors include
 
443
        ``salt`` strings that are too long.
 
444
 
 
445
        .. versionadded:: 1.6
 
446
    """
 
447
    #===================================================================
 
448
    # class attrs
 
449
    #===================================================================
 
450
    #--GenericHandler--
 
451
    name = "crypt16"
 
452
    setting_kwds = ("salt",)
 
453
    checksum_size = 22
 
454
    checksum_chars = uh.HASH64_CHARS
 
455
 
 
456
    #--HasSalt--
 
457
    min_salt_size = max_salt_size = 2
 
458
    salt_chars = uh.HASH64_CHARS
 
459
 
 
460
    #===================================================================
 
461
    # internal helpers
 
462
    #===================================================================
 
463
    _hash_regex = re.compile(u(r"""
 
464
        ^
 
465
        (?P<salt>[./a-z0-9]{2})
 
466
        (?P<chk>[./a-z0-9]{22})?
 
467
        $"""), re.X|re.I)
 
468
 
 
469
    @classmethod
 
470
    def from_string(cls, hash):
 
471
        hash = to_unicode(hash, "ascii", "hash")
 
472
        m = cls._hash_regex.match(hash)
 
473
        if not m:
 
474
            raise uh.exc.InvalidHashError(cls)
 
475
        salt, chk = m.group("salt", "chk")
 
476
        return cls(salt=salt, checksum=chk)
 
477
 
 
478
    def to_string(self):
 
479
        hash = u("%s%s") % (self.salt, self.checksum or u(''))
 
480
        return uascii_to_str(hash)
 
481
 
 
482
    #===================================================================
 
483
    # backend
 
484
    #===================================================================
 
485
    def _calc_checksum(self, secret):
 
486
        if isinstance(secret, unicode):
 
487
            secret = secret.encode("utf-8")
 
488
 
 
489
        # parse salt value
 
490
        try:
 
491
            salt_value = h64.decode_int12(self.salt.encode("ascii"))
 
492
        except ValueError: # pragma: no cover - caught by class
 
493
            raise ValueError("invalid chars in salt")
 
494
 
 
495
        # convert first 8 byts of secret string into an integer,
 
496
        key1 = _crypt_secret_to_key(secret)
 
497
 
 
498
        # run data through des using input of 0
 
499
        result1 = des_encrypt_int_block(key1, 0, salt_value, 20)
 
500
 
 
501
        # convert next 8 bytes of secret string into integer (key=0 if secret < 8 chars)
 
502
        key2 = _crypt_secret_to_key(secret[8:16])
 
503
 
 
504
        # run data through des using input of 0
 
505
        result2 = des_encrypt_int_block(key2, 0, salt_value, 5)
 
506
 
 
507
        # done
 
508
        chk = h64big.encode_int64(result1) + h64big.encode_int64(result2)
 
509
        return chk.decode("ascii")
 
510
 
 
511
    #===================================================================
 
512
    # eoc
 
513
    #===================================================================
 
514
 
 
515
#=============================================================================
 
516
# eof
 
517
#=============================================================================