require 'openssl'
require 'digest/sha2'
require 'digest/sha1'
require 'base64'
module EzCrypto #:nodoc:
=begin rdoc
The Key is the only class you need to understand for simple use.
=== Algorithms
The crypto algorithms default to aes-128-cbc however on any of the class methods you can change it to one of the standard openssl cipher names using the
optional :algorithm=>alg name parameter.
Eg.
Key.new @raw, :algorithm=>"des"
Key.generate :algorithm=>"blowfish"
Key.with_password @pwd,@salt,:algorithm=>"aes256"
== License
ActiveCrypto and EzCrypto are released under the MIT license.
== Support
To contact the author, send mail to pelleb@gmail.com
Also see my blogs at:
http://stakeventures.com and
http://neubia.com
This project was based on code used in my project StakeItOut, where you can securely share web services with your partners.
https://stakeitout.com
(C) 2005 Pelle Braendgaard
=end
class Key
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 = {})
Key.new(EzCrypto::Digester.generate_key(calculate_key_size(options[:algorithm])),options)
end
=begin rdoc
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)
=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.
=end
def self.calculate_key_size(algorithm)
if !algorithm.nil?
algorithm=~/^([[:alnum:]]+)(-(\d+))?/
if $3
size=($3.to_i)/8
else
case $1
when "bf"
size = 16
when "blowfish"
size = 16
when "des"
size = 8
when "des3"
size = 24
when "aes128"
size = 16
when "aes192"
size = 24
when "aes256"
size = 32
when "rc2"
size = 16
when "rc4"
size = 16
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).chop
end
=begin rdoc
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)
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
def decrypt(data)
if data==nil || data==""
nil
else
decrypter("")
@cipher.gulp(data)
end
# rescue
# puts @algorithm
# puts self.encode
# puts data.size
# throw $!
end
=begin rdoc
Decrypts a Base64 formatted string
=end
def decrypt64(data)
decrypt(Base64.decode64(data))
end
=begin rdoc
Allows keys to be marshalled
=end
def marshal_dump
"#{self.algorithm}$$$#{self.encode}"
end
=begin rdoc
Allows keys to be unmarshalled
=end
def marshal_load(s)
a, r = s.split '$$$'
@algorithm = a
@raw = Base64.decode64(r)
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.
Warning! The interface may change.
=end
class CipherWrapper #:nodoc:
=begin rdoc
=end
def initialize(key,target,mode,algorithm)
@cipher = OpenSSL::Cipher::Cipher.new(algorithm)
if mode
@cipher.encrypt
else
@cipher.decrypt
end
@cipher.key=key.raw
@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<< to_target(@cipher.update(data))
end
=begin rdoc
=end
def <<(data)
update(data)
end
=begin rdoc
Finishes up any last bits of data in the cipher and returns the final result.
=end
def final
@target<< to_target(@cipher.final)
@finished=true
@target
end
=begin rdoc
Processes the entire data string using update and performs a final on it returning the data.
=end
def gulp(data)
update(data)
final
# rescue
# breakpoint
end
=begin rdoc
=end
def reset(target="")
@target=target
@finished=false
end
end
=begin rdoc
Wrapper around OpenSSL Cipher for Encryption use.
You probably should be using Key instead.
Warning! The interface may change.
=end
class Encrypter