You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

245 lines
6.7 KiB

  1. """
  2. passlib.crypto._md4 -- fallback implementation of MD4
  3. Helper implementing insecure and obsolete md4 algorithm.
  4. used for NTHASH format, which is also insecure and broken,
  5. since it's just md4(password).
  6. Implementated based on rfc at http://www.faqs.org/rfcs/rfc1320.html
  7. .. note::
  8. This shouldn't be imported directly, it's merely used conditionally
  9. by ``passlib.crypto.lookup_hash()`` when a native implementation can't be found.
  10. """
  11. #=============================================================================
  12. # imports
  13. #=============================================================================
  14. # core
  15. from binascii import hexlify
  16. import struct
  17. # site
  18. from passlib.utils.compat import bascii_to_str, irange, PY3
  19. # local
  20. __all__ = ["md4"]
  21. #=============================================================================
  22. # utils
  23. #=============================================================================
  24. def F(x,y,z):
  25. return (x&y) | ((~x) & z)
  26. def G(x,y,z):
  27. return (x&y) | (x&z) | (y&z)
  28. ##def H(x,y,z):
  29. ## return x ^ y ^ z
  30. MASK_32 = 2**32-1
  31. #=============================================================================
  32. # main class
  33. #=============================================================================
  34. class md4(object):
  35. """pep-247 compatible implementation of MD4 hash algorithm
  36. .. attribute:: digest_size
  37. size of md4 digest in bytes (16 bytes)
  38. .. method:: update
  39. update digest by appending additional content
  40. .. method:: copy
  41. create clone of digest object, including current state
  42. .. method:: digest
  43. return bytes representing md4 digest of current content
  44. .. method:: hexdigest
  45. return hexadecimal version of digest
  46. """
  47. # FIXME: make this follow hash object PEP better.
  48. # FIXME: this isn't threadsafe
  49. name = "md4"
  50. digest_size = digestsize = 16
  51. block_size = 64
  52. _count = 0 # number of 64-byte blocks processed so far (not including _buf)
  53. _state = None # list of [a,b,c,d] 32 bit ints used as internal register
  54. _buf = None # data processed in 64 byte blocks, this holds leftover from last update
  55. def __init__(self, content=None):
  56. self._count = 0
  57. self._state = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]
  58. self._buf = b''
  59. if content:
  60. self.update(content)
  61. # round 1 table - [abcd k s]
  62. _round1 = [
  63. [0,1,2,3, 0,3],
  64. [3,0,1,2, 1,7],
  65. [2,3,0,1, 2,11],
  66. [1,2,3,0, 3,19],
  67. [0,1,2,3, 4,3],
  68. [3,0,1,2, 5,7],
  69. [2,3,0,1, 6,11],
  70. [1,2,3,0, 7,19],
  71. [0,1,2,3, 8,3],
  72. [3,0,1,2, 9,7],
  73. [2,3,0,1, 10,11],
  74. [1,2,3,0, 11,19],
  75. [0,1,2,3, 12,3],
  76. [3,0,1,2, 13,7],
  77. [2,3,0,1, 14,11],
  78. [1,2,3,0, 15,19],
  79. ]
  80. # round 2 table - [abcd k s]
  81. _round2 = [
  82. [0,1,2,3, 0,3],
  83. [3,0,1,2, 4,5],
  84. [2,3,0,1, 8,9],
  85. [1,2,3,0, 12,13],
  86. [0,1,2,3, 1,3],
  87. [3,0,1,2, 5,5],
  88. [2,3,0,1, 9,9],
  89. [1,2,3,0, 13,13],
  90. [0,1,2,3, 2,3],
  91. [3,0,1,2, 6,5],
  92. [2,3,0,1, 10,9],
  93. [1,2,3,0, 14,13],
  94. [0,1,2,3, 3,3],
  95. [3,0,1,2, 7,5],
  96. [2,3,0,1, 11,9],
  97. [1,2,3,0, 15,13],
  98. ]
  99. # round 3 table - [abcd k s]
  100. _round3 = [
  101. [0,1,2,3, 0,3],
  102. [3,0,1,2, 8,9],
  103. [2,3,0,1, 4,11],
  104. [1,2,3,0, 12,15],
  105. [0,1,2,3, 2,3],
  106. [3,0,1,2, 10,9],
  107. [2,3,0,1, 6,11],
  108. [1,2,3,0, 14,15],
  109. [0,1,2,3, 1,3],
  110. [3,0,1,2, 9,9],
  111. [2,3,0,1, 5,11],
  112. [1,2,3,0, 13,15],
  113. [0,1,2,3, 3,3],
  114. [3,0,1,2, 11,9],
  115. [2,3,0,1, 7,11],
  116. [1,2,3,0, 15,15],
  117. ]
  118. def _process(self, block):
  119. """process 64 byte block"""
  120. # unpack block into 16 32-bit ints
  121. X = struct.unpack("<16I", block)
  122. # clone state
  123. orig = self._state
  124. state = list(orig)
  125. # round 1 - F function - (x&y)|(~x & z)
  126. for a,b,c,d,k,s in self._round1:
  127. t = (state[a] + F(state[b],state[c],state[d]) + X[k]) & MASK_32
  128. state[a] = ((t<<s) & MASK_32) + (t>>(32-s))
  129. # round 2 - G function
  130. for a,b,c,d,k,s in self._round2:
  131. t = (state[a] + G(state[b],state[c],state[d]) + X[k] + 0x5a827999) & MASK_32
  132. state[a] = ((t<<s) & MASK_32) + (t>>(32-s))
  133. # round 3 - H function - x ^ y ^ z
  134. for a,b,c,d,k,s in self._round3:
  135. t = (state[a] + (state[b] ^ state[c] ^ state[d]) + X[k] + 0x6ed9eba1) & MASK_32
  136. state[a] = ((t<<s) & MASK_32) + (t>>(32-s))
  137. # add back into original state
  138. for i in irange(4):
  139. orig[i] = (orig[i]+state[i]) & MASK_32
  140. def update(self, content):
  141. if not isinstance(content, bytes):
  142. if PY3:
  143. raise TypeError("expected bytes")
  144. else:
  145. # replicate behavior of hashlib under py2
  146. content = content.encode("ascii")
  147. buf = self._buf
  148. if buf:
  149. content = buf + content
  150. idx = 0
  151. end = len(content)
  152. while True:
  153. next = idx + 64
  154. if next <= end:
  155. self._process(content[idx:next])
  156. self._count += 1
  157. idx = next
  158. else:
  159. self._buf = content[idx:]
  160. return
  161. def copy(self):
  162. other = md4()
  163. other._count = self._count
  164. other._state = list(self._state)
  165. other._buf = self._buf
  166. return other
  167. def digest(self):
  168. # NOTE: backing up state so we can restore it after _process is called,
  169. # in case object is updated again (this is only attr altered by this method)
  170. orig = list(self._state)
  171. # final block: buf + 0x80,
  172. # then 0x00 padding until congruent w/ 56 mod 64 bytes
  173. # then last 8 bytes = msg length in bits
  174. buf = self._buf
  175. msglen = self._count*512 + len(buf)*8
  176. block = buf + b'\x80' + b'\x00' * ((119-len(buf)) % 64) + \
  177. struct.pack("<2I", msglen & MASK_32, (msglen>>32) & MASK_32)
  178. if len(block) == 128:
  179. self._process(block[:64])
  180. self._process(block[64:])
  181. else:
  182. assert len(block) == 64
  183. self._process(block)
  184. # render digest & restore un-finalized state
  185. out = struct.pack("<4I", *self._state)
  186. self._state = orig
  187. return out
  188. def hexdigest(self):
  189. return bascii_to_str(hexlify(self.digest()))
  190. #===================================================================
  191. # eoc
  192. #===================================================================
  193. #=============================================================================
  194. # eof
  195. #=============================================================================