1 ## truecrypt.py - partial TrueCrypt implementation in Python.
2 ## Copyright (c) 2008 Bjorn Edstrom <be@bjrn.se>
4 ## Permission is hereby granted, free of charge, to any person
5 ## obtaining a copy of this software and associated documentation
6 ## files (the "Software"), to deal in the Software without
7 ## restriction, including without limitation the rights to use,
8 ## copy, modify, merge, publish, distribute, sublicense, and/or sell
9 ## copies of the Software, and to permit persons to whom the
10 ## Software is furnished to do so, subject to the following
13 ## The above copyright notice and this permission notice shall be
14 ## included in all copies or substantial portions of the Software.
16 ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 ## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 ## OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 ## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 ## HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 ## WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 ## FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 ## OTHER DEALINGS IN THE SOFTWARE.
26 ## Jan 4 2008: Initial version. Plenty of room for improvements.
38 from rijndael
import Rijndael
39 from serpent
import Serpent
40 from twofish
import Twofish
42 from keystrengthening
import *
53 print >> sys
.stderr
, "Progress:", message
57 crc
= binascii
.crc32(data
)
58 # Convert from signed to unsigned word32.
59 return crc
% 0x100000000
62 """Bytes to 16 bit big endian word."""
63 return struct
.unpack(">H", x
)[0]
66 """Bytes to 32 bit big endian word."""
67 return struct
.unpack(">L", x
)[0]
70 """Bytes to 64 bit big endian word."""
71 a
, b
= struct
.unpack(">LL", x
)
74 def Win32FileTime2UnixTime(filetime
):
75 """Converts a win32 FILETIME to a unix timestamp."""
76 return filetime
/ 10000000 - 11644473600
83 def __init__(self
, ciphers
):
84 self
.ciphers
= [ciph() for ciph
in ciphers
]
85 def set_key(self
, keys
):
87 for cipher
in self
.ciphers
:
88 cipher
.set_key(keys
[i
])
90 def encrypt(self
, data
):
91 for cipher
in self
.ciphers
:
92 data
= cipher
.encrypt(data
)
94 def decrypt(self
, data
):
95 for cipher
in reversed(self
.ciphers
):
96 data
= cipher
.decrypt(data
)
99 return '-'.join(reversed([cipher
.get_name() for cipher
in self
.ciphers
]))
106 [Serpent
, Twofish
, Rijndael
],
108 [Rijndael
, Twofish
, Serpent
],
113 (HMAC_SHA1
, "SHA-1"),
114 (HMAC_RIPEMD160
, "RIPEMD-160"),
115 (HMAC_WHIRLPOOL
, "Whirlpool")
123 TC_HIDDEN_VOLUME_OFFSET
= 1536
125 class TrueCryptVolume
:
126 """Object representing a TrueCrypt volume."""
127 def __init__(self
, fileobj
, password
, progresscallback
=lambda x
: None):
129 self
.fileobj
= fileobj
130 self
.decrypted_header
= None
132 self
.master_lrwkew
= None
135 for volume_type
in ["normal", "hidden"]:
137 if volume_type
== "hidden":
138 fileobj
.seek(-TC_HIDDEN_VOLUME_OFFSET
, 2)
140 progresscallback("Is this a " + volume_type
+ " volume?")
142 salt
= fileobj
.read(64)
143 header
= fileobj
.read(448)
145 assert len(salt
) == 64
146 assert len(header
) == 448
148 for hmac
, hmac_name
in HMACs
:
149 # Generate the keys needed to decrypt the volume header.
151 if hmac
== HMAC_WHIRLPOOL
:
155 if hmac_name
in "RIPEMD-160 Whirlpool":
156 info
= ' (this will take a while)'
157 progresscallback("Trying " + hmac_name
+ info
)
159 header_keypool
= PBKDF2(hmac
, password
, salt
, iterations
, 128)
160 header_lrwkey
= header_keypool
[0:16]
161 header_key1
= header_keypool
[32:64]
162 header_key2
= header_keypool
[64:96]
163 header_key3
= header_keypool
[96:128]
165 for cascade
in Cascades
:
166 # Try each cipher and cascades and see if we can successfully
167 # decrypt the header with it.
168 cipher
= CipherChain(cascade
)
170 progresscallback("..." + cipher
.get_name())
172 cipher
.set_key([header_key1
, header_key2
, header_key3
])
173 decrypted_header
= LRWMany(cipher
.decrypt
, header_lrwkey
, 1, header
)
174 if TCIsValidVolumeHeader(decrypted_header
):
176 self
.decrypted_header
= decrypted_header
178 master_keypool
= decrypted_header
[192:]
179 master_lrwkey
= master_keypool
[0:16]
180 master_key1
= master_keypool
[32:64]
181 master_key2
= master_keypool
[64:96]
182 master_key3
= master_keypool
[96:128]
184 self
.master_lrwkey
= master_lrwkey
186 self
.cipher
.set_key([master_key1
, master_key2
, master_key3
])
187 self
.hidden_size
= BE64(decrypted_header
[28:28+8])
188 self
.format_ver
= BE16(decrypted_header
[4:6])
190 # We don't really need the information below but we save
191 # it so it can be displayed by print_information()
192 self
.info_hash
= hmac_name
193 self
.info_headerlrwkey
= hexdigest(header_lrwkey
)
194 self
.info_headerkey
= hexdigest(header_keypool
[32:128])
195 self
.info_masterkey
= hexdigest(master_keypool
[32:128])
197 progresscallback("Success!")
200 raise KeyError, "incorrect password (or not a truecrypt volume)"
203 if not self
.decrypted_header
:
204 return "<TrueCryptVolume>"
205 return "<TrueCryptVolume %s %s>" % (self
.cipher
.get_name(), self
.info_hash
)
207 def TCIsValidVolumeHeader(header
):
209 checksum
= BE32(header
[8:12])
210 return magic
== 'TRUE' and CRC32(header
[192:448]) == checksum
212 def TCReadSector(tc
, index
):
213 """Read a sector from the volume."""
215 tc
.fileobj
.seek(0, 2)
216 file_len
= tc
.fileobj
.tell()
218 # The LRW functions work on blocks of length 16. Since a TrueCrypt
219 # sector is 512 bytes each call to LRWMany will decrypt 32 blocks,
220 # and each call to this function must therefore advance the block
221 # index 32. The block index also starts at 1, not 0. index 1
222 # corresponds to lrw_index 1, index 2 corresponds to lrw_index 33 etc.
223 lrw_index
= (index
- 1) * 32 + 1 # LRWSector2Index(index)
225 # For a regular (non-hidden) volume the file system starts at byte
226 # 512. However for a hidden volume, the start of the file system
227 # is not at byte 512. Starting from the end of the volume, namely
228 # byte file_len, we subtract the hidden volume salt+header (at offset
229 # 1536 from the end of the file). We then subtract the size of the
232 last_sector_offset
= TC_SECTOR_SIZE
234 mod
= file_len
- tc
.hidden_size
- TC_HIDDEN_VOLUME_OFFSET
235 # We subtract another sector from mod because the index starts
237 mod
-= TC_SECTOR_SIZE
238 last_sector_offset
= TC_SECTOR_SIZE
+ TC_HIDDEN_VOLUME_OFFSET
239 seekto
= mod
+ TC_SECTOR_SIZE
* index
241 # last_sector_offset is the beginning of the last sector relative
242 # the end of the file. For a regular non-hidden volume this is simply
243 # 512 bytes from the end of the file. However for hidden volumes we
244 # must not read past the headers, so the last sector begins 512 bytes
245 # before the header offset.
246 if seekto
> file_len
- last_sector_offset
:
249 tc
.fileobj
.seek(seekto
)
250 data
= tc
.fileobj
.read(TC_SECTOR_SIZE
)
252 return LRWMany(tc
.cipher
.decrypt
, tc
.master_lrwkey
, lrw_index
, data
)
254 def TCSectorCount(tc
):
255 """How many sectors can we read with TCReadSector?"""
258 volume_size
= tc
.hidden_size
260 tc
.fileobj
.seek(0, 2)
261 volume_size
= tc
.fileobj
.tell()
262 # Minus the salt+header.
264 return volume_size
/ TC_SECTOR_SIZE
266 def TCPrintInformation(tc
):
267 if not tc
.decrypted_header
:
270 header
= tc
.decrypted_header
271 program_ver
= BE16(header
[6:8])
272 volume_create
= Win32FileTime2UnixTime(BE64(header
[12:12+8]))
273 header_create
= Win32FileTime2UnixTime(BE64(header
[20:20+8]))
278 print repr(tc
.decrypted_header
)
280 print "Parsed Header"
282 print "Hash :", tc
.info_hash
283 print "Cipher :", tc
.cipher
.get_name()
285 print "Volume Type : Hidden"
286 print "Hidden size :", tc
.hidden_size
288 print "Volume Type : Normal"
289 print "Header Key :", tc
.info_headerkey
290 print "Header LRW Key:", tc
.info_headerlrwkey
291 print "Master Key :", tc
.info_masterkey
292 print "Master LRW Key:", hexdigest(tc
.master_lrwkey
)
293 print "Format ver :", hex(tc
.format_ver
)
294 print "Min prog. ver :", hex(program_ver
)
295 print "Volume create :", time
.asctime(time
.localtime(volume_create
))
296 print "Header create :", time
.asctime(time
.localtime(header_create
))
300 scriptname
= sys
.argv
[0]
302 path
, password
, outfile
= sys
.argv
[1:]
304 print >> sys
.stderr
, "%s volumepath password outfile" % scriptname
307 if os
.path
.exists(outfile
):
308 print >> sys
.stderr
, "outfile %s already exists. use another " \
309 "filename and try again (we don't want to overwrite " \
310 "files by mistake)" % outfile
314 fileobj
= file(path
, "rb")
316 print >> sys
.stderr
, "file %s doesn't exist" % path
320 tc
= TrueCryptVolume(fileobj
, password
, Log
)
322 print >> sys
.stderr
, "incorrect password or not a TrueCrypt volume"
325 except KeyboardInterrupt:
326 print >> sys
.stderr
, "aborting"
330 TCPrintInformation(tc
)
332 outfileobj
= file(outfile
, "ab")
333 num_sectors
= TCSectorCount(tc
)
336 for i
in xrange(1, num_sectors
+ 1):
338 Log("Decrypting sector %d of %d." % (i
, num_sectors
))
339 outfileobj
.write(TCReadSector(tc
, i
))
341 except KeyboardInterrupt:
342 print "Aborted decryption."
345 print "Wrote %d sectors (%d bytes)." % (num_written
,
346 num_written
* TC_SECTOR_SIZE
)
350 if __name__
== '__main__':
353 ## If you want to use the code from the toploop:
355 #fileobj = file("volume.tc", "rb")
356 #tc = TrueCryptVolume(fileobj, "password", Log)
357 #TCPrintInformation(tc)
358 #print repr(TCReadSector(tc, 1))
359 #print repr(TCReadSector(tc, 2))