Package ldaptor :: Module md4
[hide private]
[frames] | no frames]

Source Code for Module ldaptor.md4

  1  """ 
  2  helper implementing insecure and obsolete md4 algorithm. 
  3  used for NTHASH format, which is also insecure and broken, 
  4  since it's just md4(password) 
  5   
  6  implementated based on rfc at http://www.faqs.org/rfcs/rfc1320.html 
  7   
  8  """ 
  9  # Passlib is (c) `Assurance Technologies <http://www.assurancetechnologies.com>` 
 10  # and is released under the `BSD license <http://www.opensource.org/licenses/bsd-license.php>` 
 11   
 12  #============================================================================= 
 13  # imports 
 14  #============================================================================= 
 15  # core 
 16  from binascii import hexlify 
 17  import struct 
 18  from warnings import warn 
 19  # site 
 20  from compat import b, bytes, bascii_to_str, irange, PY3 
 21  # local 
 22  __all__ = [ "md4" ] 
 23  #============================================================================= 
 24  # utils 
 25  #============================================================================= 
26 -def F(x,y,z):
27 return (x&y) | ((~x) & z)
28
29 -def G(x,y,z):
30 return (x&y) | (x&z) | (y&z)
31 32 ##def H(x,y,z): 33 ## return x ^ y ^ z 34 35 MASK_32 = 2**32-1 36 37 #============================================================================= 38 # main class 39 #=============================================================================
40 -class md4(object):
41 """pep-247 compatible implementation of MD4 hash algorithm 42 43 .. attribute:: digest_size 44 45 size of md4 digest in bytes (16 bytes) 46 47 .. method:: update 48 49 update digest by appending additional content 50 51 .. method:: copy 52 53 create clone of digest object, including current state 54 55 .. method:: digest 56 57 return bytes representing md4 digest of current content 58 59 .. method:: hexdigest 60 61 return hexdecimal version of digest 62 """ 63 # FIXME: make this follow hash object PEP better. 64 # FIXME: this isn't threadsafe 65 # XXX: should we monkeypatch ourselves into hashlib for general use? probably wouldn't be nice. 66 67 name = "md4" 68 digest_size = digestsize = 16 69 70 _count = 0 # number of 64-byte blocks processed so far (not including _buf) 71 _state = None # list of [a,b,c,d] 32 bit ints used as internal register 72 _buf = None # data processed in 64 byte blocks, this holds leftover from last update 73
74 - def __init__(self, content=None):
75 self._count = 0 76 self._state = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476] 77 self._buf = b('') 78 if content: 79 self.update(content)
80 81 # round 1 table - [abcd k s] 82 _round1 = [ 83 [0,1,2,3, 0,3], 84 [3,0,1,2, 1,7], 85 [2,3,0,1, 2,11], 86 [1,2,3,0, 3,19], 87 88 [0,1,2,3, 4,3], 89 [3,0,1,2, 5,7], 90 [2,3,0,1, 6,11], 91 [1,2,3,0, 7,19], 92 93 [0,1,2,3, 8,3], 94 [3,0,1,2, 9,7], 95 [2,3,0,1, 10,11], 96 [1,2,3,0, 11,19], 97 98 [0,1,2,3, 12,3], 99 [3,0,1,2, 13,7], 100 [2,3,0,1, 14,11], 101 [1,2,3,0, 15,19], 102 ] 103 104 # round 2 table - [abcd k s] 105 _round2 = [ 106 [0,1,2,3, 0,3], 107 [3,0,1,2, 4,5], 108 [2,3,0,1, 8,9], 109 [1,2,3,0, 12,13], 110 111 [0,1,2,3, 1,3], 112 [3,0,1,2, 5,5], 113 [2,3,0,1, 9,9], 114 [1,2,3,0, 13,13], 115 116 [0,1,2,3, 2,3], 117 [3,0,1,2, 6,5], 118 [2,3,0,1, 10,9], 119 [1,2,3,0, 14,13], 120 121 [0,1,2,3, 3,3], 122 [3,0,1,2, 7,5], 123 [2,3,0,1, 11,9], 124 [1,2,3,0, 15,13], 125 ] 126 127 # round 3 table - [abcd k s] 128 _round3 = [ 129 [0,1,2,3, 0,3], 130 [3,0,1,2, 8,9], 131 [2,3,0,1, 4,11], 132 [1,2,3,0, 12,15], 133 134 [0,1,2,3, 2,3], 135 [3,0,1,2, 10,9], 136 [2,3,0,1, 6,11], 137 [1,2,3,0, 14,15], 138 139 [0,1,2,3, 1,3], 140 [3,0,1,2, 9,9], 141 [2,3,0,1, 5,11], 142 [1,2,3,0, 13,15], 143 144 [0,1,2,3, 3,3], 145 [3,0,1,2, 11,9], 146 [2,3,0,1, 7,11], 147 [1,2,3,0, 15,15], 148 ] 149
150 - def _process(self, block):
151 "process 64 byte block" 152 # unpack block into 16 32-bit ints 153 X = struct.unpack("<16I", block) 154 155 # clone state 156 orig = self._state 157 state = list(orig) 158 159 # round 1 - F function - (x&y)|(~x & z) 160 for a,b,c,d,k,s in self._round1: 161 t = (state[a] + F(state[b],state[c],state[d]) + X[k]) & MASK_32 162 state[a] = ((t<<s) & MASK_32) + (t>>(32-s)) 163 164 # round 2 - G function 165 for a,b,c,d,k,s in self._round2: 166 t = (state[a] + G(state[b],state[c],state[d]) + X[k] + 0x5a827999) & MASK_32 167 state[a] = ((t<<s) & MASK_32) + (t>>(32-s)) 168 169 # round 3 - H function - x ^ y ^ z 170 for a,b,c,d,k,s in self._round3: 171 t = (state[a] + (state[b] ^ state[c] ^ state[d]) + X[k] + 0x6ed9eba1) & MASK_32 172 state[a] = ((t<<s) & MASK_32) + (t>>(32-s)) 173 174 # add back into original state 175 for i in irange(4): 176 orig[i] = (orig[i]+state[i]) & MASK_32
177
178 - def update(self, content):
179 if not isinstance(content, bytes): 180 raise TypeError("expected bytes") 181 buf = self._buf 182 if buf: 183 content = buf + content 184 idx = 0 185 end = len(content) 186 while True: 187 next = idx + 64 188 if next <= end: 189 self._process(content[idx:next]) 190 self._count += 1 191 idx = next 192 else: 193 self._buf = content[idx:] 194 return
195
196 - def copy(self):
197 other = _builtin_md4() 198 other._count = self._count 199 other._state = list(self._state) 200 other._buf = self._buf 201 return other
202
203 - def digest(self):
204 # NOTE: backing up state so we can restore it after _process is called, 205 # in case object is updated again (this is only attr altered by this method) 206 orig = list(self._state) 207 208 # final block: buf + 0x80, 209 # then 0x00 padding until congruent w/ 56 mod 64 bytes 210 # then last 8 bytes = msg length in bits 211 buf = self._buf 212 msglen = self._count*512 + len(buf)*8 213 block = buf + b('\x80') + b('\x00') * ((119-len(buf)) % 64) + \ 214 struct.pack("<2I", msglen & MASK_32, (msglen>>32) & MASK_32) 215 if len(block) == 128: 216 self._process(block[:64]) 217 self._process(block[64:]) 218 else: 219 assert len(block) == 64 220 self._process(block) 221 222 # render digest & restore un-finalized state 223 out = struct.pack("<4I", *self._state) 224 self._state = orig 225 return out
226
227 - def hexdigest(self):
228 return bascii_to_str(hexlify(self.digest()))
229 230 #=================================================================== 231 # eoc 232 #=================================================================== 233 234 # keep ref around for unittest, 'md4' usually replaced by ssl wrapper, below. 235 _builtin_md4 = md4 236 237 #============================================================================= 238 # check if hashlib provides accelarated md4 239 #============================================================================= 240 import hashlib 241 from compat import PYPY 242
243 -def _has_native_md4(): # pragma: no cover -- runtime detection
244 try: 245 h = hashlib.new("md4") 246 except ValueError: 247 # not supported - ssl probably missing (e.g. ironpython) 248 return False 249 result = h.hexdigest() 250 if result == '31d6cfe0d16ae931b73c59d7e0c089c0': 251 return True 252 if PYPY and result == '': 253 # workaround for https://bugs.pypy.org/issue957, fixed in PyPy 1.8 254 return False 255 # anything else and we should alert user 256 from passlib.exc import PasslibRuntimeWarning 257 warn("native md4 support disabled, sanity check failed!", PasslibRuntimeWarning) 258 return False 259 260 if _has_native_md4(): 261 # overwrite md4 class w/ hashlib wrapper
262 - def md4(content=None):
263 "wrapper for hashlib.new('md4')" 264 return hashlib.new('md4', content or b(''))
265 266 #============================================================================= 267 # Include to match existing md4.new() code: 268 #============================================================================= 269 new = md4 270 271 #============================================================================= 272 # eof 273 #============================================================================= 274