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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
|
# frozen-string-literal: true
require 'argon2'
# :nocov:
if !defined?(Argon2::VERSION) || Argon2::VERSION < '2'
raise LoadError, "argon2 version 1.x not supported as it does not support argon2id hashes"
end
# :nocov:
module Rodauth
Feature.define(:argon2, :Argon2) do
depends :login_password_requirements_base
auth_value_method :argon2_old_secret, nil
auth_value_method :argon2_secret, nil
auth_value_method :use_argon2?, true
def password_hash(password)
return super unless use_argon2?
if secret = argon2_secret
argon2_params = Hash[password_hash_cost]
argon2_params[:secret] = secret
else
argon2_params = password_hash_cost
end
::Argon2::Password.new(argon2_params).create(password)
end
private
if Argon2::VERSION != '2.1.0'
def argon2_salt_option
:salt_do_not_supply
end
# :nocov:
else
def argon2_salt_option
:salt_for_testing_purposes_only
end
# :nocov:
end
def password_hash_cost
return super unless use_argon2?
argon2_hash_cost
end
def password_hash_match?(hash, password)
return super unless argon2_hash_algorithm?(hash)
argon2_password_hash_match?(hash, password)
end
def password_hash_using_salt(password, salt)
return super unless argon2_hash_algorithm?(salt)
argon2_password_hash_using_salt_and_secret(password, salt, argon2_secret)
end
def argon2_password_hash_using_salt_and_secret(password, salt, secret)
argon2_params = Hash[extract_password_hash_cost(salt)]
argon2_params[argon2_salt_option] = salt.split('$').last.unpack("m")[0]
argon2_params[:secret] = secret
::Argon2::Password.new(argon2_params).create(password)
end
if Argon2::VERSION >= '2.1'
def extract_password_hash_cost(hash)
return super unless argon2_hash_algorithm?(hash)
/\A\$argon2id\$v=\d+\$m=(\d+),t=(\d+),p=(\d+)/ =~ hash
{ t_cost: $2.to_i, m_cost: Math.log2($1.to_i).to_i, p_cost: $3.to_i }
end
if ENV['RACK_ENV'] == 'test'
def argon2_hash_cost
{ t_cost: 1, m_cost: 5, p_cost: 1 }
end
# :nocov:
else
def argon2_hash_cost
{ t_cost: 2, m_cost: 16, p_cost: 1 }
end
end
else
def extract_password_hash_cost(hash)
return super unless argon2_hash_algorithm?(hash )
/\A\$argon2id\$v=\d+\$m=(\d+),t=(\d+)/ =~ hash
{ t_cost: $2.to_i, m_cost: Math.log2($1.to_i).to_i }
end
if ENV['RACK_ENV'] == 'test'
def argon2_hash_cost
{ t_cost: 1, m_cost: 5 }
end
else
def argon2_hash_cost
{ t_cost: 2, m_cost: 16 }
end
end
end
# :nocov:
def argon2_hash_algorithm?(hash)
hash.start_with?('$argon2id$')
end
def argon2_password_hash_match?(hash, password)
ret = ::Argon2::Password.verify_password(password, hash, argon2_secret)
if ret == false && argon2_old_secret != argon2_secret && (ret = ::Argon2::Password.verify_password(password, hash, argon2_old_secret))
@update_password_hash = true
end
ret
end
def database_function_password_match?(name, hash_id, password, salt)
return true if super
if use_argon2? && argon2_hash_algorithm?(salt) && argon2_old_secret != argon2_secret && (ret = db.get(Sequel.function(function_name(name), hash_id, argon2_password_hash_using_salt_and_secret(password, salt, argon2_old_secret))))
@update_password_hash = true
end
!!ret
end
end
end
|