<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">import functools, io, logging
from hashlib import md5
from struct import pack

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms

logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())


def _makekey(password, salt, block):
    r"""
    Return a intermediate key.

        &gt;&gt;&gt; password = 'password1'
        &gt;&gt;&gt; salt = b'\xe8w,\x1d\x91\xc5j7\x96Ga\xb2\x80\x182\x17'
        &gt;&gt;&gt; block = 0
        &gt;&gt;&gt; expected = b' \xbf2\xdd\xf5@\x85\x8cQ7D\xaf\x0f$\xe0&lt;'
        &gt;&gt;&gt; _makekey(password, salt, block) == expected
        True
    """
    # https://msdn.microsoft.com/en-us/library/dd920360(v=office.12).aspx
    password = password.encode("UTF-16LE")
    h0 = md5(password).digest()
    truncatedHash = h0[:5]
    intermediateBuffer = (truncatedHash + salt) * 16
    h1 = md5(intermediateBuffer).digest()
    truncatedHash = h1[:5]
    blockbytes = pack("&lt;I", block)
    hfinal = md5(truncatedHash + blockbytes).digest()
    key = hfinal[: 128 // 8]
    return key


class DocumentRC4:
    def __init__(self):
        pass

    @staticmethod
    def verifypw(password, salt, encryptedVerifier, encryptedVerifierHash):
        r"""
        Return True if the given password is valid.

            &gt;&gt;&gt; password = 'password1'
            &gt;&gt;&gt; salt = b'\xe8w,\x1d\x91\xc5j7\x96Ga\xb2\x80\x182\x17'
            &gt;&gt;&gt; encryptedVerifier = b'\xc9\xe9\x97\xd4T\x97=1\x0b\xb1\xbap\x14&amp;\x83~'
            &gt;&gt;&gt; encryptedVerifierHash = b'\xb1\xde\x17\x8f\x07\xe9\x89\xc4M\xae^L\xf9j\xc4\x07'
            &gt;&gt;&gt; DocumentRC4.verifypw(password, salt, encryptedVerifier, encryptedVerifierHash)
            True
        """
        # https://msdn.microsoft.com/en-us/library/dd952648(v=office.12).aspx
        block = 0
        key = _makekey(password, salt, block)
        cipher = Cipher(algorithms.ARC4(key), mode=None, backend=default_backend())
        decryptor = cipher.decryptor()
        verifier = decryptor.update(encryptedVerifier)
        verfiferHash = decryptor.update(encryptedVerifierHash)
        hash = md5(verifier).digest()
        logging.debug([verfiferHash, hash])
        return hash == verfiferHash

    @staticmethod
    def decrypt(password, salt, ibuf, blocksize=0x200):
        r"""
        Return decrypted data.
        """
        obuf = io.BytesIO()

        block = 0
        key = _makekey(password, salt, block)

        for c, buf in enumerate(iter(functools.partial(ibuf.read, blocksize), b"")):
            cipher = Cipher(algorithms.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 &lt;spaar@mirider.augusta.de&gt; figured out
            # this rekeying, big kudos to him
            block += 1
            key = _makekey(password, salt, block)

        obuf.seek(0)
        return obuf
</pre></body></html>