class AspnetPasswordHasher::PasswordHasher
Public Class Methods
new(options = {})
click to toggle source
# File lib/aspnet_password_hasher/password_hasher.rb, line 9 def initialize(options = {}) @mode = options[:mode] || :v3 @rng = options[:random_number_generator] || SecureRandom case @mode when :v2 @iter_count = 0 when :v3 @iter_count = options[:iter_count] || 10000 if @iter_count < 1 raise ArgumentError, "Invalid password hasher iteration count" end else raise ArgumentError, "Invalid password hasher compatibility mode" end end
Public Instance Methods
hash_password(password)
click to toggle source
# File lib/aspnet_password_hasher/password_hasher.rb, line 26 def hash_password(password) bytes = case @mode when :v2 hash_password_v2(password) when :v3 hash_password_v3(password) end Base64.strict_encode64(bytes) end
verify_hashed_password(hashed_password, provided_password)
click to toggle source
# File lib/aspnet_password_hasher/password_hasher.rb, line 36 def verify_hashed_password(hashed_password, provided_password) decoded_hashed_password = Base64.strict_decode64(hashed_password) case decoded_hashed_password[0] when "\x00" # v2 if verify_hashed_password_v2(decoded_hashed_password, provided_password) @mode == :v3 ? :success_rehash_needed : :success else :failed end when "\x01" # v3 result, embed_iter_count = verify_hashed_password_v3(decoded_hashed_password, provided_password) if result embed_iter_count < @iter_count ? :success_rehash_needed : :success else :failed end else :failed end end
Private Instance Methods
hash_password_v2(password)
click to toggle source
# File lib/aspnet_password_hasher/password_hasher.rb, line 61 def hash_password_v2(password) iter_count = 1000 # default for Rfc2898DeriveBytes subkey_len = 256 / 8 # 256 bits salt_size = 128 / 8 # 128 bits salt = @rng.bytes(salt_size) digest = OpenSSL::Digest::SHA1.new subkey = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iter_count, subkey_len, digest) output_bytes = String.new output_bytes << "\x00" # format marker output_bytes << salt output_bytes << subkey output_bytes end
hash_password_v3(password)
click to toggle source
# File lib/aspnet_password_hasher/password_hasher.rb, line 77 def hash_password_v3(password) prf = 1 # HMACSHA256 salt_size = 128 / 8 num_bytes_requested = 256 / 8 salt = @rng.bytes(salt_size) digest = OpenSSL::Digest::SHA256.new subkey = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, @iter_count, num_bytes_requested, digest) output_bytes = String.new output_bytes << "\x01" # format marker [prf].pack('N', buffer: output_bytes) [@iter_count].pack('N', buffer: output_bytes) [salt_size].pack('N', buffer: output_bytes) output_bytes << salt output_bytes << subkey output_bytes end
verify_hashed_password_v2(hashed_password, password)
click to toggle source
# File lib/aspnet_password_hasher/password_hasher.rb, line 96 def verify_hashed_password_v2(hashed_password, password) iter_count = 1000 # default for Rfc2898DeriveBytes subkey_len = 256 / 8 # 256 bits salt_size = 128 / 8 # 128 bits if hashed_password.length != 1 + subkey_len + salt_size return false # bad size end salt = hashed_password[1..salt_size] expected_subkey = hashed_password[(salt_size + 1)...hashed_password.length] digest = OpenSSL::Digest::SHA1.new actual_subkey = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iter_count, subkey_len, digest) expected_subkey == actual_subkey end
verify_hashed_password_v3(hashed_password, password)
click to toggle source
# File lib/aspnet_password_hasher/password_hasher.rb, line 113 def verify_hashed_password_v3(hashed_password, password) prf = hashed_password[1..4].unpack('N')[0] iter_count = hashed_password[5..8].unpack('N')[0] salt_len = hashed_password[9..12].unpack('N')[0] # salt must be >= 128 bits if salt_len < 128 / 8 return [false, nil] end salt = hashed_password[13...(13 + salt_len)] subkey_len = hashed_password.length - 13 - salt_len # subkey must by >= 128 bits if subkey_len < 128 / 8 return [false, nil] end expected_subkey = hashed_password[(13 + salt_len)...hashed_password.length] digest = case prf when 0 OpenSSL::Digest::SHA1.new when 1 OpenSSL::Digest::SHA256.new when 2 OpenSSL::Digest::SHA512.new end actual_subkey = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iter_count, subkey_len, digest) [expected_subkey == actual_subkey, iter_count] rescue StandardError [false, nil] end