Создание md5-хэша числа, строки, массива или хэша в Ruby - PullRequest
40 голосов
/ 24 июня 2011

Мне нужно создать строку подписи для переменной в Ruby, где переменная может быть числом, строкой, хэшем или массивом.Хеш-значения и элементы массива также могут быть любого из этих типов.

Эта строка будет использоваться для сравнения значений в базе данных (в данном случае Mongo).

Моя первая мысль былачтобы создать MD5-хэш JSON-кодированного значения, например, так: (body - это переменная, указанная выше)

def createsig(body)    
  Digest::MD5.hexdigest(JSON.generate(body))
end

Это почти работает, но JSON.generate не кодирует ключи хэша вкаждый раз один и тот же порядок, поэтому createsig({:a=>'a',:b=>'b'}) не всегда равен createsig({:b=>'b',:a=>'a'}).

Каков наилучший способ создания строки подписи, чтобы удовлетворить эту потребность?

Примечание: Для ориентированных на деталисреди нас я знаю, что вы не можете JSON.generate() число или строку.В этих случаях я бы просто позвонил MD5.hexdigest() напрямую.

Ответы [ 5 ]

31 голосов
/ 24 июня 2011

Я довольно быстро кодирую следующее, и у меня нет времени, чтобы по-настоящему проверить это здесь, на работе, но это должно сработать.Дайте мне знать, если вы обнаружите какие-либо проблемы с ним, и я посмотрю.

Это должно правильно сгладить и отсортировать массивы и хэши, и вам понадобится несколько довольно странно выглядящих строк длятам должны быть какие-либо столкновения.

def createsig(body)
  Digest::MD5.hexdigest( sigflat body )
end

def sigflat(body)
  if body.class == Hash
    arr = []
    body.each do |key, value|
      arr << "#{sigflat key}=>#{sigflat value}"
    end
    body = arr
  end
  if body.class == Array
    str = ''
    body.map! do |value|
      sigflat value
    end.sort!.each do |value|
      str << value
    end
  end
  if body.class != String
    body = body.to_s << body.class.to_s
  end
  body
end

> sigflat({:a => {:b => 'b', :c => 'c'}, :d => 'd'}) == sigflat({:d => 'd', :a => {:c => 'c', :b => 'b'}})
=> true
13 голосов
/ 25 июня 2011

Если бы вы могли получить только строковое представление body и не иметь хэша Ruby 1.8, возвращающегося с разными порядками от одного к другому, вы могли бы надежно хэшировать это строковое представление. Давайте испачкаем руки обезьянами:

require 'digest/md5'

class Object
  def md5key
    to_s
  end
end

class Array
  def md5key
    map(&:md5key).join
  end
end

class Hash
  def md5key
    sort.map(&:md5key).join
  end
end

Теперь любой объект (из типов, упомянутых в вопросе) отвечает на md5key, возвращая надежный ключ для использования при создании контрольной суммы, поэтому:

def createsig(o)
  Digest::MD5.hexdigest(o.md5key)
end

Пример:

body = [
  {
    'bar' => [
      345,
      "baz",
    ],
    'qux' => 7,
  },
  "foo",
  123,
]
p body.md5key        # => "bar345bazqux7foo123"
p createsig(body)    # => "3a92036374de88118faf19483fe2572e"

Примечание. Это хэш-представление не кодирует структуру, а только объединяет значения. Поэтому ["a", "b", "c"] будут хешировать так же, как ["abc"].

1 голос
/ 13 декабря 2013

Только мои 2 цента:

module Ext
  module Hash
    module InstanceMethods
      # Return a string suitable for generating content signature.
      # Signature image does not depend on order of keys.
      #
      #   {:a => 1, :b => 2}.signature_image == {:b => 2, :a => 1}.signature_image                  # => true
      #   {{:a => 1, :b => 2} => 3}.signature_image == {{:b => 2, :a => 1} => 3}.signature_image    # => true
      #   etc.
      #
      # NOTE: Signature images of identical content generated under different versions of Ruby are NOT GUARANTEED to be identical.
      def signature_image
        # Store normalized key-value pairs here.
        ar = []

        each do |k, v|
          ar << [
            k.is_a?(::Hash) ? k.signature_image : [k.class.to_s, k.inspect].join(":"),
            v.is_a?(::Hash) ? v.signature_image : [v.class.to_s, v.inspect].join(":"),
          ]
        end

        ar.sort.inspect
      end
    end
  end
end

class Hash    #:nodoc:
  include Ext::Hash::InstanceMethods
end
1 голос
/ 22 ноября 2013

Вот мое решение.Я обхожу структуру данных и строю список частей, которые объединяются в одну строку.Чтобы гарантировать, что видимые типы классов влияют на хеш, я вставляю один символ Unicode, который кодирует информацию о базовом типе.(Например, мы хотим ["1", "2", "3"]. Objsum! = [1,2,3] .objsum)

Я сделал это как уточнение для объекта, это легкопортирован на патч обезьяны.Чтобы использовать его просто нужно файл и запустить «использование ObjSum».

module ObjSum
  refine Object do
    def objsum
      parts = []
      queue = [self]

      while queue.size > 0
        item = queue.shift

        if item.kind_of?(Hash)
          parts << "\\000"
          item.keys.sort.each do |k| 
            queue << k
            queue << item[k]
          end
        elsif item.kind_of?(Set)
          parts << "\\001"
          item.to_a.sort.each { |i| queue << i }
        elsif item.kind_of?(Enumerable)
          parts << "\\002"
          item.each { |i| queue << i }
        elsif item.kind_of?(Fixnum)
          parts << "\\003"
          parts << item.to_s
        elsif item.kind_of?(Float)
          parts << "\\004"
          parts << item.to_s
        else
          parts << item.to_s
        end
      end

      Digest::MD5.hexdigest(parts.join)
    end
  end
end
0 голосов
/ 24 июня 2011

В зависимости от ваших потребностей, вы можете позвонить ary.inspect или ary.to_yaml, даже.

...