init
This commit is contained in:
commit
920228e75b
|
@ -0,0 +1,5 @@
|
||||||
|
__pycache__
|
||||||
|
*.py
|
||||||
|
*.egg-info
|
||||||
|
build
|
||||||
|
dist
|
|
@ -0,0 +1,7 @@
|
||||||
|
[submodule "ext/sx"]
|
||||||
|
path = ext/sx
|
||||||
|
url = https://weeaboo.software/Shiz/sx
|
||||||
|
branch = feat/sxplore
|
||||||
|
[submodule "ext/atwork"]
|
||||||
|
path = ext/atwork
|
||||||
|
url = https://github.com/Shizmob/atwork
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 63d8e3e619fc4514b7a6ecf48ebd1bfab94304e6
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 9f3f0a024bdcc12462ce1bfb65797190ca06b43c
|
|
@ -0,0 +1,3 @@
|
||||||
|
pycryptodome
|
||||||
|
-e git+https://github.com/Shizmob/atwork#egg=atwork
|
||||||
|
-e git+https://weeaboo.software/Shiz/sx@feat/sxplore#egg=sx
|
|
@ -0,0 +1,20 @@
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='truedecrypt',
|
||||||
|
version='0.1.0',
|
||||||
|
author='Shiz <hi@shiz.me>',
|
||||||
|
url='https://weeaboo.software/Shiz/truedecrypt',
|
||||||
|
description='Truecrypt version 4.1 - 4.3 volume decrypter',
|
||||||
|
|
||||||
|
py_modules=['truedecrypt'],
|
||||||
|
install_requires=["atwork", "sx", "pycryptodome"],
|
||||||
|
dependency_links=[
|
||||||
|
"https://github.com/Shizmob/atwork/archive/refs/heads/master.tar.gz#egg=atwork",
|
||||||
|
"https://weeaboo.software/Shiz/sx/archive/feat/sxplore.tar.gz#egg=sx",
|
||||||
|
],
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': ['truedecrypt=truedecrypt:main'],
|
||||||
|
},
|
||||||
|
zip_safe=True,
|
||||||
|
)
|
|
@ -0,0 +1,282 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
import enum
|
||||||
|
import zlib
|
||||||
|
import hashlib
|
||||||
|
import itertools
|
||||||
|
from sx import Struct, parse
|
||||||
|
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
|
||||||
|
|
||||||
|
IV_LEN = 32
|
||||||
|
|
||||||
|
def prf_ripemd160(key: bytes, salt: bytes, nout: int) -> bytes:
|
||||||
|
return hashlib.pbkdf2_hmac('ripemd160', key, salt, 2000, nout)
|
||||||
|
|
||||||
|
def prf_sha512(key: bytes, salt: bytes, nout: int) -> bytes:
|
||||||
|
return hashlib.pbkdf2_hmac('sha512', key, salt, 1000, nout)
|
||||||
|
|
||||||
|
def prf_sha1(key: bytes, salt: bytes, nout: int) -> bytes:
|
||||||
|
return hashlib.pbkdf2_hmac('sha1', key, salt, 1000, nout)
|
||||||
|
|
||||||
|
def prf_whirlpool(key: bytes, salt: bytes, nout: int) -> bytes:
|
||||||
|
return hashlib.pbkdf2_hmac('whirlpool', key, salt, 1000, nout)
|
||||||
|
|
||||||
|
def prf_single(key: bytes, salt: bytes, nout: int, method) -> tuple[bytes, bytes]:
|
||||||
|
r = method(key, salt, nout + IV_LEN)
|
||||||
|
return r[IV_LEN:], r[:IV_LEN]
|
||||||
|
|
||||||
|
|
||||||
|
PRF_METHODS = [prf_ripemd160, prf_sha512, prf_sha1, prf_whirlpool]
|
||||||
|
|
||||||
|
def prf_all(key: bytes, salt: bytes, nout: int) -> list[tuple[bytes, bytes]]:
|
||||||
|
d = []
|
||||||
|
for method in PRF_METHODS:
|
||||||
|
key, iv = prf_single(key, salt, nout, method)
|
||||||
|
yield (method, key, iv)
|
||||||
|
|
||||||
|
|
||||||
|
def gf2_add(a: int, b: int):
|
||||||
|
""" Add two numbers in GF(2 ^ n) """
|
||||||
|
return a ^ b
|
||||||
|
|
||||||
|
def gf2_mul(a, b, mod):
|
||||||
|
""" Multiply two numbers in GF(2^n) with a given polynomial - adapted from http://www.bjrn.se/code/pytruecrypt/lrwpy.txt """
|
||||||
|
def xor_mod(n, mod):
|
||||||
|
while True:
|
||||||
|
x = n.bit_length() - mod.bit_length()
|
||||||
|
|
||||||
|
if x == 0:
|
||||||
|
n = n ^ mod
|
||||||
|
if x <= 0:
|
||||||
|
break
|
||||||
|
lower = n & ((1 << x) - 1)
|
||||||
|
n = (((n >> x) ^ mod) << x) | lower
|
||||||
|
return n
|
||||||
|
|
||||||
|
# Naively mutiply two polynomials together. Lets say a is x^8+x^3+1
|
||||||
|
# and b is x^4+x^2, then we can write this as the following pseudo code:
|
||||||
|
res = 0
|
||||||
|
a_cnt = 0
|
||||||
|
# for each term in [x^8, x^3, 1]:
|
||||||
|
while a:
|
||||||
|
b2 = b
|
||||||
|
b_cnt = 0
|
||||||
|
if a & 1:
|
||||||
|
# for each term in [x^4, x^2]:
|
||||||
|
while b2:
|
||||||
|
if b2 & 1:
|
||||||
|
# 1 << (a_cnt + b_cnt) constructs the new term
|
||||||
|
# and the xor adds it to the result modulo 2.
|
||||||
|
res ^= 1 << (a_cnt + b_cnt)
|
||||||
|
b2 >>= 1
|
||||||
|
b_cnt += 1
|
||||||
|
a >>= 1
|
||||||
|
a_cnt += 1
|
||||||
|
|
||||||
|
return xor_mod(res, mod)
|
||||||
|
|
||||||
|
def gf2b_add(a: bytes, b: bytes) -> bytes:
|
||||||
|
n = len(a)
|
||||||
|
return gf2_add(
|
||||||
|
int.from_bytes(a, byteorder='big'),
|
||||||
|
int.from_bytes(b, byteorder='big'),
|
||||||
|
).to_bytes(n, byteorder='big')
|
||||||
|
|
||||||
|
def gf2b_mul(a: bytes, b: bytes, poly: int) -> bytes:
|
||||||
|
n = len(a)
|
||||||
|
return gf2_mul(
|
||||||
|
int.from_bytes(a, byteorder='big'),
|
||||||
|
int.from_bytes(b, byteorder='big'),
|
||||||
|
poly,
|
||||||
|
).to_bytes(n, byteorder='big')
|
||||||
|
|
||||||
|
def gf2b_128_mul(a: bytes, b: bytes):
|
||||||
|
return gf2b_mul(a, b, 0x100000000000000000000000000000087) # x^128+x^7+x^2+x+1
|
||||||
|
|
||||||
|
|
||||||
|
class LRW:
|
||||||
|
""" LRW cipher mode. """
|
||||||
|
def __init__(self, cipher, tweak: bytes):
|
||||||
|
self.cipher = cipher
|
||||||
|
self.block_size = 16
|
||||||
|
self.tweak = tweak[:self.block_size]
|
||||||
|
|
||||||
|
def get_tweak(self, offset: int) -> bytes:
|
||||||
|
return gf2b_128_mul(
|
||||||
|
self.tweak,
|
||||||
|
(offset // self.block_size).to_bytes(self.block_size, byteorder='big'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def encrypt(self, input: bytes, offset: int) -> bytes:
|
||||||
|
output = b''
|
||||||
|
for i in range(0, len(input), self.block_size):
|
||||||
|
tweak = self.get_tweak(offset + i)
|
||||||
|
inblk = gf2b_add(tweak, input[i:i + self.block_size])
|
||||||
|
output += gf2b_add(tweak, self.cipher.encrypt(inblk))
|
||||||
|
return output
|
||||||
|
|
||||||
|
def decrypt(self, input: bytes, offset: int) -> bytes:
|
||||||
|
output = b''
|
||||||
|
for i in range(0, len(input), self.block_size):
|
||||||
|
tweak = self.get_tweak(offset + i)
|
||||||
|
inblk = gf2b_add(tweak, input[i:i + self.block_size])
|
||||||
|
output += gf2b_add(tweak, self.cipher.decrypt(inblk))
|
||||||
|
return output
|
||||||
|
|
||||||
|
def cipher_mode_lrw(cipher, iv: bytes):
|
||||||
|
return LRW(cipher, iv)
|
||||||
|
|
||||||
|
def cipher_algo_aes(key: bytes):
|
||||||
|
return AES.new(key[:32], AES.MODE_ECB)
|
||||||
|
|
||||||
|
|
||||||
|
CIPHER_ALGOS = [cipher_algo_aes]
|
||||||
|
CIPHER_MODES = [cipher_mode_lrw]
|
||||||
|
|
||||||
|
def encrypt_single(key: bytes, iv: bytes, block: bytes, offset: int, cipher_algo, cipher_mode):
|
||||||
|
cipher = cipher_mode(cipher_algo(key), iv)
|
||||||
|
return cipher.encrypt(block, offset)
|
||||||
|
|
||||||
|
def decrypt_single(key: bytes, iv: bytes, block: bytes, offset: int, cipher_algo, cipher_mode):
|
||||||
|
cipher = cipher_mode(cipher_algo(key), iv)
|
||||||
|
return cipher.decrypt(block, offset)
|
||||||
|
|
||||||
|
def decrypt_all(key: bytes, iv: bytes, block: bytes, offset: int):
|
||||||
|
d = []
|
||||||
|
for (algo, mode) in itertools.product(CIPHER_ALGOS, CIPHER_MODES):
|
||||||
|
dec = decrypt_single(key, iv, block, offset, algo, mode)
|
||||||
|
yield (algo, mode, dec)
|
||||||
|
|
||||||
|
|
||||||
|
def crc32p(b: bytes, v: int) -> int:
|
||||||
|
return (~zlib.crc32(b, (~v) & 0xFFFFFFFF)) & 0xFFFFFFFF
|
||||||
|
|
||||||
|
class TCKeyfilePool:
|
||||||
|
STATE_SIZE = 64
|
||||||
|
KEYFILE_SIZE = 1 * 1024 * 1024
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.state = bytearray(self.STATE_SIZE)
|
||||||
|
|
||||||
|
def add(self, file) -> None:
|
||||||
|
crc = 0xFFFFFFFF
|
||||||
|
|
||||||
|
pos = 0
|
||||||
|
for _ in range(self.KEYFILE_SIZE):
|
||||||
|
b = file.read(1)
|
||||||
|
if not b:
|
||||||
|
break
|
||||||
|
crc = crc32p(b, crc)
|
||||||
|
for b in crc.to_bytes(4, byteorder='big'):
|
||||||
|
self.state[pos] = (self.state[pos] + b) & 0xff
|
||||||
|
pos += 1
|
||||||
|
pos %= len(self.state)
|
||||||
|
|
||||||
|
def apply(self, password: bytes) -> bytes:
|
||||||
|
if len(password) < len(self.state):
|
||||||
|
password = password.ljust(len(self.state), b'\x00')
|
||||||
|
return bytes((p + s) & 0xFF for (p, s) in zip(password, self.state)) + password[len(self.state):]
|
||||||
|
|
||||||
|
|
||||||
|
class TCVolumeFlags(enum.Flag):
|
||||||
|
System = (1 << 0)
|
||||||
|
InPlace = (1 << 1)
|
||||||
|
|
||||||
|
class TCVolumeHeader(Struct):
|
||||||
|
magic: Fixed(b'TRUE')
|
||||||
|
# 1.0: TrueCrypt 1.0
|
||||||
|
# 2.0: TrueCrypt 4.1
|
||||||
|
# 3.0: TrueCrypt 5.0
|
||||||
|
# 4.0: TrueCrypt 6.0
|
||||||
|
# 5.0: TrueCrypt 7.0
|
||||||
|
fmt_version: uint16be
|
||||||
|
prog_version: uint16be
|
||||||
|
key_checksum: uint32be
|
||||||
|
vol_timestamp: uint64be
|
||||||
|
hdr_timestamp: uint64be
|
||||||
|
hidvol_size: If(self.fmt_version >= 2, uint64be, Ignored(Data(8)))
|
||||||
|
vol_size: If(self.fmt_version >= 3, uint64be, Ignored(Data(8)))
|
||||||
|
master_key_off: If(self.fmt_version >= 3, uint64be, Ignored(Data(8)))
|
||||||
|
master_key_size: If(self.fmt_version >= 3, uint64be, Ignored(Data(8)))
|
||||||
|
flags: If(self.fmt_version >= 4, Enum(TCVolumeFlags, uint32be), Ignored(Data(4)))
|
||||||
|
sector_size: If(self.fmt_version >= 5, uint32be, Ignored(Data(4)))
|
||||||
|
_reserved84: Ignored(Data(120))
|
||||||
|
hdr_checksum: If(self.fmt_version >= 4, uint32be, Ignored(Data(4)))
|
||||||
|
master_iv: Data(32)
|
||||||
|
master_key: Data(224)
|
||||||
|
|
||||||
|
|
||||||
|
SECTOR_SIZE = 512
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
import os
|
||||||
|
import math
|
||||||
|
import argparse
|
||||||
|
import atwork
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('-k', '--keyfile', type=argparse.FileType('rb'), action='append')
|
||||||
|
parser.add_argument('-p', '--password')
|
||||||
|
parser.add_argument('-x', '--hidden', action='store_true')
|
||||||
|
parser.add_argument('infile', type=argparse.FileType('rb'))
|
||||||
|
parser.add_argument('outfile', type=argparse.FileType('wb'), nargs='?')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
with atwork.Task('generating key') as t:
|
||||||
|
key = b''
|
||||||
|
if args.password:
|
||||||
|
key += args.password.encode('utf-8')
|
||||||
|
if args.keyfile:
|
||||||
|
pool = TCKeyfilePool()
|
||||||
|
for f in args.keyfile:
|
||||||
|
pool.add(f)
|
||||||
|
key = pool.apply(key)
|
||||||
|
|
||||||
|
with atwork.Task('decrypting header') as t:
|
||||||
|
found = False
|
||||||
|
for offset in (0, -1536):
|
||||||
|
if offset < 0:
|
||||||
|
args.infile.seek(offset, os.SEEK_END)
|
||||||
|
else:
|
||||||
|
args.infile.seek(offset, os.SEEK_SET)
|
||||||
|
salt = args.infile.read(64)
|
||||||
|
hdr = args.infile.read(448)
|
||||||
|
|
||||||
|
for (prf_method, crypt_key, crypt_iv) in prf_all(key, salt, 32):
|
||||||
|
for (cipher_algo, cipher_mode, dec_hdr) in decrypt_all(crypt_key, crypt_iv, hdr, 16):
|
||||||
|
if dec_hdr.startswith(b'TRUE'):
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if found:
|
||||||
|
break
|
||||||
|
if found:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ValueError('nope')
|
||||||
|
|
||||||
|
tchdr = parse(TCVolumeHeader, dec_hdr)
|
||||||
|
t.success('hidden' if offset < 0 else 'normal')
|
||||||
|
t.message(tchdr)
|
||||||
|
|
||||||
|
if args.outfile:
|
||||||
|
CHUNK_SIZE = 0x8000
|
||||||
|
|
||||||
|
with atwork.Task('decrypting volume', total=math.ceil(tchdr.hidvol_size / CHUNK_SIZE), unit='chunk') as t:
|
||||||
|
if tchdr.hidvol_size:
|
||||||
|
t.message('hidden')
|
||||||
|
args.infile.seek(-1536 - tchdr.hidvol_size, os.SEEK_END)
|
||||||
|
else:
|
||||||
|
t.message('normal')
|
||||||
|
args.infile.seek(SECTOR_SIZE, os.SEEK_SET)
|
||||||
|
|
||||||
|
c = cipher_mode(cipher_algo(tchdr.master_key), tchdr.master_iv)
|
||||||
|
for i in range(0, tchdr.hidvol_size, CHUNK_SIZE):
|
||||||
|
dec = c.decrypt(args.infile.read(CHUNK_SIZE), 16 + i)
|
||||||
|
args.outfile.write(dec)
|
||||||
|
t.step()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
Loading…
Reference in New Issue