summaryrefslogtreecommitdiff
path: root/passlib/handlers/sha1_crypt.py
blob: 5098a7284745b594e66c044e9fc4ecd5192a6081 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
"""passlib.handlers.sha1_crypt
"""

#=========================================================
#imports
#=========================================================

#core
from hmac import new as hmac
from hashlib import sha1
import re
import logging; log = logging.getLogger(__name__)
from warnings import warn
#site
#libs
from passlib.utils import classproperty, h64, safe_crypt, test_crypt
from passlib.utils.compat import b, bytes, u, uascii_to_str, unicode
from passlib.utils.pbkdf2 import get_prf
import passlib.utils.handlers as uh
#pkg
#local
__all__ = [
]
#=========================================================
#sha1-crypt
#=========================================================
_hmac_sha1 = get_prf("hmac-sha1")[0]
_BNULL = b('\x00')

class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
    """This class implements the SHA1-Crypt password hash, and follows the :ref:`password-hash-api`.

    It supports a variable-length salt, and a variable number of rounds.

    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:

    :type salt: str
    :param salt:
        Optional salt string.
        If not specified, an 8 character one will be autogenerated (this is recommended).
        If specified, it must be 0-64 characters, drawn from the regexp range ``[./0-9A-Za-z]``.

    :type salt_size: int
    :param salt_size:
        Optional number of bytes to use when autogenerating new salts.
        Defaults to 8 bytes, but can be any value between 0 and 64.

    :type rounds: int
    :param rounds:
        Optional number of rounds to use.
        Defaults to 40000, must be between 1 and 4294967295, inclusive.

    :type relaxed: bool
    :param relaxed:
        By default, providing an invalid value for one of the other
        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
        will be issued instead. Correctable errors include ``rounds``
        that are too small or too large, and ``salt`` strings that are too long.

        .. versionadded:: 1.6
    """

    #=========================================================
    #class attrs
    #=========================================================
    #--GenericHandler--
    name = "sha1_crypt"
    setting_kwds = ("salt", "salt_size", "rounds")
    ident = u("$sha1$")
    checksum_size = 28
    checksum_chars = uh.HASH64_CHARS

    #--HasSalt--
    default_salt_size = 8
    min_salt_size = 0
    max_salt_size = 64
    salt_chars = uh.HASH64_CHARS

    #--HasRounds--
    default_rounds = 40000 #current passlib default
    min_rounds = 1 #really, this should be higher.
    max_rounds = 4294967295 # 32-bit integer limit
    rounds_cost = "linear"

    #=========================================================
    #formatting
    #=========================================================

    @classmethod
    def from_string(cls, hash):
        rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls)
        return cls(rounds=rounds, salt=salt, checksum=chk)

    def to_string(self, config=False):
        chk = None if config else self.checksum
        return uh.render_mc3(self.ident, self.rounds, self.salt, chk)

    #=========================================================
    #backend
    #=========================================================
    backends = ("os_crypt", "builtin")

    _has_backend_builtin = True

    @classproperty
    def _has_backend_os_crypt(cls):
        return test_crypt("test", '$sha1$1$Wq3GL2Vp$C8U25GvfHS8qGHim'
                                          'ExLaiSFlGkAe')

    def _calc_checksum_builtin(self, secret):
        if isinstance(secret, unicode):
            secret = secret.encode("utf-8")
        if _BNULL in secret:
            raise uh.exc.NullPasswordError(self)
        rounds = self.rounds
        # NOTE: this seed value is NOT the same as the config string
        result = (u("%s$sha1$%s") % (self.salt, rounds)).encode("ascii")
        # NOTE: this algorithm is essentially PBKDF1, modified to use HMAC.
        r = 0
        while r < rounds:
            result = _hmac_sha1(secret, result)
            r += 1
        return h64.encode_transposed_bytes(result, self._chk_offsets).decode("ascii")

    _chk_offsets = [
        2,1,0,
        5,4,3,
        8,7,6,
        11,10,9,
        14,13,12,
        17,16,15,
        0,19,18,
    ]

    def _calc_checksum_os_crypt(self, secret):
        config = self.to_string(config=True)
        hash = safe_crypt(secret, config)
        if hash:
            assert hash.startswith(config) and len(hash) == len(config) + 29
            return hash[-28:]
        else:
            return self._calc_checksum_builtin(secret)

    #=========================================================
    #eoc
    #=========================================================

#=========================================================
#eof
#=========================================================