Last active
August 29, 2015 14:07
-
-
Save pachacamac/9d2efa59842d3afa6e5b to your computer and use it in GitHub Desktop.
Timecapsule Encryption PoC/WiP
This file contains 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
require 'digest/sha2' | |
require 'securerandom' | |
require 'time' | |
def random_seed | |
Digest::SHA512.hexdigest(SecureRandom.random_bytes(64)) | |
end | |
def timekey(rounds, seed = random_seed) | |
# see also: http://www.gwern.net/Self-decrypting%20files | |
start = Time.now | |
key = seed.dup | |
r = rounds | |
begin | |
key = Digest::SHA512.hexdigest(key) | |
r -= 1 | |
end while r > 0 | |
{seconds: Time.now - start, seed_info: {rounds: rounds, seed: seed}, secret_key: key} | |
end | |
def benchmark(desired_seconds, bench_rounds = 2**16) | |
bench_seconds = timekey(bench_rounds)[:seconds] | |
((bench_rounds / bench_seconds) * desired_seconds).to_i | |
end | |
def cpu_count | |
@cpu_count ||= (`nproc` || `lscpu -e|grep '^[0-9]'|wc -l` || `cat /proc/cpuinfo|grep processor|wc -l`).to_i | |
end | |
def xor_crypt(s,k) | |
s, k = s.bytes, k.bytes.cycle | |
s.map{|e| (e^k.next).chr}.join | |
end | |
def get_password(msg='> ') | |
require 'io/console' | |
print msg | |
STDIN.noecho(&:gets).chomp | |
end | |
HOSTS_TO_BLOCK = %w{reddit.com hackaday.com onethingwell.org watch-series-online.li wallhaven.cc alpha.wallhaven.cc | |
twitter.com gmail.com accounts.google.com mail.google.com bbc.com} | |
def hosts_file(action=:block, hosts=HOSTS_TO_BLOCK, file='/etc/hosts') | |
comment = '#BLOCKED' # used for unblocking | |
lines = hosts.reduce([]){|s,e| s<<"127.0.0.1 #{e} #{comment}"<<"127.0.0.1 www.#{e} #{comment}"} | |
case action.to_s | |
when 'block'; `echo '#{lines.join("\n")}' >> #{file}` | |
when 'unblock'; `echo '#{File.readlines(file).reject{|e| e.match /#{comment}$/}.join}' > #{file}` | |
else raise 'wrong action! (block or unblock)' | |
end | |
end | |
# has to be run as daemon as root and then logged out | |
def lockdown!(opts={}) | |
secret_key = opts.delete(:secret_key) || raise('No secret_key given!') | |
opts[:seed_info] || raise('No seed info given!') | |
opts[:unlock_time] || raise('No unlock time given!') | |
opts[:users] ||= [`id -un`.chomp] | |
opts[:sudo_group] ||= 'wheel' | |
raise 'Invalid unlock time!' if Time.now >= opts[:unlock_time] | |
raise 'Unreasonable unlock time!' if Time.now + 3600*6 < opts[:unlock_time] # 6 hour limit | |
puts "Starting lockdown!" | |
# 0) create new timecapsule (seed, rounds, key), save seed and rounds for user | |
puts "Writing seed info" | |
File.open('seed_info', 'a') do |f| | |
f.puts "Lockdown started (#{Time.now})" | |
opts.each{|k,v| f.puts "\t#{k}:\t#{v.inspect}"} | |
f.chmod(0777) | |
end | |
# 1) get current root password, save it in a file only root has access to | |
puts "Saving original and new root passwords in /root" | |
File.write '/root/original_password', get_password('Enter root password > ') | |
File.write '/root/current_password', secret_key | |
# 2) block sites in /etc/hosts | |
puts "Blocking hosts" | |
hosts_file :block | |
# 3) remove users from sudo group | |
puts "Removing users from group #{opts[:sudo_group]}" | |
opts[:users].each{|user| `gpasswd -d #{user} #{opts[:sudo_group]}`} | |
# 4) change root password to timecapsule key, save it in a file only root has access to | |
puts "Changing root password" | |
`echo -e "#{secret_key}\n#{secret_key}" | passwd -q root` | |
# 5) do nothing for x time | |
puts "Sleeping until #{opts[:unlock_time]}" | |
sleep 1 while Time.now < opts[:unlock_time] | |
# 6) change root password back to original | |
puts "Restoring root password" | |
key_orig = File.read('/root/original_password') | |
`echo -e "#{key_orig}\n#{key_orig}" | passwd -q root` | |
puts "Deleting password backups from /root" | |
File.delete '/root/original_password' | |
File.delete '/root/current_password' | |
# 7) add user backs to sudo group | |
puts "Adding users back to group #{opts[:sudo_group]}" | |
opts[:users].each{|user| `gpasswd -a #{user} #{opts[:sudo_group]}`} | |
# 8) unblock sites in /etc/hosts | |
puts "Unblocking hosts" | |
hosts_file :unblock | |
puts "Lockdown finished!" | |
end | |
tc = case ARGV[1].to_s.downcase.to_sym | |
when :rounds; timekey(ARGV[0].to_i) | |
when :seconds; timekey(benchmark(ARGV[0].to_i)) | |
when :minutes; timekey(benchmark(ARGV[0].to_i)*60) | |
else | |
puts "Usage: #{__FILE__} <num> <rounds/seconds/minutes> [lockdown unlock_time]" | |
exit | |
end | |
if ARGV[2].to_s.downcase.to_sym == :lockdown | |
lockdown!(tc.merge(unlock_time: Time.parse(ARGV[3].to_s))) | |
else | |
puts tc.inspect | |
end | |
# Idea for generating quicker than verifying: | |
# * Generate N hashes with R rounds each in parallel (a good value for n would be the number of CPU cores) | |
# * Set up a chain between the n results: | |
# * The final hash of seed 1 is used to encrypt the seed for hash 2 | |
# * The final hash of seed n is used to encrypt the seed for hash n+1 | |
# * Only release the plain first seed, the n-1 encrypted seeds, and the number of rounds | |
# * Every block of the chain has to be calculated one after the other, effectively preventing parallelization |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment