|
#!/usr/bin/env ruby |
|
|
|
require "optparse" |
|
require "tempfile" |
|
require "stringio" |
|
|
|
class OptionError < StandardError; end |
|
class OpenSSLError < StandardError; end |
|
|
|
module Base |
|
def closed_temp_file(prefix) |
|
Tempfile.new(prefix).tap do |file| |
|
file.close |
|
end |
|
end |
|
|
|
def run(msg) |
|
puts msg |
|
value = yield |
|
raise OpenSSLError, value unless $?.exitstatus == 0 |
|
end |
|
end |
|
|
|
class Encypter |
|
include Base |
|
|
|
attr_accessor :public_key_file, :encrypted_aes_key_file, :input_file, :encrypted_output_file, :overwrite, :key_size |
|
|
|
def initialize(options = {}) |
|
@public_key_file = options[:rsa_key] or raise OptionError, "RSA public key file must be given" |
|
@encrypted_aes_key_file = options[:aes_key] or raise OptionError, "Target AES encrypted key file must be given" |
|
@input_file = options[:input_file] or raise OptionError, "Please provide the file to encrypt" |
|
@encrypted_output_file = options[:output_file] or raise OptionError, "Please provide the target outfile file name" |
|
@overwrite = options[:overwrite] || false |
|
@key_size = options[:key_size] || 64 |
|
|
|
raise OptionError, "Input file does not exist" unless File.exist?(input_file) |
|
raise OptionError, "Target AES encrypted file must not exist before hand" if !overwrite & File.exist?(encrypted_aes_key_file) |
|
raise OptionError, "Output file must not exist before hand" if !overwrite & File.exist?(encrypted_output_file) |
|
end |
|
|
|
def perform |
|
aes_key_file = closed_temp_file("aes") |
|
|
|
if File.read(public_key_file).start_with?("ssh-") |
|
pem_public_key_file = closed_temp_file("pub_pem") |
|
pem_public_key_file_path = pem_public_key_file.path |
|
`ssh-keygen -f #{public_key_file} -e -m pkcs8 > #{pem_public_key_file_path}` |
|
end |
|
|
|
begin |
|
run "Creating AES key..." do |
|
`openssl rand -base64 #{key_size} | tr -d '\r\n' > #{aes_key_file.path}` |
|
end |
|
|
|
run "Encrypting the file..." do |
|
`openssl enc -aes-256-cbc -in #{input_file} -out #{encrypted_output_file} -kfile #{aes_key_file.path}` |
|
end |
|
|
|
run "Encrypting the AES key..." do |
|
`openssl rsautl -encrypt -inkey #{pem_public_key_file_path || public_key_file} -pubin -in #{aes_key_file.path} -out #{encrypted_aes_key_file} 2>&1` |
|
end |
|
|
|
puts "Encryption complete. Please send #{encrypted_output_file} and #{encrypted_aes_key_file} to the sender." |
|
puts "They can decrypt #{File.basename(encrypted_output_file)} provided they have the right private key." |
|
ensure |
|
aes_key_file.unlink |
|
pem_public_key_file.unlink if pem_public_key_file |
|
end |
|
end |
|
end |
|
|
|
class Decrypter |
|
include Base |
|
|
|
attr_accessor :private_key_file, :encrypted_aes_key_file, :encrypted_file, :output_file, :overwrite |
|
|
|
def initialize(options = {}) |
|
@private_key_file = options[:rsa_key] or raise OptionError, "RSA private key file must be given" |
|
@encrypted_aes_key_file = options[:aes_key] or raise OptionError, "AES encrypted key file must be given" |
|
@encrypted_file = options[:input_file] or raise OptionError, "Please provide the file to decrypt" |
|
@output_file = options[:output_file] or raise OptionError, "Please provide the target output file name" |
|
@overwrite = options[:overwrite] || false |
|
|
|
raise OptionError, "Input file does not exists" unless File.exist?(encrypted_file) |
|
raise OptionError, "Output file must not exist before hand" if !overwrite & File.exist?(output_file) |
|
end |
|
|
|
def perform |
|
decrypted_aes_key_file = closed_temp_file("aes") |
|
|
|
begin |
|
run "Decrypting your AES key..." do |
|
`openssl rsautl -decrypt -inkey #{private_key_file} -in #{encrypted_aes_key_file} -out #{decrypted_aes_key_file.path}` |
|
end |
|
|
|
run "Decrypting your encrypted file..." do |
|
`openssl enc -aes-256-cbc -d -in #{encrypted_file} -out #{output_file} -kfile #{decrypted_aes_key_file.path}` |
|
end |
|
|
|
puts "File decrypted. Check #{output_file}" |
|
ensure |
|
decrypted_aes_key_file.unlink |
|
end |
|
end |
|
end |
|
|
|
options = {} |
|
|
|
opts = OptionParser.new do |opts| |
|
opts.banner = "Usage: enc_dec.rb [options]" |
|
|
|
opts.on("-a", "--action ACTION", {:encrypt => "Encypter", :decrypt => "Decrypter"}) do |action| |
|
options[:action] = action |
|
end |
|
|
|
opts.on("--rsa FILE", "RSA key (either public when encrypting, or private when decrypting)") do |rsa_key| |
|
options[:rsa_key] = rsa_key |
|
end |
|
|
|
opts.on("--aes FILE", "AES key file (encrypted or target)") do |aes_key| |
|
options[:aes_key] = aes_key |
|
end |
|
|
|
opts.on("--key-size SIZE", Integer, "Key size to use when generating AES key (default 32)") do |key_size| |
|
options[:key_size] = key_size |
|
end |
|
|
|
opts.on("--input FILE", "Input file to encrypt/decrypt") do |input_file| |
|
options[:input_file] = input_file |
|
end |
|
|
|
opts.on("--output FILE", "Output file to encrypt/decrypt") do |output_file| |
|
options[:output_file] = output_file |
|
end |
|
|
|
opts.on("--overwrite", "Overwrite target files") do |
|
options[:overwrite] = true |
|
end |
|
|
|
opts.on_tail("-h", "--help", "Show this message") do |
|
puts opts |
|
exit |
|
end |
|
|
|
opts.on_tail("--version", "Show version") do |
|
puts "0.1.0" |
|
exit |
|
end |
|
end |
|
|
|
opts.parse!(ARGV) |
|
|
|
begin |
|
action = options.delete(:action) or raise OptionError, "You must specify action" |
|
crypto = Kernel.const_get(action).new(options) |
|
crypto.perform |
|
rescue OptionError => e |
|
STDERR.puts "#{e.message}\n\n" |
|
STDERR.puts opts |
|
rescue OpenSSLError => e |
|
STDERR.puts "\nOpenSSL is mis behaving. The error:\n\n" |
|
STDERR.puts e.message |
|
end |