Class: HTAuth::Algorithm

Inherits:
Object
  • Object
show all
Extended by:
DescendantTracker
Defined in:
lib/htauth/algorithm.rb

Overview

Internal: Base class all the password algorithms derive from

Direct Known Subclasses

Argon2, Bcrypt, Crypt, Md5, Plaintext, Sha1

Constant Summary collapse

SALT_CHARS =
(%w[ . / ] + ("0".."9").to_a + ('A'..'Z').to_a + ('a'..'z').to_a).freeze
SALT_LENGTH =
8
ARGON2 =

Public: flag for the argon2 algorithm

"argon2".freeze
BCRYPT =

Public: flag for the bcrypt algorithm

"bcrypt".freeze
MD5 =

Public: flag for the md5 algorithm

"md5".freeze
SHA1 =

Public: flag for the sha1 algorithm

"sha1".freeze
PLAINTEXT =

Public: flag for the plaintext algorithm

"plaintext".freeze
CRYPT =

Public: flag for the crypt algorithm

"crypt".freeze
DEFAULT =

Public: flag for the default algorithm

MD5
EXISTING =

Public: flag to indicate using the existing algorithm of the entry

"existing".freeze

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DescendantTracker

children, find_child, inherited

Class Method Details

.algorithm_from_field(password_field) ⇒ Object

NOTE: if it is plaintext, and the length is 13 - it may matched crypt

and be tested that way. If that is the case - this is explicitly
siding with crypt() as you shouldn't be using plaintext. Or
crypt for that matter.


54
55
56
57
58
59
60
61
# File 'lib/htauth/algorithm.rb', line 54

def algorithm_from_field(password_field)
  match = find_child(:handles?, password_field)
  match = ::HTAuth::Plaintext if match.nil? && ::HTAuth::Plaintext.entry_matches?(password_field)

  raise InvalidAlgorithmError, "unknown encryption algorithm used for `#{password_field}`" if match.nil?

  return match.new(:existing => password_field)
end

.algorithm_from_name(a_name, params = {}) ⇒ Object



41
42
43
44
45
46
47
48
# File 'lib/htauth/algorithm.rb', line 41

def algorithm_from_name(a_name, params = {})
  found = children.find { |c| c.algorithm_name == a_name }
  if !found then
    names = children.map { |c| c.algorithm_name }
    raise InvalidAlgorithmError, "`#{a_name}' is an unknown encryption algorithm, use one of #{names.join(', ')}"
  end
  return found.new(params)
end

.algorithm_nameObject



37
38
39
# File 'lib/htauth/algorithm.rb', line 37

def algorithm_name
  self.name.split("::").last.downcase
end

.handles?(password_entry) ⇒ Boolean

Internal: Does this class handle this type of password entry

Returns:

  • (Boolean)

Raises:

  • (NotImplementedError)


65
66
67
# File 'lib/htauth/algorithm.rb', line 65

def handles?(password_entry)
  raise NotImplementedError, "#{self.name} must implement #{self.name}.handles?(password_entry)"
end

.secure_compare(a, b) ⇒ Object

Internal: Constant time string comparison.

From github.com/rack/rack/blob/master/lib/rack/utils.rb

NOTE: the values compared should be of fixed length, such as strings that have already been processed by HMAC. This should not be used on variable length plaintext strings because it could leak length info via timing attacks.



77
78
79
80
81
82
83
84
85
# File 'lib/htauth/algorithm.rb', line 77

def secure_compare(a, b)
  return false unless a.bytesize == b.bytesize

  l = a.unpack("C*")

  r, i = 0, -1
  b.each_byte { |v| r |= v ^ l[i+=1] }
  r == 0
end

Instance Method Details

#encode(password) ⇒ Object

Internal

Raises:

  • (NotImplementedError)


89
90
91
# File 'lib/htauth/algorithm.rb', line 89

def encode(password)
  raise NotImplementedError, "#{self.class.name} must implement #{self.class.name}.encode(password)"
end

#gen_salt(length = SALT_LENGTH) ⇒ Object

Internal: 8 bytes of random items from SALT_CHARS



102
103
104
# File 'lib/htauth/algorithm.rb', line 102

def gen_salt(length = SALT_LENGTH)
  Array.new(length) { SALT_CHARS.sample }.join('')
end

#to_64(number, rounds) ⇒ Object

Internal: this is not the Base64 encoding, this is the to64() method from the Apache Portable Runtime (APR) library github.com/apache/apr/blob/trunk/crypto/apr_md5.c#L493-L502



109
110
111
112
113
114
115
116
# File 'lib/htauth/algorithm.rb', line 109

def to_64(number, rounds)
  r = StringIO.new
  rounds.times do |x|
    r.print(SALT_CHARS[number % 64])
    number >>= 6
  end
  return r.string
end

#verify_password?(password, digest) ⇒ Boolean

Internal: Does the given password match the digest, the default just encodes and secure compares the result, different algorithms may overide this method

Returns:

  • (Boolean)


96
97
98
99
# File 'lib/htauth/algorithm.rb', line 96

def verify_password?(password, digest)
  encoded = encode(password)
  self.class.secure_compare(encoded, digest)
end