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()
を直接呼び出すだけです。
私は次のコードをかなり迅速にコーディングし、実際にここで実際にテストする時間はありませんが、仕事をするべきです。問題が見つかった場合はお知らせください。確認します。
これにより、配列とハッシュが適切に平坦化およびソートされ、衝突が発生するためには、かなり奇妙な外観の文字列が必要になります。
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
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つの文字列に結合される部分のリストを作成します。見られるクラスのタイプがハッシュに影響を与えることを保証するために、途中で基本的なタイプ情報をエンコードする単一のユニコード文字を注入します。 (たとえば、["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
ちょうど私の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