1
"""passlib.handlers.des_crypt - traditional unix (DES) crypt and variants"""
2
#=============================================================================
4
#=============================================================================
7
import logging; log = logging.getLogger(__name__)
8
from warnings import warn
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
23
#=============================================================================
24
# pure-python backend for des_crypt family
25
#=============================================================================
28
def _crypt_secret_to_key(secret):
29
"""convert secret to 64-bit DES key.
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.
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]))
42
def _raw_des_crypt(secret, salt):
43
"pure-python backed for des_crypt"
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.
53
salt_value = h64.decode_int12(salt)
54
except ValueError: # pragma: no cover - always caught by class
55
raise ValueError("invalid chars in salt")
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)
62
# forbidding NULL char because underlying crypt() rejects them too.
64
raise uh.exc.NullPasswordError(des_crypt)
66
# convert first 8 bytes of secret string into an integer
67
key_value = _crypt_secret_to_key(secret)
69
# run data through des using input of 0
70
result = des_encrypt_int_block(key_value, 0, salt_value, 25)
72
# run h64 encode on result
73
return h64big.encode_int64(result)
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)
82
tmp_value = _crypt_secret_to_key(secret[idx:next])
83
key_value = des_encrypt_int_block(key_value, key_value) ^ tmp_value
87
def _raw_bsdi_crypt(secret, rounds, salt):
88
"pure-python backend for bsdi_crypt"
92
salt_value = h64.decode_int24(salt)
93
except ValueError: # pragma: no cover - always caught by class
94
raise ValueError("invalid salt")
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)
101
# forbidding NULL char because underlying crypt() rejects them too.
103
raise uh.exc.NullPasswordError(bsdi_crypt)
105
# convert secret string into an integer
106
key_value = _bsdi_secret_to_key(secret)
108
# run data through des using input of 0
109
result = des_encrypt_int_block(key_value, 0, salt_value, rounds)
111
# run h64 encode on result
112
return h64big.encode_int64(result)
114
#=============================================================================
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`.
120
It supports a fixed-length salt.
122
The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
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]``.
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.
138
.. versionadded:: 1.6
140
#===================================================================
142
#===================================================================
145
setting_kwds = ("salt",)
146
checksum_chars = uh.HASH64_CHARS
150
min_salt_size = max_salt_size = 2
151
salt_chars = uh.HASH64_CHARS
153
#===================================================================
155
#===================================================================
156
# FORMAT: 2 chars of H64-encoded salt + 11 chars of H64-encoded checksum
158
_hash_regex = re.compile(u(r"""
160
(?P<salt>[./a-z0-9]{2})
161
(?P<chk>[./a-z0-9]{11})?
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)
171
hash = u("%s%s") % (self.salt, self.checksum or u(''))
172
return uascii_to_str(hash)
174
#===================================================================
176
#===================================================================
177
backends = ("os_crypt", "builtin")
179
_has_backend_builtin = True
182
def _has_backend_os_crypt(cls):
183
return test_crypt("test", 'abgOeLfPimXQo')
185
def _calc_checksum_builtin(self, secret):
186
return _raw_des_crypt(secret, self.salt.encode("ascii")).decode("ascii")
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)
193
assert hash.startswith(self.salt) and len(hash) == 13
196
return self._calc_checksum_builtin(secret)
198
#===================================================================
200
#===================================================================
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`.
205
It supports a fixed-length salt, and a variable number of rounds.
207
The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
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]``.
217
Optional number of rounds to use.
218
Defaults to 5001, must be between 1 and 16777215, inclusive.
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.
228
.. versionadded:: 1.6
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).
234
#===================================================================
236
#===================================================================
239
setting_kwds = ("salt", "rounds")
241
checksum_chars = uh.HASH64_CHARS
244
min_salt_size = max_salt_size = 4
245
salt_chars = uh.HASH64_CHARS
248
default_rounds = 5001
250
max_rounds = 16777215 # (1<<24)-1
251
rounds_cost = "linear"
253
# NOTE: OpenBSD login.conf reports 7250 as minimum allowed rounds,
254
# but that seems to be an OS policy, not a algorithm limitation.
256
#===================================================================
258
#===================================================================
259
_hash_regex = re.compile(u(r"""
262
(?P<rounds>[./a-z0-9]{4})
263
(?P<salt>[./a-z0-9]{4})
264
(?P<chk>[./a-z0-9]{11})?
268
def from_string(cls, hash):
269
hash = to_unicode(hash, "ascii", "hash")
270
m = cls._hash_regex.match(hash)
272
raise uh.exc.InvalidHashError(cls)
273
rounds, salt, chk = m.group("rounds", "salt", "chk")
275
rounds=h64.decode_int24(rounds.encode("ascii")),
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)
285
#===================================================================
287
#===================================================================
289
# flag so CryptContext won't generate even rounds.
290
_avoid_even_rounds = True
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)
302
def _bind_needs_update(cls, **settings):
303
return cls._needs_update
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
314
#===================================================================
316
#===================================================================
317
backends = ("os_crypt", "builtin")
319
_has_backend_builtin = True
322
def _has_backend_os_crypt(cls):
323
return test_crypt("test", '_/...lLDAxARksGCHin.')
325
def _calc_checksum_builtin(self, secret):
326
return _raw_bsdi_crypt(secret, self.rounds, self.salt.encode("ascii")).decode("ascii")
328
def _calc_checksum_os_crypt(self, secret):
329
config = self.to_string()
330
hash = safe_crypt(secret, config)
332
assert hash.startswith(config[:9]) and len(hash) == 20
335
return self._calc_checksum_builtin(secret)
337
#===================================================================
339
#===================================================================
341
class bigcrypt(uh.HasSalt, uh.GenericHandler):
342
"""This class implements the BigCrypt password hash, and follows the :ref:`password-hash-api`.
344
It supports a fixed-length salt.
346
The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
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]``.
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.
362
.. versionadded:: 1.6
364
#===================================================================
366
#===================================================================
369
setting_kwds = ("salt",)
370
checksum_chars = uh.HASH64_CHARS
371
# NOTE: checksum chars must be multiple of 11
374
min_salt_size = max_salt_size = 2
375
salt_chars = uh.HASH64_CHARS
377
#===================================================================
379
#===================================================================
380
_hash_regex = re.compile(u(r"""
382
(?P<salt>[./a-z0-9]{2})
383
(?P<chk>([./a-z0-9]{11})+)?
387
def from_string(cls, hash):
388
hash = to_unicode(hash, "ascii", "hash")
389
m = cls._hash_regex.match(hash)
391
raise uh.exc.InvalidHashError(cls)
392
salt, chk = m.group("salt", "chk")
393
return cls(salt=salt, checksum=chk)
396
hash = u("%s%s") % (self.salt, self.checksum or u(''))
397
return uascii_to_str(hash)
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)
405
#===================================================================
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"))
416
chk += _raw_des_crypt(secret[idx:next], chk[-11:-9])
418
return chk.decode("ascii")
420
#===================================================================
422
#===================================================================
424
class crypt16(uh.HasSalt, uh.GenericHandler):
425
"""This class implements the crypt16 password hash, and follows the :ref:`password-hash-api`.
427
It supports a fixed-length salt.
429
The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
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]``.
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.
445
.. versionadded:: 1.6
447
#===================================================================
449
#===================================================================
452
setting_kwds = ("salt",)
454
checksum_chars = uh.HASH64_CHARS
457
min_salt_size = max_salt_size = 2
458
salt_chars = uh.HASH64_CHARS
460
#===================================================================
462
#===================================================================
463
_hash_regex = re.compile(u(r"""
465
(?P<salt>[./a-z0-9]{2})
466
(?P<chk>[./a-z0-9]{22})?
470
def from_string(cls, hash):
471
hash = to_unicode(hash, "ascii", "hash")
472
m = cls._hash_regex.match(hash)
474
raise uh.exc.InvalidHashError(cls)
475
salt, chk = m.group("salt", "chk")
476
return cls(salt=salt, checksum=chk)
479
hash = u("%s%s") % (self.salt, self.checksum or u(''))
480
return uascii_to_str(hash)
482
#===================================================================
484
#===================================================================
485
def _calc_checksum(self, secret):
486
if isinstance(secret, unicode):
487
secret = secret.encode("utf-8")
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")
495
# convert first 8 byts of secret string into an integer,
496
key1 = _crypt_secret_to_key(secret)
498
# run data through des using input of 0
499
result1 = des_encrypt_int_block(key1, 0, salt_value, 20)
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])
504
# run data through des using input of 0
505
result2 = des_encrypt_int_block(key2, 0, salt_value, 5)
508
chk = h64big.encode_int64(result1) + h64big.encode_int64(result2)
509
return chk.decode("ascii")
511
#===================================================================
513
#===================================================================
515
#=============================================================================
517
#=============================================================================