require 'dionysus' require 'dionysus/digest' require 'dionysus/string' require 'dionysus/security/password_salt' require 'dionysus/version_string' ## # 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. # # NOTE: In Ruby 1.9, this uses the String#setbyte(index, int) method. In Ruby < 1.9, this # uses String#[]=(int) # def sanitize( passes = 7 ) if RUBY_VERSION.satisfies?('< 1.9') passes.times do (0...self.length).each { |i| self[i] = rand(256) } end (0...self.length).each { |i| self[i] = 0 } self.delete!("\000") elsif RUBY_VERSION.satisfies?('>= 1.9') passes.times do (0...self.length).each { |i| self.setbyte(i, rand(256)) } end (0...self.length).each { |i| self.setbyte(i, 0) } self.clear end 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