class RbPass::PasswordHasher

Public Class Methods

new(iteration_count_log2, portable_hashes) click to toggle source
# File lib/rbpass/password_hasher.rb, line 5
def initialize(iteration_count_log2, portable_hashes)
  @itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
  @iteration_count_log2 = iteration_count_log2.between?(4, 31) ? iteration_count_log2 : 8
  @portable_hashes = portable_hashes
  @random_state = Time.now.usec.to_s + Process.pid.to_s
end

Public Instance Methods

check(password, stored_hash) click to toggle source
# File lib/rbpass/password_hasher.rb, line 171
def check(password, stored_hash)
  hash = crypt_private(password, stored_hash)
  
  if hash[0] == '*'
    hash = password.crypt(stored_hash)
  end

  hash == stored_hash
end
crypt_private(password, setting) click to toggle source
# File lib/rbpass/password_hasher.rb, line 83
def crypt_private(password, setting)
  output = '*0'

  if setting[0,2] == output
    output = '*1'
  end

  return output if setting[0,3] != '$P$' and setting[0,3] != '$H$'

  count_log2 = @itoa64.index(setting[3])
  return output if !count_log2.between?(7, 30)

  count = 1 << count_log2

  salt = setting[4,8]
  return output if salt.length != 8

  hash = Digest::MD5.digest(salt + password)

  while count > 0 do
    hash = Digest::MD5.digest(hash + password)
    count -= 1
  end

  output = setting[0, 12]

  output << encode64(hash, 16)

  output
end
encode64(input, count) click to toggle source
# File lib/rbpass/password_hasher.rb, line 35
def encode64(input, count)
  output = ''
  i = 0

  while i < count
    value = (input[i]).ord
    i += 1
    output << @itoa64[value & 0x3f]

    if i < count
      value |= (input[i]).ord << 8
    end

    output << @itoa64[(value >> 6) & 0x3f]

    if i >= count
      break
    end

    i += 1

    if i < count 
      value |= (input[i]).ord << 16
    end

    output << @itoa64[(value >> 12) & 0x3f]

    if i >= count
      break
    end

    i += 1

    output << @itoa64[(value >> 18) & 0x3f]
  end

  output
end
gensalt_blowfish(input) click to toggle source
# File lib/rbpass/password_hasher.rb, line 114
def gensalt_blowfish(input)
  itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
  output = '$2a$'

  output << ('0'.ord + @iteration_count_log2 / 10).chr
  output << ('0'.ord + @iteration_count_log2 % 10).chr
  output << '$'

  i = 0

  while true
      c1 = (input[i]).ord
      i += 1
      output << itoa64[c1 >> 2]
      c1 = (c1 & 0x03) << 4

      if i >= 16
        output << itoa64[c1]
        break
      end

      c2 = (input[i]).ord
      i += 1
      c1 |= c2 >> 4
      output << itoa64[c1]
      c1 = (c2 & 0x0f) << 2

      c2 = (input[i]).ord
      i += 1
      c1 |= c2 >> 6
      output << itoa64[c1]
      output << itoa64[c2 & 0x3f]
  end

  output
end
gensalt_private(input) click to toggle source
# File lib/rbpass/password_hasher.rb, line 74
def gensalt_private(input)
  output = '$P$'
  
  output << @itoa64[[@iteration_count_log2 + 5, 30].min]
  output << encode64(input, 6)
  
  output
end
get_random_bytes(count) click to toggle source
# File lib/rbpass/password_hasher.rb, line 12
def get_random_bytes(count)
  output = ''
  
  if File.readable?('/dev/urandom')
    output = File.read('/dev/urandom', count)
  end

  if output.length < count
    output = ''
    i = 0

    while i < count do
      @random_state = Digest::MD5.hexdigest(Time.now.usec.to_s + @random_state)
      output << Digest::MD5.hexdigest(@random_state).split(//).pack('H*')
      i += 16
    end

    output = output[0..count]
  end

  output
end
hash(password) click to toggle source
# File lib/rbpass/password_hasher.rb, line 151
def hash(password)
  random = ''

  if !@portable_hashes
    random = get_random_bytes(16)
    hash = password.crypt(gensalt_blowfish(random))
    return hash if hash.length == 60
  end

  if random.length < 6
    random = get_random_bytes(6)
  end

  hash = crypt_private(password, gensalt_private(random))

  return hash if hash.length == 34
  
  '*'
end