forked from discourse/discourse
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpassword_hasher.rb
50 lines (36 loc) · 1.46 KB
/
password_hasher.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 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