245 lines
6.7 KiB
Python
245 lines
6.7 KiB
Python
"""
|
|
passlib.crypto._md4 -- fallback implementation of MD4
|
|
|
|
Helper implementing insecure and obsolete md4 algorithm.
|
|
used for NTHASH format, which is also insecure and broken,
|
|
since it's just md4(password).
|
|
|
|
Implementated based on rfc at http://www.faqs.org/rfcs/rfc1320.html
|
|
|
|
.. note::
|
|
|
|
This shouldn't be imported directly, it's merely used conditionally
|
|
by ``passlib.crypto.lookup_hash()`` when a native implementation can't be found.
|
|
"""
|
|
|
|
#=============================================================================
|
|
# imports
|
|
#=============================================================================
|
|
# core
|
|
from binascii import hexlify
|
|
import struct
|
|
# site
|
|
from passlib.utils.compat import bascii_to_str, irange, PY3
|
|
# local
|
|
__all__ = ["md4"]
|
|
|
|
#=============================================================================
|
|
# utils
|
|
#=============================================================================
|
|
def F(x,y,z):
|
|
return (x&y) | ((~x) & z)
|
|
|
|
def G(x,y,z):
|
|
return (x&y) | (x&z) | (y&z)
|
|
|
|
##def H(x,y,z):
|
|
## return x ^ y ^ z
|
|
|
|
MASK_32 = 2**32-1
|
|
|
|
#=============================================================================
|
|
# main class
|
|
#=============================================================================
|
|
class md4(object):
|
|
"""pep-247 compatible implementation of MD4 hash algorithm
|
|
|
|
.. attribute:: digest_size
|
|
|
|
size of md4 digest in bytes (16 bytes)
|
|
|
|
.. method:: update
|
|
|
|
update digest by appending additional content
|
|
|
|
.. method:: copy
|
|
|
|
create clone of digest object, including current state
|
|
|
|
.. method:: digest
|
|
|
|
return bytes representing md4 digest of current content
|
|
|
|
.. method:: hexdigest
|
|
|
|
return hexadecimal version of digest
|
|
"""
|
|
# FIXME: make this follow hash object PEP better.
|
|
# FIXME: this isn't threadsafe
|
|
|
|
name = "md4"
|
|
digest_size = digestsize = 16
|
|
block_size = 64
|
|
|
|
_count = 0 # number of 64-byte blocks processed so far (not including _buf)
|
|
_state = None # list of [a,b,c,d] 32 bit ints used as internal register
|
|
_buf = None # data processed in 64 byte blocks, this holds leftover from last update
|
|
|
|
def __init__(self, content=None):
|
|
self._count = 0
|
|
self._state = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]
|
|
self._buf = b''
|
|
if content:
|
|
self.update(content)
|
|
|
|
# round 1 table - [abcd k s]
|
|
_round1 = [
|
|
[0,1,2,3, 0,3],
|
|
[3,0,1,2, 1,7],
|
|
[2,3,0,1, 2,11],
|
|
[1,2,3,0, 3,19],
|
|
|
|
[0,1,2,3, 4,3],
|
|
[3,0,1,2, 5,7],
|
|
[2,3,0,1, 6,11],
|
|
[1,2,3,0, 7,19],
|
|
|
|
[0,1,2,3, 8,3],
|
|
[3,0,1,2, 9,7],
|
|
[2,3,0,1, 10,11],
|
|
[1,2,3,0, 11,19],
|
|
|
|
[0,1,2,3, 12,3],
|
|
[3,0,1,2, 13,7],
|
|
[2,3,0,1, 14,11],
|
|
[1,2,3,0, 15,19],
|
|
]
|
|
|
|
# round 2 table - [abcd k s]
|
|
_round2 = [
|
|
[0,1,2,3, 0,3],
|
|
[3,0,1,2, 4,5],
|
|
[2,3,0,1, 8,9],
|
|
[1,2,3,0, 12,13],
|
|
|
|
[0,1,2,3, 1,3],
|
|
[3,0,1,2, 5,5],
|
|
[2,3,0,1, 9,9],
|
|
[1,2,3,0, 13,13],
|
|
|
|
[0,1,2,3, 2,3],
|
|
[3,0,1,2, 6,5],
|
|
[2,3,0,1, 10,9],
|
|
[1,2,3,0, 14,13],
|
|
|
|
[0,1,2,3, 3,3],
|
|
[3,0,1,2, 7,5],
|
|
[2,3,0,1, 11,9],
|
|
[1,2,3,0, 15,13],
|
|
]
|
|
|
|
# round 3 table - [abcd k s]
|
|
_round3 = [
|
|
[0,1,2,3, 0,3],
|
|
[3,0,1,2, 8,9],
|
|
[2,3,0,1, 4,11],
|
|
[1,2,3,0, 12,15],
|
|
|
|
[0,1,2,3, 2,3],
|
|
[3,0,1,2, 10,9],
|
|
[2,3,0,1, 6,11],
|
|
[1,2,3,0, 14,15],
|
|
|
|
[0,1,2,3, 1,3],
|
|
[3,0,1,2, 9,9],
|
|
[2,3,0,1, 5,11],
|
|
[1,2,3,0, 13,15],
|
|
|
|
[0,1,2,3, 3,3],
|
|
[3,0,1,2, 11,9],
|
|
[2,3,0,1, 7,11],
|
|
[1,2,3,0, 15,15],
|
|
]
|
|
|
|
def _process(self, block):
|
|
"""process 64 byte block"""
|
|
# unpack block into 16 32-bit ints
|
|
X = struct.unpack("<16I", block)
|
|
|
|
# clone state
|
|
orig = self._state
|
|
state = list(orig)
|
|
|
|
# round 1 - F function - (x&y)|(~x & z)
|
|
for a,b,c,d,k,s in self._round1:
|
|
t = (state[a] + F(state[b],state[c],state[d]) + X[k]) & MASK_32
|
|
state[a] = ((t<<s) & MASK_32) + (t>>(32-s))
|
|
|
|
# round 2 - G function
|
|
for a,b,c,d,k,s in self._round2:
|
|
t = (state[a] + G(state[b],state[c],state[d]) + X[k] + 0x5a827999) & MASK_32
|
|
state[a] = ((t<<s) & MASK_32) + (t>>(32-s))
|
|
|
|
# round 3 - H function - x ^ y ^ z
|
|
for a,b,c,d,k,s in self._round3:
|
|
t = (state[a] + (state[b] ^ state[c] ^ state[d]) + X[k] + 0x6ed9eba1) & MASK_32
|
|
state[a] = ((t<<s) & MASK_32) + (t>>(32-s))
|
|
|
|
# add back into original state
|
|
for i in irange(4):
|
|
orig[i] = (orig[i]+state[i]) & MASK_32
|
|
|
|
def update(self, content):
|
|
if not isinstance(content, bytes):
|
|
if PY3:
|
|
raise TypeError("expected bytes")
|
|
else:
|
|
# replicate behavior of hashlib under py2
|
|
content = content.encode("ascii")
|
|
buf = self._buf
|
|
if buf:
|
|
content = buf + content
|
|
idx = 0
|
|
end = len(content)
|
|
while True:
|
|
next = idx + 64
|
|
if next <= end:
|
|
self._process(content[idx:next])
|
|
self._count += 1
|
|
idx = next
|
|
else:
|
|
self._buf = content[idx:]
|
|
return
|
|
|
|
def copy(self):
|
|
other = md4()
|
|
other._count = self._count
|
|
other._state = list(self._state)
|
|
other._buf = self._buf
|
|
return other
|
|
|
|
def digest(self):
|
|
# NOTE: backing up state so we can restore it after _process is called,
|
|
# in case object is updated again (this is only attr altered by this method)
|
|
orig = list(self._state)
|
|
|
|
# final block: buf + 0x80,
|
|
# then 0x00 padding until congruent w/ 56 mod 64 bytes
|
|
# then last 8 bytes = msg length in bits
|
|
buf = self._buf
|
|
msglen = self._count*512 + len(buf)*8
|
|
block = buf + b'\x80' + b'\x00' * ((119-len(buf)) % 64) + \
|
|
struct.pack("<2I", msglen & MASK_32, (msglen>>32) & MASK_32)
|
|
if len(block) == 128:
|
|
self._process(block[:64])
|
|
self._process(block[64:])
|
|
else:
|
|
assert len(block) == 64
|
|
self._process(block)
|
|
|
|
# render digest & restore un-finalized state
|
|
out = struct.pack("<4I", *self._state)
|
|
self._state = orig
|
|
return out
|
|
|
|
def hexdigest(self):
|
|
return bascii_to_str(hexlify(self.digest()))
|
|
|
|
#===================================================================
|
|
# eoc
|
|
#===================================================================
|
|
|
|
#=============================================================================
|
|
# eof
|
|
#=============================================================================
|