我需要在Ruby中为变量创建一个签名字符串,其中变量可以是数字,字符串,散列或数组。哈希值和数组元素也可以是这些类型中的任何一种。
此字符串将用于比较数据库中的值(在本例中为Mongo)。
我的第一个想法是创建一个JSON编码值的MD5哈希值,如下所示:( 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()
。
答案 0 :(得分:31)
我快速编写以下代码并且没有时间在工作中对它进行真正的测试,但它应该完成这项工作。如果您发现任何问题,请告诉我,我会看看。
这应该适当地展平并对数组和散列进行排序,并且你需要有一些非常奇怪的字符串才能产生任何碰撞。
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
答案 1 :(得分:13)
如果你只能获得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”]一样哈希。
答案 2 :(得分: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
答案 3 :(得分: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
答案 4 :(得分:0)
现在有一种规范化 JSON 的正式定义方法,正是出于这个原因:https://datatracker.ietf.org/doc/html/draft-rundgren-json-canonicalization-scheme-16
这里有一个 ruby 实现:https://github.com/dryruby/json-canonicalization
答案 5 :(得分:-1)
根据您的需要,您甚至可以致电ary.inspect
或ary.to_yaml
。