lib/argon2/hash_format.rb



# frozen_string_literal: true

module Argon2
  ##
  # Get the values from an Argon2 compatible string.
  #
  class HashFormat
    attr_reader :variant, :version, :t_cost, :m_cost, :p_cost, :salt, :checksum

    # FIXME: Reduce complexity/AbcSize
    # rubocop:disable Metrics/AbcSize
    def initialize(digest)
      digest = digest.to_s unless digest.is_a?(String)

      raise Argon2::ArgonHashFail, 'Invalid Argon2 hash' unless self.class.valid_hash?(digest)

      _, variant, version, config, salt, checksum = digest.split('$')
      # Regex magic to extract the values for each setting
      version = /v=(\d+)/.match(version)
      t_cost  = /t=(\d+),/.match(config)
      m_cost  = /m=(\d+),/.match(config)
      p_cost  = /p=(\d+)/.match(config)

      # Make sure none of the values are missing
      raise Argon2::ArgonHashFail, 'Invalid Argon2 version' if version.nil?
      raise Argon2::ArgonHashFail, 'Invalid Argon2 time cost' if t_cost.nil?
      raise Argon2::ArgonHashFail, 'Invalid Argon2 memory cost' if m_cost.nil?
      raise Argon2::ArgonHashFail, 'Invalid Argon2 parallelism cost' if p_cost.nil?

      @variant  = variant.to_str
      @version  = version[1].to_i
      @t_cost   = t_cost[1].to_i
      @m_cost   = m_cost[1].to_i
      @p_cost   = p_cost[1].to_i
      @salt     = salt.to_str
      @checksum = checksum.to_str
    end
    # rubocop:enable Metrics/AbcSize

    ##
    # Checks whether a given digest is a valid Argon2 hash.
    #
    # Supports 1 and argon2id formats.
    #
    def self.valid_hash?(digest)
      /^\$argon2(id?|d).{,113}/ =~ digest
    end
  end
end