1

我在我的 Ruby 程序中使用了 bcrypt 库。我注意到相等运算符的顺序似乎很重要。根据'=='左侧或右侧的变量,我得到不同的结果。这是一个示例程序:

require 'bcrypt'
my_pw = "pw1"
puts "This is my unhashed password: #{my_pw}"
hashed_pw = BCrypt::Password.create(my_pw)
puts "This is my hashed password: #{hashed_pw}"

20.times{print"-"}
puts

puts "my_pw == hashed_pw equals:"
if (my_pw == hashed_pw)
  puts "TRUE"
else
  puts "FALSE"
end

puts "hashed_pw == my_pw equals:"
if (hashed_pw == my_pw)
  puts "TRUE"
else
  puts "FALSE"
end

问候尚德

4

4 回答 4

4

是,有一点不同。

my_pw == hashed_pw调用字符串==上的方法并作为参数传递。这意味着您正在使用该方法。来自以下文档my_pwhashed_pwString#==String#==

string == object → true or false

true如果object长度和内容相同,则返回;如self; false否则

而在实例上hashed_pw == my_pw调用方法并作为参数传递。来自以下文档:==BCrypt::Passwordmy_pwBCrypt::Password#==

#==(secret) ⇒ Object

将潜在秘密与哈希进行比较。true如果秘密是原始秘密,则返回,false否则。

于 2021-02-15T08:48:39.493 回答
1

这与平等没有任何关系。这只是面向对象编程的基础。

在 OOP 中,所有计算都是通过对象向其他对象发送消息来完成的。OOP的基本属性之一是接收者对象并且只有接收者对象决定如何响应此消息。这就是在 OOP 中实现封装、数据隐藏和抽象的方式。

因此,如果您将消息发送到作为参数传递的m对象,则可以决定如何解释此消息。如果您将消息发送到作为参数传递的对象,则由它来决定如何解释此消息。没有内置机制可以保证和解释此消息相同。只有当两个对象决定相互协调时,响应才会真正相同。abambabab

如果你想一想,如果2 - 33 - 2有相同的结果,那将是非常奇怪的。

这正是这里发生的事情:在第一个示例中,您将消息发送==到,作为参数my_pw传递。是class的一个实例,因此消息将被分派给该方法。此方法知道如何将接收者对象与另一个对象进行比较。但是,它确实不知道如何将接收器与 a 进行比较,这就是 of 的类别。hashed_pwmy_pwString==String#==StringBCrypt::Passwordhashed_pw

如果你仔细想想,这是有道理的:BCrypt::Password一个来自 Ruby 之外的第三方类,一个内置的 Ruby 类怎么会知道在实现该类时甚至不存在的东西String

另一方面,在第二个示例中,您将消息发送==到,作为参数hashed_pw传递。my_pw此消息被分派给该BCrypt::Password#==方法,该方法确实知道如何将接收者与 a 进行比较String

方法:BCrypt::Password#==

定义在:lib/bcrypt/password.rb

#==(secret)Object
也称为:is_password?

将潜在秘密与哈希进行比较。true如果秘密是原始秘密,则返回,false否则。

实际上,这种特殊情况下的问题比最初看起来更微妙。

我在上面写了String#==不知道如何处理 aBCrypt::Password作为参数,因为它只知道如何比较Strings。嗯,实际上BCrypt::Password继承自String,意思是一个BCrypt::Password IS-A String,所以String#== 应该知道如何处理它!

但想想是什么String#==

string == objecttruefalse

true如果object长度和内容相同,则返回;如self; false否则 […]

想一想:“true如果object长度和内容相同,则返回”。对于哈希,这实际上永远不会是真的。selfwill be something like'P@$$w0rd!'objectwill be something like '$2y$12$bxWYpd83lWyIr.dF62eO7.gp4ldf2hMxDofXPwdDZsnc2bCE7hN9q',很明显,它们的长度和内容都不相同。并且object 不可能是相同的内容,因为加密安全密码哈希的全部意义在于您无法反转它。因此,即使object以某种方式想将自己显示为原始密码,它也无法做到。

唯一可行的方法是,如果String并且BCrypt::Password可以以某种方式“共同努力”来确定平等。

现在,如果我们仔细查看 的文档String#==,实际上有一种方法可以完成这项工作:

如果object不是 String 的实例但响应to_str,则使用 比较两个字符串object.==

所以,如果作者BCrypt::Password做出了不同的设计决定,那么它会起作用:

  1. 不要让BCrypt::Password继承自String.
  2. 实施BCrypt::Password#to_str。这实际上将允许BCrypt::Password与实际互换使用,String因为任何接受Strings 的方法也应该接受响应 的对象to_str

现在,根据 的文档String#==,如果您编写my_pw == hashed_pw,会发生以下情况:

  1. String#==通知hashed_pw不是String.
  2. String#==hashed_pw 确实响应的通知to_str
  3. 因此,它将发送消息object == self(在我们的情况下相当于hashed_pw == my_pw),这意味着我们现在处于您问题的第二种情况,这工作得很好。

这是一个如何工作的示例:

class Pwd
  def initialize(s)
    @s = s.downcase.freeze
  end

  def to_str
    p __callee__
    @s.dup.freeze
  end

  def ==(other)
    p __callee__, other
    @s == other.downcase
  end

end

p = Pwd.new('HELLO')
s = 'hello'

p == s
# :==
# "hello"
#=> true

s == p
# :==
# "hello"
#=> true

如您所见,我们得到了预期的结果,并且Pwd#==两次都被调用。此外,to_str永远不会被调用,它只会被String#==.

因此,具有讽刺意味的是,问题不在于String#==不知道如何处理BCrypt::Password对象,而实际上问题在于它确实知道如何将它们作为泛型Strings处理。如果他们不是Strings 而只​​是回应to_str,那么String#==实际上会知道向他们寻求帮助。

NumericRuby 中的对象具有完整的强制协议,以确保即使第三方数字库也支持不同“类似数字”操作数类型之间的算术运算。

于 2021-02-15T09:30:32.873 回答
0

例如,如果两个操作数都是 String 类型,则表达式将是等价的。在您的情况下,一个操作数是 a String,另一个是 a BCrypt::Password。因此my_pw == hashed_pw调用 String 类中定义的相等方法,同时hashed_pw == my_pw调用 BCrypt::Password 中定义的方法。

我从未使用过 BCrypt::Password,但希望你能得到false前者和true后者。

于 2021-02-15T08:42:06.463 回答
0

在 Ruby 中,您可以覆盖给定类或实例的相等方法:

class Test
  define_method(:==) do |_other|
    true
  end
end

Test.new == 'foo' # => true
Test.new == nil # => true
Test.new == 42 # => true

'foo' == Test.new # => false
nil == Test.new # => false
42 == Test.new # => true

一般来说,在不使其对称的情况下覆盖平等被认为是一种不好的做法,但您有时会在野外看到它。

于 2021-02-15T08:47:14.707 回答