class HashMan::Hasher

Constants

AlphabetError
DEFAULT_ALPHABET
DEFAULT_SEPS
GUARD_DIV
InputError
MIN_ALPHABET_LENGTH

VERSION = “1.0.2”

MinLengthError
SEP_DIV
SaltError

Attributes

alphabet[R]
guards[R]
min_hash_length[R]
salt[R]
seps[R]

Public Class Methods

new(salt = "", min_hash_length = 0, alphabet = DEFAULT_ALPHABET) click to toggle source
# File lib/hashman/hasher.rb, line 17
def initialize(salt = "", min_hash_length = 0, alphabet = DEFAULT_ALPHABET)
  @salt             = salt
  @min_hash_length  = min_hash_length
  @alphabet         = alphabet

  setup_alphabet
end

Public Instance Methods

decode(hash) click to toggle source
# File lib/hashman/hasher.rb, line 45
def decode(hash)
  return [] if hash.nil? || hash.empty?

  internal_decode(hash, @alphabet)
end
decode_hex(hash) click to toggle source
# File lib/hashman/hasher.rb, line 51
def decode_hex(hash)
  ret = ""
  numbers = decode(hash)

  numbers.length.times do |i|
    ret += numbers[i].to_s(16)[1 .. -1]
  end

  ret.upcase
end
encode(*numbers) click to toggle source
# File lib/hashman/hasher.rb, line 25
def encode(*numbers)
  numbers.flatten! if numbers.length == 1

  if numbers.empty? || numbers.reject { |n| Integer(n) && n >= 0 }.any?
    ""
  else
    internal_encode(numbers)
  end
end
encode_hex(str) click to toggle source
# File lib/hashman/hasher.rb, line 35
def encode_hex(str)
  return "" unless hex_string?(str)

  numbers = str.scan(/[\w\W]{1,12}/).map do |num|
    "1#{num}".to_i(16)
  end

  encode(numbers)
end

Protected Instance Methods

consistent_shuffle(alphabet, salt) click to toggle source
# File lib/hashman/hasher.rb, line 142
def consistent_shuffle(alphabet, salt)
  return alphabet if salt.nil? || salt.empty?

  v = 0
  p = 0

  (alphabet.length-1).downto(1) do |i|
    v  = v % salt.length
    p += n = salt[v].ord
    j  = (n + v + p) % i

    tmp_char = alphabet[j]

    alphabet = alphabet[0, j] + alphabet[i] + alphabet[j + 1..-1]
    alphabet = alphabet[0, i] + tmp_char    + alphabet[i + 1..-1]

    v += 1
  end

  alphabet
end
hash(input, alphabet) click to toggle source
# File lib/hashman/hasher.rb, line 164
def hash(input, alphabet)
  num = input.to_i
  len = alphabet.length
  res   = ""

  begin
    res = "#{alphabet[num % len]}#{res}"
    num = num.div(alphabet.length)
  end while num > 0

  res
