Module: Capsium::Reactor::Htpasswd::Md5Crypt

Defined in:
lib/capsium/reactor/htpasswd.rb,
sig/capsium/reactor/htpasswd.rbs

Overview

md5-crypt as deployed by Apache htpasswd, OpenSSL and glibc.

Constant Summary collapse

ITOA64 =

Returns:

  • (String)
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
DIGEST_GROUPS =

Returns:

  • (Array[Array[Integer]])
[[0, 6, 12], [1, 7, 13], [2, 8, 14], [3, 9, 15], [4, 10, 5]].freeze

Class Method Summary collapse

Class Method Details

.digest(password, salt, magic) ⇒ String

Parameters:

  • password (String)
  • salt (String)
  • magic (String)

Returns:

  • (String)


35
36
37
38
39
40
41
# File 'lib/capsium/reactor/htpasswd.rb', line 35

def self.digest(password, salt, magic)
  final = initial_digest(password, salt, magic)
  1000.times do |i|
    final = stretch_digest(i, password, salt, final)
  end
  "#{magic}#{salt}$#{to64(final)}"
end

.initial_digest(password, salt, magic) ⇒ String

Parameters:

  • password (String)
  • salt (String)
  • magic (String)

Returns:

  • (String)


43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/capsium/reactor/htpasswd.rb', line 43

def self.initial_digest(password, salt, magic)
  inner = Digest::MD5.digest(password + salt + password)
  context = Digest::MD5.new
  context << password << magic << salt
  length = password.length
  while length.positive?
    context << inner[0, [16, length].min]
    length -= 16
  end
  length = password.length
  while length.positive?
    context << (length.odd? ? "\0" : password[0])
    length >>= 1
  end
  context.digest
end

.stretch_digest(iteration, password, salt, final) ⇒ String

Parameters:

  • iteration (Integer)
  • password (String)
  • salt (String)
  • final (String)

Returns:

  • (String)


60
61
62
63
64
65
66
67
# File 'lib/capsium/reactor/htpasswd.rb', line 60

def self.stretch_digest(iteration, password, salt, final)
  context = Digest::MD5.new
  context << (iteration.odd? ? password : final)
  context << salt unless (iteration % 3).zero?
  context << password unless (iteration % 7).zero?
  context << (iteration.odd? ? final : password)
  context.digest
end

.to64(final) ⇒ String

The custom base-64 of md5-crypt: digest triples permuted, first index to the high bits, 22 characters total.

Parameters:

  • final (String)

Returns:

  • (String)


71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/capsium/reactor/htpasswd.rb', line 71

def self.to64(final)
  encoded = +""
  DIGEST_GROUPS.each do |a, b, c|
    value = final[c].ord | (final[b].ord << 8) | (final[a].ord << 16)
    4.times do
      encoded << ITOA64[value & 0x3f]
      value >>= 6
    end
  end
  value = final[11].ord
  2.times do
    encoded << ITOA64[value & 0x3f]
    value >>= 6
  end
  encoded
end

.verify(hash, password) ⇒ Boolean

Parameters:

  • hash (String)
  • password (String)

Returns:

  • (Boolean)


28
29
30
31
32
33
# File 'lib/capsium/reactor/htpasswd.rb', line 28

def self.verify(hash, password)
  _prefix, magic, salt, = hash.split("$", 4)
  return false if salt.nil? || salt.empty?

  Reactor.secure_compare(digest(password, salt, "$#{magic}$"), hash)
end