#!/opt/local/bin/python3.13


#
# Author......: See docs/credits.txt
# License.....: MIT
#

from argparse import ArgumentParser, ArgumentTypeError


SALT_LENGTH = 64
DATA_LENGTH = 448
HEADER_LENGTH = SALT_LENGTH + DATA_LENGTH

SIGNATURE = "$truecrypt$"

BOOTABLE_OFFSET = 31744  # 62 * 512
HIDDEN_OFFSET = 65536  # 64K


def validate_offset(offset):
    # see also https://hashcat.net/wiki/doku.php?id=frequently_asked_questions#how_do_i_extract_the_hashes_from_truecrypt_volumes
    if offset == "bootable":
        offset = BOOTABLE_OFFSET
    elif offset == "hidden":
        offset = HIDDEN_OFFSET
    elif offset == "bootable+hidden":
        offset = BOOTABLE_OFFSET + HIDDEN_OFFSET
    try:
        offset = int(offset)
    except ValueError as e:
        raise ArgumentTypeError("value is nether number nor allowed string") from e
    if offset < 0:
        raise ArgumentTypeError("value cannot be less than zero")
    return offset


if __name__ == "__main__":
    parser = ArgumentParser(description="truecrypt2hashcat extraction tool")
    parser.add_argument(
        "--offset",
        default=0,
        type=validate_offset,
        required=False,
        help="select between bootable, hidden, bootable+hidden or custom one (default: 0)",
    )
    parser.add_argument("path", type=str, help="path to TrueCrypt container")

    args = parser.parse_args()

    try:
        with open(args.path, "rb") as file:
            file.seek(args.offset)

            header = file.read(HEADER_LENGTH)

        if len(header) < HEADER_LENGTH:
            parser.error("file contains less data than needed")

        salt, data = header[:SALT_LENGTH], header[SALT_LENGTH:]

        hash = SIGNATURE + salt.hex() + "$" + data.hex()
        print(hash)
    except IOError as e:
        parser.error(e.strerror.lower())
