Skip to content

Instantly share code, notes, and snippets.

@defuse
Created December 3, 2013 21:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save defuse/7777867 to your computer and use it in GitHub Desktop.
Save defuse/7777867 to your computer and use it in GitHub Desktop.
TrueCrypt Challenge Generator
#!/usr/bin/env ruby
# @DefuseSec's TrueCrypt Challenge Generator!
#
# This script generates a set of TrueCrypt "challenges." Volumes are created in
# different ways using secure 128-bit passwords to provide a challenge for
# anyone claiming TrueCrypt is backdoored. If there is a backdoor, then one
# should be able to use one of the published challenges to prove it.
#
# There are 5 different types of challenges:
#
# 1. SamePasswordKnownPlaintext
#
# Multiple volumes are created using the same password. They are each filled
# with a repeating plaintext sequence of 4 random bytes. Some of them contain
# the same plaintext. The 4 bytes of plaintext that repeat are included in the
# file name.
#
# Goal: Recover the password or master key.
#
# 2. SamePasswordUnknownPlaintext
#
# Like (1), except the 4 bytes of plaintext that are repeated are not made
# public.
#
# Goal: Recover the password, master key, or plaintext.
#
# 3. RandomPasswordKnownPlaintext
#
# A single volume with a random password and a plaintext which is a repeting
# sequence of 4 random bytes. The plaintext is included in the filename.
#
# Goal: Recover the password, master key.
#
# 4. RandomPasswordUnknownPlaintext
#
# Like (3), except the plaintext bytes are not made public.
#
# Goal: Recover the password, master key, or plaintext.
#
# 5. RandomPasswordSamePlaintext
#
# Multiple volumes containing the same repeated sequence of 4 random bytes are
# created using different random passwords. The plaintext is not made public.
#
# Goal: Recover the password, master key, or plaintext.
#
# These challenges are generated using all combinations of hash function and
# cipher cascades supported by TrueCrypt.
require 'securerandom'
# Configuration:
# The dirctory to generate the challenge volumes in. Must exist and be empty.
CHALLENGE_DIRECTORY = "/tmp/challenges"
# The size of a challenge volume, in megabytes. Be careful, this script
# generates lots of them!
CHALLENGE_VOLUME_MEGABYTES = 2
# All the hashes TrueCrypt supports.
TRUECRYPT_HASHES = ["SHA-512", "RIPEMD-160", "WHIRLPOOL"]
# All the ciphers and cascades TrueCrypt supports.
TRUECRYPT_ENCRYPTIONS = [
"AES", "Twofish", "Serpent",
"AES-Twofish", "Twofish-Serpent", "Serpent-AES",
"Serpent-Twofish-AES", "AES-Twofish-Serpent"
]
module TrueCrypt
def TrueCrypt::create_tc_volume(encryption, hash, size_mb, path, password)
IO.popen(
[
"truecrypt", "-t", "-c", "-p", password, "--volume-type=normal",
"--filesystem=none", "--encryption=#{encryption}", "--hash=#{hash}",
"--size=#{size_mb*1024**2}", "--keyfiles=", "--random-source=/dev/urandom",
path
]
) do |tc|
return tc.read =~ /The TrueCrypt volume has been successfully created/
end
end
def TrueCrypt::mount_tc_volume(path, password, slot)
# NOTE: It doesn't actually mount in /media/truecrypXX, since there is no
# filesystem, but this "tricks" truecrypt into mounting it at slot XX, so we
# know it'll be at /dev/mapper/truecrypXX.
IO.popen(
[
"truecrypt", "-t", "--keyfiles=", "--protect-hidden=no",
"--filesystem=none", "-p", password, path, "/media/truecrypt#{slot}"
]
) do |tc|
return tc.read.empty?
end
end
def TrueCrypt::unmount_tc_volume(path)
IO.popen( ["truecrypt", "-d", path]) { |tc_close| }
end
def TrueCrypt::get_blockdev_size_bytes(path)
IO.popen( ["blockdev", "--getsz", path] ) do |io|
return io.read.to_i * 512
end
end
def TrueCrypt::fill_tc_volume(slot, plaintext)
File.open("/dev/mapper/truecrypt#{slot}", "w") do |f|
written = 0
device_bytes = get_blockdev_size_bytes("/dev/mapper/truecrypt#{slot}")
while written < device_bytes
f.write(plaintext)
written += plaintext.length
end
end
end
end
module Challenge
def Challenge::create_challenges(directory, hash, encryption)
if Dir.exists?(directory) && (Dir.entries(directory) - [".", ".."]).empty?
subdir = File.join(directory, "SamePasswordKnownPlaintext")
Dir.mkdir(subdir)
# 10 volumes with the same password and random plaintext.
password = Challenge::random_password()
10.times do |i|
plaintext = Challenge::random_plaintext()
Challenge::create_known_plaintext_challenge(encryption, hash, subdir, password, plaintext)
end
# 3 with the same password as before, but a fixed plaintext.
plaintext = Challenge::random_plaintext()
3.times do |i|
Challenge::create_known_plaintext_challenge(encryption, hash, subdir, password, plaintext, "_#{i}")
end
subdir = File.join(directory, "SamePasswordUnknownPlaintext")
Dir.mkdir(subdir)
# 10 volumes with the same password and unknown random plaintexts.
password = Challenge::random_password()
10.times do |i|
plaintext = Challenge::random_plaintext()
Challenge::create_unknown_plaintext_challenge(encryption, hash, subdir, password, plaintext, "_#{i}")
end
# Fix the plaintext for 3 of them.
plaintext = Challenge::random_plaintext()
3.times do |i|
Challenge::create_unknown_plaintext_challenge(encryption, hash, subdir, password, plaintext, "SAME_#{i}")
end
# One volume with a random password and random plaintext (made public).
subdir = File.join(directory, "RandomPasswordKnownPlaintext")
Dir.mkdir(subdir)
password = Challenge::random_password()
plaintext = Challenge::random_plaintext()
Challenge::create_known_plaintext_challenge(encryption, hash, subdir, password, plaintext)
# One volume with a random password and plaintext (kept secret).
subdir = File.join(directory, "RandomPasswordUnknownPlaintext")
Dir.mkdir(subdir)
password = Challenge::random_password()
plaintext = Challenge::random_plaintext()
Challenge::create_unknown_plaintext_challenge(encryption, hash, subdir, password, plaintext)
# A bunch of volumes with different passwords, but all the same plaintext.
subdir = File.join(directory, "RandomPasswordSamePlaintext")
Dir.mkdir(subdir)
plaintext = Challenge::random_plaintext()
10.times do |i|
password = Challenge::random_password()
Challenge::create_unknown_plaintext_challenge(encryption, hash, subdir, password, plaintext)
end
else
puts "ERROR: Directory #{directory} doesn't exist or isn't empty."
end
end
def Challenge::create_known_plaintext_challenge(encryption, hash, directory, password, plaintext, path_suffix = '')
path = File.join(directory, hash + "_" + encryption + "_" + Challenge::bin2hex(plaintext) + path_suffix + ".tc")
puts path
puts " PASSWORD: #{password}"
puts " PLAINTEXT: #{Challenge::bin2hex(plaintext)}"
create_challenge_volume(encryption, hash, path, password, plaintext)
end
def Challenge::create_unknown_plaintext_challenge(encryption, hash, directory, password, plaintext, path_suffix = '')
path = File.join(directory, hash + "_" + encryption + "_UNKNOWN" + path_suffix + ".tc")
puts path
puts " PASSWORD: #{password}"
puts " PLAINTEXT: #{Challenge::bin2hex(plaintext)}"
Challenge::create_challenge_volume(encryption, hash, path, password, plaintext)
end
def Challenge::create_challenge_volume(encryption, hash, path, password, plaintext)
TrueCrypt::create_tc_volume(encryption, hash, CHALLENGE_VOLUME_MEGABYTES, path, password)
TrueCrypt::mount_tc_volume(path, password, 55)
TrueCrypt::fill_tc_volume(55, plaintext)
TrueCrypt::unmount_tc_volume(path)
end
def Challenge::bin2hex(binary_string)
binary_string.unpack("H*")[0]
end
def Challenge::random_password()
SecureRandom.hex(16)
end
def Challenge::random_plaintext()
SecureRandom.random_bytes(4)
end
end
puts "+" + "-" * 78 + "+"
puts "| " + " " * 35 + "WARNING" + " " * 35 + "|"
puts "+" + "-" * 78 + "+"
puts "| A bug could potentially cause this script to overwrite data in another |"
puts "| mounted TrueCrypt volume. It shouldn't happen, but be careful! |"
puts "+" + "-" * 78 + "+"
print "Do you still want to run this script [y/N]? "
choice = gets.strip
if choice == "y" || choice == "Y"
if Dir.exists?(CHALLENGE_DIRECTORY) && (Dir.entries(CHALLENGE_DIRECTORY) - [".", ".."]).empty?
TRUECRYPT_HASHES.each do |hash|
TRUECRYPT_ENCRYPTIONS.each do |encryption|
subdir = File.join(CHALLENGE_DIRECTORY, "#{hash}_#{encryption}")
Dir.mkdir(subdir)
Challenge::create_challenges(subdir, hash, encryption)
end
end
else
puts "ERROR: Directory #{CHALLENGE_DIRECTORY} must exist and be empty."
end
else
puts "Ok, quitting. Bye."
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment