module Sentry class AsymmetricSentry attr_reader :private_key_file attr_reader :public_key_file attr_accessor :symmetric_algorithm @@default_private_key_file = nil @@default_public_key_file = nil @@default_symmetric_algorithm = nil # available options: # * :private_key_file - encrypted private key file # * :public_key_file - public key file # * :symmetric_algorithm - algorithm to use for SymmetricSentry def initialize(options = {}) @public_key = @private_key = nil self.private_key_file = options[:private_key_file] self.public_key_file = options[:public_key_file] || @@default_public_key_file @symmetric_algorithm = options[:symmetric_algorithm] || @@default_symmetric_algorithm end def encrypt(data) raise NoPublicKeyError unless public? rsa = public_rsa return rsa.public_encrypt(data) end def decrypt_large_from_base64(data, key=nil) raise NoPrivateKeyError unless private? chunk_length = public_rsa.max_encryptable_length + 11 # 11 is magic padding for RSA encoding b64_decoded = Base64.decode64(data) padding_length = b64_decoded[0] data = b64_decoded[1, data.length] return (0...data.length).step(chunk_length).inject("") { |accum, idx| accum + decrypt_with_padding(data.slice(idx, chunk_length), padding_length, key)} end def chunk_size(padding_length) return public_rsa.max_encryptable_length - padding_length end def encrypt_large_to_base64(data) raise NoPublicKeyError unless public? padding_length = 8 chunk_length = chunk_size(padding_length) return Base64.encode64(padding_length.chr + (0...data.length).step(chunk_length).inject("") {|accum, idx| accum + encrypt_with_padding( data.slice(idx, chunk_length), padding_length)} ) end def decrypt_with_padding(data, padding_length, key=nil) decrypted = decrypt(data, key) return decrypted[0, decrypted.length - padding_length] end def encrypt_with_padding(data, padding_length) encrypt(data + rand_string(padding_length)) end @@CHARS = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a def rand_string(length=8) s='' length.times{ s << @@CHARS[rand(@@CHARS.length)] } s end def encrypt_to_base64(data) Base64.encode64(encrypt(data)) end def decrypt(data, key = nil) raise NoPrivateKeyError unless private? rsa = private_rsa(key) return rsa.private_decrypt(data) end def decrypt_from_base64(data, key = nil) decrypt(Base64.decode64(data), key) end def private_key_file=(file) @private_key_file = file and load_private_key end def public_key_file=(file) @public_key_file = file and load_public_key end def public? return true unless @public_key.nil? load_public_key and return @public_key end def private? return true unless @private_key.nil? load_private_key and return @private_key end class << self # * :key - secret password # * :symmetric_algorithm - symmetrical algorithm to use def save_random_rsa_key(private_key_file, public_key_file, options = {}) rsa = OpenSSL::PKey::RSA.new(512) public_key = rsa.public_key private_key = options[:key].to_s.empty? ? rsa.to_s : SymmetricSentry.new(:algorithm => options[:symmetric_algorithm]).encrypt_to_base64(rsa.to_s, options[:key]) File.open(public_key_file, 'w') { |f| f.write(public_key) } File.open(private_key_file, 'w') { |f| f.write(private_key) } end def encrypt(data) self.new.encrypt(data) end def encrypt_to_base64(data) self.new.encrypt_to_base64(data) end def encrypt_large_to_base64(data) self.new.encrypt_large_to_base64(data) end def decrypt(data, key = nil) self.new.decrypt(data, key) end def decrypt_large_from_base64(data, key = nil) self.new.decrypt_large_from_base64(data, key) end def decrypt_from_base64(data, key = nil) self.new.decrypt_from_base64(data, key) end # cattr_accessor would be lovely def default_private_key_file @@default_private_key_file end def default_private_key_file=(value) @@default_private_key_file = value end def default_public_key_file @@default_public_key_file end def default_public_key_file=(value) @@default_public_key_file = value end def default_symmetric_algorithm @@default_symmetric_algorithm end def default_symmetric_algorithm=(value) @@default_symmetric_algorithm = value end end private def encryptor @encryptor ||= SymmetricSentry.new(:algorithm => @symmetric_algorithm) end def load_private_key @private_rsa = nil @private_key_file ||= @@default_private_key_file if @private_key_file and File.file?(@private_key_file) @private_key = File.open(@private_key_file) { |f| f.read } end return @private_key end def load_public_key @public_rsa = nil @public_key_file ||= @@default_public_key_file if @public_key_file and File.file?(@public_key_file) @public_key = File.open(@public_key_file) { |f| f.read } end end # retrieves private rsa from encrypted private key def private_rsa(key = nil) return @private_rsa ||= OpenSSL::PKey::RSA.new(@private_key) unless key OpenSSL::PKey::RSA.new(encryptor.decrypt_from_base64(@private_key, key)) end # retrieves public rsa def public_rsa @public_rsa ||= OpenSSL::PKey::RSA.new(@public_key) end end end