File: rc4_cryptoapi.py

package info (click to toggle)
python-msoffcrypto-tool 5.4.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 272 kB
  • sloc: python: 3,430; makefile: 12
file content (90 lines) | stat: -rw-r--r-- 2,833 bytes parent folder | download
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