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 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
|
import io
import logging
from hashlib import sha1
from struct import pack, unpack
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
class ECMA376Standard:
def __init__(self):
pass
@staticmethod
def decrypt(key, ibuf):
r"""
Return decrypted data.
"""
obuf = io.BytesIO()
totalSize = unpack("<I", ibuf.read(4))[0]
logger.debug("totalSize: {}".format(totalSize))
ibuf.seek(8)
aes = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())
decryptor = aes.decryptor()
x = ibuf.read()
dec = decryptor.update(x) + decryptor.finalize()
obuf.write(dec[:totalSize])
return obuf.getvalue() # return obuf.getbuffer()
@staticmethod
def verifykey(key, encryptedVerifier, encryptedVerifierHash):
r"""
Return True if the given intermediate key is valid.
>>> key = b'@\xb1:q\xf9\x0b\x96n7T\x08\xf2\xd1\x81\xa1\xaa'
>>> encryptedVerifier = b'Qos.\x96o\xac\x17\xb1\xc5\xd7\xd8\xcc6\xc9('
>>> encryptedVerifierHash = b'+ah\xda\xbe)\x11\xad+\xd3|\x17Ft\\\x14\xd3\xcf\x1b\xb1@\xa4\x8fNo=#\x88\x08r\xb1j'
>>> ECMA376Standard.verifykey(key, encryptedVerifier, encryptedVerifierHash)
True
"""
# TODO: For consistency with Agile, rename method to verify_password or the like
logger.debug([key, encryptedVerifier, encryptedVerifierHash])
# https://msdn.microsoft.com/en-us/library/dd926426(v=office.12).aspx
aes = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())
decryptor = aes.decryptor()
verifier = decryptor.update(encryptedVerifier)
expected_hash = sha1(verifier).digest()
decryptor = aes.decryptor()
verifierHash = decryptor.update(encryptedVerifierHash)[: sha1().digest_size]
return expected_hash == verifierHash
@staticmethod
def makekey_from_password(
password, algId, algIdHash, providerType, keySize, saltSize, salt
):
r"""
Generate intermediate key from given password.
>>> password = 'Password1234_'
>>> algId = 0x660e
>>> algIdHash = 0x8004
>>> providerType = 0x18
>>> keySize = 128
>>> saltSize = 16
>>> salt = b'\xe8\x82fI\x0c[\xd1\xee\xbd+C\x94\xe3\xf80\xef'
>>> expected = b'@\xb1:q\xf9\x0b\x96n7T\x08\xf2\xd1\x81\xa1\xaa'
>>> ECMA376Standard.makekey_from_password(password, algId, algIdHash, providerType, keySize, saltSize, salt) == expected
True
"""
logger.debug(
[
password,
hex(algId),
hex(algIdHash),
hex(providerType),
keySize,
saltSize,
salt,
]
)
xor_bytes = lambda a, b: bytearray(
[p ^ q for p, q in zip(bytearray(a), bytearray(b))]
) # bytearray() for Python 2 compat.
# https://msdn.microsoft.com/en-us/library/dd925430(v=office.12).aspx
ITER_COUNT = 50000
password = password.encode("UTF-16LE")
h = sha1(salt + password).digest()
for i in range(ITER_COUNT):
ibytes = pack("<I", i)
h = sha1(ibytes + h).digest()
block = 0
blockbytes = pack("<I", block)
hfinal = sha1(h + blockbytes).digest()
cbRequiredKeyLength = keySize // 8
cbHash = sha1().digest_size
buf1 = b"\x36" * 64
buf1 = xor_bytes(hfinal, buf1[:cbHash]) + buf1[cbHash:]
x1 = sha1(buf1).digest()
buf2 = b"\x5c" * 64
buf2 = xor_bytes(hfinal, buf2[:cbHash]) + buf2[cbHash:]
x2 = sha1(buf2).digest() # In spec but unused
x3 = x1 + x2
keyDerived = x3[:cbRequiredKeyLength]
logger.debug(keyDerived)
return keyDerived
|