end
internal_decode(hash, alphabet) click to toggle source
# File lib/hashman/hasher.rb, line 113
def internal_decode(hash, alphabet)
  ret = []

  breakdown = hash.gsub(/[#{@guards}]/, " ")
  array     = breakdown.split(" ")

  i = [3,2].include?(array.length) ? 1 : 0

  if breakdown = array[i]
    lottery   = breakdown[0]
    breakdown = breakdown[1 .. -1].gsub(/[#{@seps}]/, " ")
    array     = breakdown.split(" ")

    array.length.times do |i|
      sub_hash = array[i]
      buffer   = lottery + salt + alphabet
      alphabet = consistent_shuffle(alphabet, buffer[0, alphabet.length])

      ret.push unhash(sub_hash, alphabet)
    end

    if encode(ret) != hash
      ret = []
    end
  end

  ret
end
internal_encode(numbers) click to toggle source
# File lib/hashman/hasher.rb, line 64
def internal_encode(numbers)
  ret = ""

  alphabet = @alphabet
  length   = numbers.length
  hash_int = 0

  length.times do |i|
    hash_int += (numbers[i] % (i + 100))
  end

  lottery = ret = alphabet[hash_int % alphabet.length]

  length.times do |i|
    num = numbers[i]
    buf = lottery + salt + alphabet

    alphabet = consistent_shuffle(alphabet, buf[0, alphabet.length])
    last     = hash(num, alphabet)

    ret += last

    if (i + 1) < length
      num %= (last.ord + i)
      ret += seps[num % seps.length]
    end
  end

  if ret.length < min_hash_length
    ret = guards[(hash_int + ret[0].ord) % guards.length] + ret

    if ret.length < min_hash_length
      ret += guards[(hash_int + ret[2].ord) % guards.length]
    end
  end

  half_length = alphabet.length.div(2)

  while(ret.length < min_hash_length)
    alphabet = consistent_shuffle(alphabet, alphabet)
    ret = alphabet[half_length .. -1] + ret + alphabet[0, half_length]

    excess = ret.length - min_hash_length
    ret = ret[excess / 2, min_hash_length] if excess > 0
  end

  ret
end
unhash(input, alphabet) click to toggle source
# File lib/hashman/hasher.rb, line 177
def unhash(input, alphabet)
  num = 0

  input.length.times do |i|
    pos = alphabet.index(input[i])

    raise InputError, "unable to unhash" unless pos

    num += pos * alphabet.length ** (input.length - i - 1)
  end

  num
end

Private Instance Methods

hex_string?(string) click to toggle source
# File lib/hashman/hasher.rb, line 285
def hex_string?(string)
  string.to_s.match(/\A[0-9a-fA-F]+\Z/)
end
pick_characters(array, index) click to toggle source
# File lib/hashman/hasher.rb, line 289
def pick_characters(array, index)
  array[0, index] + " " + array[index + 1 .. -1]
end
setup_alphabet() click to toggle source
# File lib/hashman/hasher.rb, line 193
def setup_alphabet
  validate_attributes

  @alphabet = uniq_characters(alphabet)

  validate_alphabet

  setup_seps
  setup_guards
end
setup_guards() click to toggle source
# File lib/hashman/hasher.rb, line 239
def setup_guards
  gc = (alphabet.length / GUARD_DIV).ceil

  if alphabet.length < 3
    @guards = seps[0, gc]
    @seps   = seps[gc .. -1]
  else
    @guards   = alphabet[0, gc]
    @alphabet = alphabet[gc .. -1]
  end
end
setup_seps() click to toggle source
# File lib/hashman/hasher.rb, line 204
def setup_seps
  @seps = DEFAULT_SEPS

  seps.length.times do |i|
    # Seps should only contain characters present in alphabet,
    # and alphabet should not contains seps
    if j = alphabet.index(seps[i])
      @alphabet = pick_characters(alphabet, j)
    else
      @seps = pick_characters(seps, i)
    end
  end

  alphabet.delete!(' ')
  seps.delete!(' ')

  @seps = consistent_shuffle(seps, salt)

  if seps.length == 0 || (alphabet.length / seps.length.to_f) > SEP_DIV
    seps_length = (alphabet.length / SEP_DIV).ceil
    seps_length = 2 if seps_length == 1

    if seps_length > seps.length
      diff = seps_length - seps.length;

      @seps    += alphabet[0, diff]
      @alphabet = alphabet[diff .. -1]
    else
      @seps = seps[0, seps_length]
    end
  end

  @alphabet = consistent_shuffle(alphabet, salt)
end
uniq_characters(string) click to toggle source
# File lib/hashman/hasher.rb, line 293
def uniq_characters(string)
  string.split('').uniq.join('')
end
validate_alphabet() click to toggle source
# File lib/hashman/hasher.rb, line 278
def validate_alphabet
  unless alphabet.length >= MIN_ALPHABET_LENGTH
    raise AlphabetError, "Alphabet must contain at least " +
        "#{MIN_ALPHABET_LENGTH} unique characters."
  end
end
validate_attributes() click to toggle source
# File lib/hashman/hasher.rb, line 256
def validate_attributes
  unless salt.kind_of?(String)
    raise SaltError, "The salt must be a String"
  end

  unless min_hash_length.kind_of?(Fixnum)
    raise MinLengthError, "The min length must be a Fixnum"
  end

  unless min_hash_length >= 0
    raise MinLengthError, "The min length must be 0 or more"
  end

  unless alphabet.kind_of?(String)
    raise AlphabetError, "The alphabet must be a String"
  end

  if alphabet.include?(' ')
    raise AlphabetError, "The alphabet can’t include spaces"
  end
end