lib/ezcrypto.rb in ezcrypto-0.2.1 vs lib/ezcrypto.rb in ezcrypto-0.3
- old
+ new
@@ -39,42 +39,60 @@
(C) 2005 Pelle Braendgaard
=end
class Key
- attr_reader :raw,:algorithm
-
+
+ attr_reader :raw, :algorithm
+
+ @@block_size = 512
+
=begin rdoc
+Set the block-size for IO-operations (default: 512 bytes)
+=end
+ def self.block_size=(size)
+ @@block_size = size
+ end
+
+=begin rdoc
+Return the block-size for IO-operations.
+=end
+ def self.block_size
+ @@block_size
+ end
+
+
+=begin rdoc
Initialize the key with raw unencoded binary key data. This needs to be at least
16 bytes long for the default aes-128 algorithm.
=end
def initialize(raw,options = {})
@raw=raw
@algorithm=options[:algorithm]||"aes-128-cbc"
end
-
+
=begin rdoc
Generate random key.
=end
- def self.generate(options = {})
+ def self.generate(options = {})
Key.new(EzCrypto::Digester.generate_key(calculate_key_size(options[:algorithm])),options)
end
-
+
=begin rdoc
-Create key generated from the given password and salt
+Create key generated from the given password and salt
=end
def self.with_password(password,salt,options = {})
Key.new(EzCrypto::Digester.get_key(password,salt,calculate_key_size(options[:algorithm])),options)
end
-
+
=begin rdoc
Initialize the key with Base64 encoded key data.
=end
def self.decode(encoded,options = {})
Key.new(Base64.decode64(encoded),options)
end
-
+
=begin rdoc
Encrypts the data with the given password and a salt. Short hand for:
key=Key.with_password(password,salt,options)
key.encrypt(data)
@@ -82,34 +100,35 @@
=end
def self.encrypt_with_password(password,salt,data,options = {})
key=Key.with_password(password,salt,options)
key.encrypt(data)
end
-
+
=begin rdoc
Decrypts the data with the given password and a salt. Short hand for:
key=Key.with_password(password,salt,options)
key.decrypt(data)
-
+
=end
def self.decrypt_with_password(password,salt,data,options = {})
key=Key.with_password(password,salt,options)
key.decrypt(data)
end
-
+
=begin rdoc
-Given an algorithm this calculates the keysize. This is used by both the generate and with_password methods. This is not yet 100% complete.
+Given an algorithm this calculates the keysize. This is used by both
+the generate and with_password methods. This is not yet 100% complete.
=end
- def self.calculate_key_size(algorithm)
+ def self.calculate_key_size(algorithm)
if !algorithm.nil?
algorithm=~/^([[:alnum:]]+)(-(\d+))?/
if $3
size=($3.to_i)/8
- else
- case $1
+ else
+ case $1
when "bf"
size = 16
when "blowfish"
size = 16
when "des"
@@ -124,71 +143,263 @@
size = 32
when "rc2"
size = 16
when "rc4"
size = 16
- else
+ else
size = 16
end
end
end
if size.nil?
size = 16
end
-
+
size
end
=begin rdoc
returns the Base64 encoded key.
=end
def encode
Base64.encode64 @raw
end
-
+
=begin rdoc
-returns the Base64 encoded key. Synonym for encode.
+returns the Base64 encoded key. Synonym for encode.
=end
def to_s
encode
end
-
+
=begin rdoc
Encrypts the data and returns it in encrypted binary form.
=end
def encrypt(data)
- @cipher=EzCrypto::Encrypter.new(self,"",@algorithm)
- @cipher.encrypt(data)
+ if data==nil || data==""
+ nil
+ else
+ encrypter("")
+ @cipher.encrypt(data)
+ end
end
=begin rdoc
Encrypts the data and returns it in encrypted Base64 encoded form.
=end
def encrypt64(data)
Base64.encode64(encrypt(data))
end
-
+
=begin rdoc
Decrypts the data passed to it in binary format.
-=end
+=end
def decrypt(data)
- @cipher=EzCrypto::Decrypter.new(self,"",@algorithm)
- @cipher.gulp(data)
+ if data==nil || data==""
+ nil
+ else
+ decrypter("")
+ @cipher.gulp(data)
+ end
rescue
puts @algorithm
throw $!
end
-
+
=begin rdoc
-Decrypts a Base64 formatted string
+Decrypts a Base64 formatted string
=end
def decrypt64(data)
decrypt(Base64.decode64(data))
end
+
+=begin rdoc
+Create a file with minimal permissions, and yield
+it to a block. By default only the owner may read it.
+=end
+ def safe_create(file, mod=0400, mask=0066)
+ begin
+ old_umask = File.umask
+ File.umask(mask)
+ File.open(file, File::CREAT | File::EXCL | File::WRONLY) do |f|
+ yield(f) if block_given?
+ end
+ ensure
+ File.umask(old_umask)
+ end
+ File.chmod(mod, file) if File.exists?(file)
+ File.size(file)
+ end
+
+
+=begin rdoc
+Overwrite a file with zero values before removing it's filesystem inode.
+This is not very safe :-)
+=end
+ def safe_delete(file)
+ begin
+ to_clear = File.size(file)
+ zeroes = Array.new(Key.block_size, "\0").join
+ File.chmod(0600, file)
+ File.open(file, File::WRONLY) do |f|
+ f.rewind
+ while to_clear > 0
+ f.write(zeroes)
+ to_clear -= Key.block_size
+ end
+ f.flush
+ end
+ ensure
+ File.delete(file) if File.exists?(file)
+ end
+ return !File.exists?(file)
+ end
+
+
+=begin rdoc
+Open a file readonly and yield it to a block.
+=end
+ def safe_read(file)
+ File.open(file, File::RDONLY) do |i|
+ yield(i) if block_given?
+ end
+ end
+
+ private :safe_create, :safe_read, :safe_delete
+
+=begin rdoc
+Load a key from a yaml_file generated via Key#store.
+=end
+ def self.load(filename)
+ require 'yaml'
+ hash = YAML::load_file(filename)
+ req = proc { |k| hash[k] or raise "Missing element #{k} in #{filename}" }
+ key = self.new Base64.decode64(req.call(:key)) , :algorithm => req.call(:algorithm)
+ return key
+ end
+
+
+=begin rdoc
+Save the key data into a file, try to do this in a secure manner.
+NOTE: YAML::store & friends are not used to encance control over
+the generated file format.
+=end
+ def store(filename)
+ safe_create(filename) do |f|
+ selfenc = self.encode
+ f.puts "---"
+ f.puts ":EZCRYPTO KEY FILE: KEEP THIS SECURE !"
+ f.puts ":created: #{Time.now}"
+ f.puts ":algorithm: #{@algorithm}"
+ f.puts ":key: #{selfenc}"
+ end
+ end
+
+
+=begin rdoc
+Get a Encrypter object. You have to call #final on it by yourself!
+=end
+ def encrypter(target='')
+ @cipher = EzCrypto::Encrypter.new(self,target,@algorithm)
+ end
+
+
+=begin rdoc
+Get a Decrypter object. You have to call #final on it by yourself!
+=end
+ def decrypter(target='')
+ @cipher = EzCrypto::Decrypter.new(self,target,@algorithm)
+ end
+
+=begin rdoc
+Create a Decrypter object and yield it to a block.
+You must *not* call #final by yourself, the method
+does this.
+=end
+ def on_decrypter(target='', &block)
+ decrypter(target)
+ on_cipher(&block)
+ end
+
+=begin rdoc
+Create an Encrypter object and yield it to a block.
+You must *not* call #final by yourself, the method
+does this.
+=end
+ def on_encrypter(target='', &block)
+ encrypter(target)
+ on_cipher(&block)
+ end
+
+
+=begin rdoc
+Helper method, yields the current cipher to a block.
+=end
+ def on_cipher(&block)
+ begin
+ block.call @cipher
+ ensure
+ @cipher.final
+ end
+ end
+
+ private :on_cipher
+
+
+=begin rdoc
+Encrypt a file 'inplace' and add a suffix
+see #cipher_file.
+IMPORTANT: The inputfile will be deleted by default.
+=end
+ def encrypt_file(src, tgt=nil, options = {} )
+ options = { :suffix => '.ez', :autoclean => 'true' }.update(options)
+ tgt = "#{src}#{options[:suffix]}" unless tgt
+ cipher_file :on_encrypter, src, tgt, options[:autoclean]
+ end
+
+
+=begin rdoc
+Decrypt a file 'inplace' and remove a suffix
+see #cipher_file
+IMPORTANT: The inputfile will be deleted by default.
+=end
+ def decrypt_file(src, tgt=nil, options = {} )
+ options = { :suffix => '.ez', :autoclean => 'true' }.update(options)
+ unless tgt
+ tgt = src
+ tgt = tgt.gsub(/#{options[:suffix]}$/, '')
+ end
+ cipher_file :on_decrypter, src, tgt, options[:autoclean]
+ end
+
+
+=begin rdoc
+uses either #on_decrypter or #on_encrypter to transform a given sourcefile
+to a targetfile. the sourcefile is deleted after sucessful transformation
+the delete_source is 'true'.
+=end
+ def cipher_file(method, sourcefile, targetfile, delete_source)
+ raise(ArgumentError, "source == target #{sourcefile}") if sourcefile == targetfile
+ safe_create(targetfile,0600) do |o|
+ self.send(method, o) do |c|
+ safe_read(sourcefile) do |i|
+ loop do
+ buffer = i.read(Key.block_size) or break
+ c << buffer
+ end
+ end
+ end
+ end
+ safe_delete(sourcefile) if delete_source && File.exists?(targetfile)
+ return targetfile
+ end
+ private :cipher_file
+
end
+
=begin rdoc
Abstract Wrapper around OpenSSL's Cipher object. Extended by Encrypter and Decrypter.
You probably should be using the Key class instead.
@@ -211,16 +422,21 @@
@cipher.padding=1
@target=target
@finished=false
end
+
+ def to_target(data)
+ return data
+ end
+
=begin rdoc
Process the givend data with the cipher.
=end
def update(data)
reset if @finished
- @target<< @cipher.update(data)
+ @target<< to_target(@cipher.update(data))
end
=begin rdoc
=end
@@ -230,10 +446,10 @@
=begin rdoc
Finishes up any last bits of data in the cipher and returns the final result.
=end
def final
- @target<< @cipher.final
+ @target<< to_target(@cipher.final)
@finished=true
@target
end
=begin rdoc