Class: ApiKeys::Services::Digestor
- Inherits:
-
Object
- Object
- ApiKeys::Services::Digestor
- Defined in:
- lib/api_keys/services/digestor.rb
Overview
Handles hashing (digesting) and verifying tokens based on configured strategy.
Constant Summary collapse
- BCRYPT_MAX_SECRET_BYTESIZE =
if BCrypt::Engine.const_defined?(:MAX_SECRET_BYTESIZE) BCrypt::Engine::MAX_SECRET_BYTESIZE else 72 end
- BCRYPT_MAX_SAFE_COST =
Costs above this value can turn a malformed/imported database row into a multi-second (or worse) CPU denial of service during authentication. The default bcrypt cost is comfortably below this ceiling.
16- MAX_TOKEN_BYTESIZE =
512
Class Method Summary collapse
-
.digest(token:, strategy: ApiKeys.configuration.hash_strategy) ⇒ Hash
Creates a digest of the given token using the configured strategy.
-
.match?(token:, stored_digest:, strategy: ApiKeys.configuration.hash_strategy, comparison_proc: ApiKeys.configuration.secure_compare_proc) ⇒ Boolean
Securely compares a plaintext token against a stored digest.
-
.valid_bcrypt_digest?(stored_digest) ⇒ Boolean
Returns whether a stored bcrypt digest is structurally valid and has a bounded cost that is safe to evaluate on an authentication request.
Class Method Details
.digest(token:, strategy: ApiKeys.configuration.hash_strategy) ⇒ Hash
Creates a digest of the given token using the configured strategy.
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 |
# File 'lib/api_keys/services/digestor.rb', line 27 def self.digest(token:, strategy: ApiKeys.configuration.hash_strategy) validate_token!(token) case strategy when :bcrypt if token.bytesize > BCRYPT_MAX_SECRET_BYTESIZE raise ArgumentError, "BCrypt tokens must not exceed #{BCRYPT_MAX_SECRET_BYTESIZE} bytes because BCrypt truncates longer inputs." end unless safe_bcrypt_cost?(BCrypt::Engine.cost) raise ArgumentError, "BCrypt cost must be between #{BCrypt::Engine::MIN_COST} and #{BCRYPT_MAX_SAFE_COST}." end # BCrypt handles salt generation internally digest = BCrypt::Password.create(token, cost: BCrypt::Engine.cost) { digest: digest.to_s, algorithm: "bcrypt" } when :sha256 # Note: Simple SHA256 without salt/pepper. Consider enhancing if needed. # BCrypt is generally preferred for password/token hashing. digest = Digest::SHA256.hexdigest(token) { digest: digest, algorithm: "sha256" } else raise ArgumentError, "Unsupported hash strategy: #{strategy}. Use :bcrypt or :sha256." end end |
.match?(token:, stored_digest:, strategy: ApiKeys.configuration.hash_strategy, comparison_proc: ApiKeys.configuration.secure_compare_proc) ⇒ Boolean
Securely compares a plaintext token against a stored digest. Uses the configured secure comparison proc and hash strategy.
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 |
# File 'lib/api_keys/services/digestor.rb', line 63 def self.match?(token:, stored_digest:, strategy: ApiKeys.configuration.hash_strategy, comparison_proc: ApiKeys.configuration.secure_compare_proc) return false unless valid_match_inputs?(token, stored_digest) case strategy when :bcrypt return false if token.bytesize > BCRYPT_MAX_SECRET_BYTESIZE bcrypt_object = validated_bcrypt_password(stored_digest) return false unless bcrypt_object # BCrypt's `==` operator is designed for secure comparison. bcrypt_object == token when :sha256 # Directly compare the SHA256 hash of the input token with the stored digest return false unless stored_digest.match?(/\A\h{64}\z/) # A custom comparator is security-sensitive. Accept only the literal # boolean true so truthy sentinel/error values can never authenticate. comparison_proc.call(stored_digest, Digest::SHA256.hexdigest(token)) == true else # Strategy mismatch or unsupported strategy should fail comparison safely if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger Rails.logger.error "[ApiKeys] Digestor comparison failed: Unsupported hash strategy '#{strategy}' for digest check." end false end rescue StandardError => error # A malformed digest or application-supplied comparison proc must never # turn an authentication failure into an exception or an availability issue. if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger Rails.logger.error "[ApiKeys] Digestor comparison error (#{error.class})." end false end |
.valid_bcrypt_digest?(stored_digest) ⇒ Boolean
Returns whether a stored bcrypt digest is structurally valid and has a bounded cost that is safe to evaluate on an authentication request.
100 101 102 |
# File 'lib/api_keys/services/digestor.rb', line 100 def self.valid_bcrypt_digest?(stored_digest) !validated_bcrypt_password(stored_digest).nil? end |