45

我需要为 Ruby 中的变量创建签名字符串,其中变量可以是数字、字符串、散列或数组。哈希值和数组元素也可以是这些类型中的任何一种。

此字符串将用于比较数据库中的值(在本例中为 Mongo)。

我的第一个想法是创建一个 JSON 编码值的 MD5 哈希,如下所示:(body 是上面提到的变量)

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

这几乎可行,但 JSON.generate 不会每次都以相同的顺序对哈希的键进行编码,因此createsig({:a=>'a',:b=>'b'})并不总是 equal createsig({:b=>'b',:a=>'a'})

创建签名字符串以满足此需求的最佳方法是什么?

注意:对于我们之间的细节导向,我知道你不能JSON.generate()数字或字符串。在这些情况下,我会直接打电话MD5.hexdigest()

4

6 回答 6

33

我很快就编写了以下代码,没有时间在工作中真正测试它,但它应该可以完成这项工作。如果您发现任何问题,请告诉我,我会看看。

这应该正确地展平和排序数组和散列,并且您需要一些看起来很奇怪的字符串才能发生任何冲突。

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
于 2011-06-24T02:02:48.267 回答
15

如果您只能获得 的字符串表示,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"] 相同。

于 2011-06-24T21:53:31.727 回答
1

只是我的 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
于 2013-12-13T18:09:06.623 回答
1

这是我的解决方案。我遍历数据结构并建立一个连接成单个字符串的片段列表。为了确保看到的类类型会影响散列,我注入了一个单一的 unicode 字符,该字符沿途对基本类型信息进行编码。(例如,我们想要 ["1", "2", "3"].objsum != [1,2,3].objsum)

我这样做是对 Object 的改进,它很容易移植到猴子补丁。要使用它,只需要该文件并运行“使用 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
于 2013-11-22T07:18:02.137 回答
1

现在有一个正式定义的 JSON 规范化方法,正是因为这个原因:https ://datatracker.ietf.org/doc/html/draft-rundgren-json-canonicalization-scheme-16

这里有一个 ruby​​ 实现:https ://github.com/dryruby/json-canonicalization

于 2021-06-16T12:11:57.530 回答
-1

根据您的需要,您可以调用ary.inspectary.to_yaml,甚至。

于 2011-06-24T03:06:06.610 回答