require 'dionysus' require 'digest' ## # Convenience methods for the Digest module. # # require 'dionysus/digest' # # The Digest::DEFAULT_DIGESTS are automatically registered, if they # exist. You can register additional digests with Digest.register_digest. # The given class must give the digest with the digest(string). # # TODO add digest detection -- by length and by proc on the digests hash module Digest DEFAULT_DIGESTS = [:md5, :sha1, :sha2, :sha256, :sha384, :sha512] @digests = {} ## # Register a digest. Raises an error if the interpreted class doesn't exist. # It will interpret the klass as Digest::SYM if it's nil, # and it will run the digest method on the klass to determine the # digests bit length if bits is nil. # # This will register :my_digest and automatically determine the bit # length by executing the class's digest method on the string # '1': # # Digest.register_digest!( :my_digest, :klass => MyDigestClass ) # # Options: # [klass] The digest class (also can be an arbitrary object). Default: # Digest::#{sym.to_s.upcase} # [bit_length] The bit length of the digest. Default: calculated by # running the digest on the string '1'. # [method] The calculation method for the digest. Default: # :digest def self.register_digest!( sym, options = {} ) options = options.with_indifferent_access options[:method] ||= :digest options[:klass] ||= "Digest::#{sym.to_s.upcase}".constantize options[:bit_length] ||= options[:klass].send(options[:method], '1').length * 8 @digests[sym.to_sym] = options end ## # Register a digest. Returns nil if an error occurs. def self.register_digest( sym, options = {} ) self.register_digest!(sym, options) rescue LoadError nil end ## # The hash of registered digests. def self.digests @digests end ## # The available digests. def self.available_digests self.digests.keys end ## # The lengths of the registered digests in the given encoding. def self.digest_lengths( encoding = :binary ) if encoding == :bit or encoding == 1 _digest_lengths(1) elsif encoding.is_a?(Symbol) and String::ENCODING_BITS_PER_CHAR[encoding] _digest_lengths(String::ENCODING_BITS_PER_CHAR[encoding]) elsif encoding.is_a?(Integer) and encoding > 0 _digest_lengths(encoding) else raise ArgumentError, "Invalid encoding" end end ## # Calculate the given digest of the given string. # # Examples: # # Digest.digest(:sha512, 'foobar') #=> binary digest # Digest.digest(Digest::SHA512, 'foobar') #=> binary digest def self.digest( sym, str ) Digest.const_get(sym.to_s.upcase).digest(str) end ## # Detect the digest of the string. Returns nil if the digest cannot be # determined. # # Example: # Digest.detect_digest("wxeCFXPVXePFcpwuFDjonyn1G/w=", :base64) #=> :sha1 # Digest.detect_digest("foobar", :hex) #=> nil def self.detect_digest( string, encoding = :binary ) string = string.strip unless encoding == :binary dig = self.digest_lengths(encoding).invert[string.length] dig = :sha256 if dig == :sha2 dig end ## # Detect the digest of the string. Returns nil if the digest cannot be # determined. # # Example: # Digest.detect_digest!("wxeCFXPVXePFcpwuFDjonyn1G/w=", :base64) #=> :sha1 # Digest.detect_digest!("foobar", :hex) #=> RuntimeError def self.detect_digest!( string, encoding = :binary ) self.detect_digest(string, encoding) or raise("Unknown digest") end private def self._digest_lengths( bits_per_char ) # :nodoc: padding_factor = (bits_per_char.lcm(8) / bits_per_char) returning result={} do self.digests.each do |dig, info| result[dig] = len = info[:bit_length] / bits_per_char if (t_ = len % padding_factor) != 0 result[dig] = len + (padding_factor - t_) end end end end end # Register some default digests Digest::DEFAULT_DIGESTS.each do |dig| Digest.register_digest(dig) end