From 9597533827b5274a1947da56000728c20af397e7 Mon Sep 17 00:00:00 2001 From: Griffin Smith Date: Thu, 6 Aug 2015 14:06:03 -0400 Subject: [PATCH 1/2] Use a constant-time secure comparison for passwords Use a constant-time byte-by-byte secure comparison to compare potential password hashes rather than `String#==`, which uses strcmp under the hood and stops as soon as there's an unmatched byte. --- lib/bcrypt/password.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/bcrypt/password.rb b/lib/bcrypt/password.rb index 3160c9b..db09009 100644 --- a/lib/bcrypt/password.rb +++ b/lib/bcrypt/password.rb @@ -76,7 +76,14 @@ def initialize(raw_hash) # # secret == @password # => probably False, because the secret is not a BCrypt::Password instance. def ==(secret) - super(BCrypt::Engine.hash_secret(secret, @salt)) + hash = BCrypt::Engine.hash_secret(secret, @salt) + + return false if hash.strip.empty? || strip.empty? || hash.bytesize != bytesize + l = hash.unpack "C#{hash.bytesize}" + + res = 0 + each_byte { |byte| res |= byte ^ l.shift } + res == 0 end alias_method :is_password?, :== From a7a868e12d80c6e0a75d81acdfdc3c4a763a622b Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 29 Oct 2024 16:04:46 -0700 Subject: [PATCH 2/2] Lets not allocate anything --- lib/bcrypt/password.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/bcrypt/password.rb b/lib/bcrypt/password.rb index db09009..0c432d6 100644 --- a/lib/bcrypt/password.rb +++ b/lib/bcrypt/password.rb @@ -79,10 +79,10 @@ def ==(secret) hash = BCrypt::Engine.hash_secret(secret, @salt) return false if hash.strip.empty? || strip.empty? || hash.bytesize != bytesize - l = hash.unpack "C#{hash.bytesize}" + # Constant time comparison so they can't tell the length. res = 0 - each_byte { |byte| res |= byte ^ l.shift } + bytesize.times { |i| res |= getbyte(i) ^ hash.getbyte(i) } res == 0 end alias_method :is_password?, :==