require 'dionysus'
require 'dionysus/digest'
require 'dionysus/string'
require 'dionysus/security/password_salt'
##
# Adds String hashing and salting convenience methods.
#
# require 'dionysus/security/string'
#
# The hash methods may be accessed by the digest method or by the
# dynamic convenience methods.
#
# Examples:
#
# 'my password'.digest(:sha1)
# 'my password'.sha1
class String
##
# Generate a random, binary salt with the given length (default 16)
def self.salt( length = 16 )
PasswordSalt.generate(length, :binary).to_s
end
##
# Sanitize the String from memory. This is non-reversible. Runs 7 passes
# by default.
#
def sanitize( passes = 7 )
passes.times do
(0...self.length).each { |i| self[i] = rand(256) }
end
(0...self.length).each { |i| self[i] = 0 }
self.delete!("\000")
end
##
# Generate the given digest hash.
#
# Options:
# [salt] Salt to append to the string. (This may be a String or a
# PasswordSalt object) Default: ''
# [encoding] Encoding for the hash: :binary, :hex,
# :hexidecimal, :base64
# Default: :base64
def digest( klass, options = {} )
digest = Digest.digest(klass, _salted(options[:salt]))
_encode(digest, options[:encoding] || :base64)
end
##
# Call the appropriate digest method.
def method_missing( method, *args )
super if block_given?
if Digest.available_digests.include?(method)
self.digest(method, *args)
else
super
end
end
##
# Detect the digest of the string. Returns nil if the digest cannot be
# determined.
#
# Example:
# "wxeCFXPVXePFcpwuFDjonyn1G/w=".detect_digest(:base64) #=> :sha1
# "foobar".detect_digest(:hex) #=> nil
def detect_digest( encoding )
Digest.detect_digest(self, encoding)
end
##
# Detect the digest of the string. Raises "Unknown digest" if the digest
# cannot be determined.
#
# Example:
# "wxeCFXPVXePFcpwuFDjonyn1G/w=".detect_digest!(:base64) #=> :sha1
# "foobar".detect_digest!(:hex) #=> RuntimeError
def detect_digest!( encoding )
Digest.detect_digest!(self, encoding)
end
private
##
# Salt the string by appending it to the end or utilizing the
# PasswordSalt's salt_password method
def _salted( salt )
return self if salt.blank?
unless salt.is_a?(PasswordSalt)
salt = PasswordSalt.new(salt)
end
salt.salt_password(self)
end
##
# Encode the value to the given encoding
def _encode( value, encoding )
case encoding
when :binary
value
when :base64
value.encode64s
when :hex, :hexidecimal
value.encode_hex
else
raise ArgumentError, "Invalid encoding: #{encoding}"
end
end
end