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