3 # phpass version: 0.3 / genuine.
5 # Placed in public domain
8 #CHECK: use pyDES instead of the native crypt module?
18 _bcrypt_hashpw = bcrypt.hashpw
22 # On App Engine, this function is not available.
23 if hasattr(os, 'getpid'):
28 _pid = random.randint(0, 100000)
33 def __init__(self, iteration_count_log2=8, portable_hashes=True,
35 alg = algorithm.lower()
36 if (alg == 'blowfish' or alg == 'bcrypt') and _bcrypt_hashpw is None:
37 raise NotImplementedError('The bcrypt module is required')
39 './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
40 if iteration_count_log2 < 4 or iteration_count_log2 > 31:
41 iteration_count_log2 = 8
42 self.iteration_count_log2 = iteration_count_log2
43 self.portable_hashes = portable_hashes
44 self.algorithm = algorithm
45 self.random_state = '%r%r' % (time.time(), _pid)
47 def get_random_bytes(self, count):
50 outp = os.urandom(count)
57 self.random_state = hashlib.md5(str(time.time())
58 + self.random_state).hexdigest()
59 outp += hashlib.md5(self.random_state).digest()
64 def encode64(self, inp, count):
70 outp += self.itoa64[value & 0x3f]
72 value |= (ord(inp[cur]) << 8)
73 outp += self.itoa64[(value >> 6) & 0x3f]
78 value |= (ord(inp[cur]) << 16)
79 outp += self.itoa64[(value >> 12) & 0x3f]
83 outp += self.itoa64[(value >> 18) & 0x3f]
86 def gensalt_private(self, inp):
88 outp += self.itoa64[min([self.iteration_count_log2 + 5, 30])]
89 outp += self.encode64(inp, 6)
92 def crypt_private(self, pw, setting):
94 if setting.startswith(outp):
96 if not setting.startswith('$P$') and not setting.startswith('$H$'):
98 count_log2 = self.itoa64.find(setting[3])
99 if count_log2 < 7 or count_log2 > 30:
101 count = 1 << count_log2
105 if not isinstance(pw, str):
106 pw = pw.encode('utf-8')
107 hx = hashlib.md5(salt + pw).digest()
109 hx = hashlib.md5(hx + pw).digest()
111 return setting[:12] + self.encode64(hx, 16)
113 def gensalt_extended(self, inp):
114 count_log2 = min([self.iteration_count_log2 + 8, 24])
115 count = (1 << count_log2) - 1
117 outp += self.itoa64[count & 0x3f]
118 outp += self.itoa64[(count >> 6) & 0x3f]
119 outp += self.itoa64[(count >> 12) & 0x3f]
120 outp += self.itoa64[(count >> 18) & 0x3f]
121 outp += self.encode64(inp, 3)
124 def gensalt_blowfish(self, inp):
126 './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
128 outp += chr(ord('0') + self.iteration_count_log2 / 10)
129 outp += chr(ord('0') + self.iteration_count_log2 % 10)
135 outp += itoa64[c1 >> 2]
136 c1 = (c1 & 0x03) << 4
144 c1 = (c2 & 0x0f) << 2
149 outp += itoa64[c2 & 0x3f]
152 def hash_password(self, pw):
154 alg = self.algorithm.lower()
155 if (not alg or alg == 'blowfish' or alg == 'bcrypt') \
156 and not self.portable_hashes:
157 if _bcrypt_hashpw is None:
158 if (alg == 'blowfish' or alg == 'bcrypt'):
159 raise NotImplementedError('The bcrypt module is required')
161 rnd = self.get_random_bytes(16)
162 salt = self.gensalt_blowfish(rnd)
163 hx = _bcrypt_hashpw(pw, salt)
166 if (not alg or alg == 'ext-des') and not self.portable_hashes:
168 rnd = self.get_random_bytes(3)
169 hx = crypt.crypt(pw, self.gensalt_extended(rnd))
173 rnd = self.get_random_bytes(6)
174 hx = self.crypt_private(pw, self.gensalt_private(rnd))
179 def check_password(self, pw, stored_hash):
180 # This part is different with the original PHP
181 if stored_hash.startswith('$2a$'):
183 if _bcrypt_hashpw is None:
184 raise NotImplementedError('The bcrypt module is required')
185 hx = _bcrypt_hashpw(pw, stored_hash)
186 elif stored_hash.startswith('_'):
188 hx = crypt.crypt(pw, stored_hash)
191 hx = self.crypt_private(pw, stored_hash)
192 return hx == stored_hash