summaryrefslogtreecommitdiff
path: root/passlib/hash/sha1_crypt.py
blob: da332bd13bd3b9b5a3e79f66aadcffe14b266bca (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
"""passlib.hash.sha1_crypt
"""

#=========================================================
#imports
#=========================================================
from __future__ import with_statement, absolute_import
#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 autodocument, h64
from passlib.utils.handlers import ExtHandler
from passlib.utils.pbkdf2 import hmac_sha1
from passlib.base import register_crypt_handler
#pkg
#local
__all__ = [
]
#=========================================================
#sha1-crypt
#=========================================================
class SHA1Crypt(ExtHandler):

    #=========================================================
    #class attrs
    #=========================================================
    name = "sha1_crypt"
    setting_kwds = ("salt", "rounds")

    default_salt_chars = 8
    min_salt_chars = 0
    max_salt_chars = 64

    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 identify(cls, hash):
        return bool(hash) and hash.startswith("$sha1$")

    _pat = re.compile(r"""
        ^
        \$sha1
        \$(?P<rounds>\d+)
        \$(?P<salt>[A-Za-z0-9./]{0,64})
        (\$(?P<chk>[A-Za-z0-9./]{28})?)?
        $
        """, re.X)

    @classmethod
    def from_string(cls, hash):
        if not hash:
            raise ValueError, "no hash specified"
        m = cls._pat.match(hash)
        if not m:
            raise ValueError, "invalid sha1_crypt hash"
        rounds, salt, chk = m.group("rounds", "salt", "chk")
        if rounds.startswith("0"):
            raise ValueError, "invalid sha1-crypt hash (zero-padded rounds)"
        return cls(
            rounds=int(rounds),
            salt=salt,
            checksum=chk,
            strict=bool(chk),
        )

    def to_string(self):
        out = "$sha1$%d$%s" % (self.rounds, self.salt)
        if self.checksum:
            out += "$" + self.checksum
        return out

    #=========================================================
    #backend
    #=========================================================
    def calc_checksum(self, secret):
        if isinstance(secret, unicode):
            secret = secret.encode("utf-8")
        rounds = self.rounds
        result = "%s$sha1$%s" % (self.salt, rounds)
        r = 0
        while r < rounds:
            result = hmac_sha1(secret, result)
            r += 1
        return h64.encode_transposed_bytes(result, self._chk_offsets)

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

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

autodocument(SHA1Crypt)
register_crypt_handler(SHA1Crypt)
#=========================================================
#eof
#=========================================================