class ROTP::OTP

def byte_secret

def byte_secret
  Base32.decode(@secret)
end

def generate_otp(input)

Parameters:
  • input (Integer) -- the number used seed the HMAC
def generate_otp(input)
  hmac = OpenSSL::HMAC.digest(
    OpenSSL::Digest.new(digest),
    byte_secret,
    int_to_bytestring(input)
  )
  offset = hmac[-1].ord & 0xf
  code = (hmac[offset].ord & 0x7f) << 24 |
         (hmac[offset + 1].ord & 0xff) << 16 |
         (hmac[offset + 2].ord & 0xff) << 8 |
         (hmac[offset + 3].ord & 0xff)
  code_str = (10 ** digits + (code % 10 ** digits)).to_s
  code_str[-digits..-1]
end

def initialize(s, options = {})

Options Hash: (**options)
  • provisioning_params (Hash) --
  • issuer (String) --
  • name (String) --
  • digest (String) --
  • digits (Integer) --

Parameters:
  • secret (String) -- in the form of base32
def initialize(s, options = {})
  @digits = options[:digits] || DEFAULT_DIGITS
  @digest = options[:digest] || 'sha1'
  @name = options[:name]
  @issuer = options[:issuer]
  @provisioning_params = options[:provisioning_params] || {}
  @secret = s
end

def int_to_bytestring(int, padding = 8)


along with the secret
bytestring, which is fed to the HMAC
Turns an integer to the OATH specified
def int_to_bytestring(int, padding = 8)
  unless int >= 0
    raise ArgumentError, '#int_to_bytestring requires a positive number'
  end
  result = []
  until int == 0
    result << (int & 0xFF).chr
    int >>= 8
  end
  result.reverse.join.rjust(padding, 0.chr)
end

def time_constant_compare(a, b)

constant-time compare the strings
def time_constant_compare(a, b)
  return false if a.empty? || b.empty? || a.bytesize != b.bytesize
  l = a.unpack "C#{a.bytesize}"
  res = 0
  b.each_byte { |byte| res |= byte ^ l.shift }
  res == 0
end

def verify(input, generated)

def verify(input, generated)
  raise ArgumentError, '`otp` should be a String' unless
      input.is_a?(String)
  time_constant_compare(input, generated)
end