openstreetmap-website/lib/password_hash.rb
Tom Hughes 55a05d9e80 Use secure_compare to compare passwords and tokens
It's unlikely there is an explotable attack here given than network
latencies and variability will swamp any local timing differences but
it's best practice and there's no reason not to.
2023-11-07 17:22:40 +00:00

46 lines
1.4 KiB
Ruby

require "argon2"
require "base64"
require "digest/md5"
require "openssl"
require "securerandom"
module PasswordHash
FORMAT = Argon2::HashFormat.new(Argon2::Password.create(""))
def self.create(password)
hash = Argon2::Password.create(password)
[hash, nil]
end
def self.check(hash, salt, candidate)
if Argon2::HashFormat.valid_hash?(hash)
Argon2::Password.verify_password(candidate, hash)
elsif salt.nil?
ActiveSupport::SecurityUtils.secure_compare(hash, Digest::MD5.hexdigest(candidate))
elsif salt.include?("!")
algorithm, iterations, salt = salt.split("!")
size = Base64.strict_decode64(hash).length
ActiveSupport::SecurityUtils.secure_compare(hash, pbkdf2(candidate, salt, iterations.to_i, size, algorithm))
else
ActiveSupport::SecurityUtils.secure_compare(hash, Digest::MD5.hexdigest(salt + candidate))
end
end
def self.upgrade?(hash, _salt)
format = Argon2::HashFormat.new(hash)
format.variant != FORMAT.variant ||
format.version != FORMAT.version ||
format.t_cost != FORMAT.t_cost ||
format.m_cost != FORMAT.m_cost ||
format.p_cost != FORMAT.p_cost
rescue Argon2::ArgonHashFail
true
end
def self.pbkdf2(password, salt, iterations, size, algorithm)
digest = OpenSSL::Digest.new(algorithm)
pbkdf2 = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, size, digest)
Base64.strict_encode64(pbkdf2)
end
end