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
|
import functools
import io
import logging
from hashlib import sha1
from struct import pack
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher
try:
# NOTE: Avoid DeprecationWarning since cryptography>=43.0
# TODO: .algorithm differs from the official documentation
from cryptography.hazmat.decrepit.ciphers.algorithms import ARC4
except ImportError:
from cryptography.hazmat.primitives.ciphers.algorithms import ARC4
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
def _makekey(password, salt, keyLength, block, algIdHash=0x00008004):
r"""
Return a intermediate key.
"""
# https://msdn.microsoft.com/en-us/library/dd920677(v=office.12).aspx
password = password.encode("UTF-16LE")
h0 = sha1(salt + password).digest()
blockbytes = pack("<I", block)
hfinal = sha1(h0 + blockbytes).digest()
if keyLength == 40:
key = hfinal[:5] + b"\x00" * 11
else:
key = hfinal[: keyLength // 8]
return key
class DocumentRC4CryptoAPI:
def __init__(self):
pass
@staticmethod
def verifypw(
password,
salt,
keySize,
encryptedVerifier,
encryptedVerifierHash,
algId=0x00006801,
block=0,
):
r"""
Return True if the given password is valid.
"""
# TODO: For consistency with others, rename method to verify_password or the like
# https://msdn.microsoft.com/en-us/library/dd953617(v=office.12).aspx
key = _makekey(password, salt, keySize, block)
cipher = Cipher(ARC4(key), mode=None, backend=default_backend())
decryptor = cipher.decryptor()
verifier = decryptor.update(encryptedVerifier)
verfiferHash = decryptor.update(encryptedVerifierHash)
hash = sha1(verifier).digest()
logging.debug([verfiferHash, hash])
return hash == verfiferHash
@staticmethod
def decrypt(password, salt, keySize, ibuf, blocksize=0x200, block=0):
r"""
Return decrypted data.
"""
obuf = io.BytesIO()
key = _makekey(password, salt, keySize, block)
for c, buf in enumerate(iter(functools.partial(ibuf.read, blocksize), b"")):
cipher = Cipher(ARC4(key), mode=None, backend=default_backend())
decryptor = cipher.decryptor()
dec = decryptor.update(buf) + decryptor.finalize()
obuf.write(dec)
# From wvDecrypt:
# at this stage we need to rekey the rc4 algorithm
# Dieter Spaar <spaar@mirider.augusta.de> figured out
# this rekeying, big kudos to him
block += 1
key = _makekey(password, salt, keySize, block)
obuf.seek(0)
return obuf
|