51 lines
1.5 KiB
Ruby
51 lines
1.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class PasswordHasher
|
|
class InvalidAlgorithmError < StandardError
|
|
end
|
|
|
|
class UnsupportedAlgorithmError < StandardError
|
|
end
|
|
|
|
HANDLERS = {}
|
|
|
|
def self.register_handler(id, &blk)
|
|
HANDLERS[id] = blk
|
|
end
|
|
|
|
# Algorithm should be specified according to the id/params parts of the
|
|
# PHC string format.
|
|
# https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
|
|
def self.hash_password(password:, salt:, algorithm:)
|
|
algorithm = algorithm.delete_prefix("$").delete_suffix("$")
|
|
|
|
parts = algorithm.split("$")
|
|
raise InvalidAlgorithmError if parts.length != 2
|
|
|
|
algorithm_id, algorithm_params = parts
|
|
|
|
algorithm_params = algorithm_params.split(",").map { |pair| pair.split("=") }.to_h
|
|
|
|
handler = HANDLERS[algorithm_id]
|
|
if handler.nil?
|
|
raise UnsupportedAlgorithmError.new "#{algorithm_id} is not a supported password algorithm"
|
|
end
|
|
|
|
handler.call(password: password, salt: salt, params: algorithm_params)
|
|
end
|
|
|
|
register_handler("pbkdf2-sha256") do |password:, salt:, params:|
|
|
raise ArgumentError.new("Salt and password must be supplied") if password.blank? || salt.blank?
|
|
|
|
if params["l"].to_i != 32
|
|
raise UnsupportedAlgorithmError.new("pbkdf2 implementation only supports l=32")
|
|
end
|
|
|
|
if params["i"].to_i < 1
|
|
raise UnsupportedAlgorithmError.new("pbkdf2 iterations must be 1 or more")
|
|
end
|
|
|
|
Pbkdf2.hash_password(password, salt, params["i"].to_i, "sha256")
|
|
end
|
|
end
|