Created
December 3, 2013 21:35
-
-
Save defuse/7777867 to your computer and use it in GitHub Desktop.
TrueCrypt Challenge Generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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