class ROTP::OTP

Constants

DEFAULT_DIGITS

Attributes

digest[R]
digits[R]
issuer[R]
name[R]
provisioning_params[R]
secret[R]

Public Class Methods

new(s, options = {}) click to toggle source

@param [String] secret in the form of base32 @option options digits [Integer] (6)

Number of integers in the OTP.
Google Authenticate only supports 6 currently

@option options digest [String] (sha1)

Digest used in the HMAC.
Google Authenticate only supports 'sha1' currently

@option options name [String]

The name of the account for the OTP.
Used in the provisioning URL

@option options issuer [String]

The issuer of the OTP.
Used in the provisioning URL

@option options provisioning_params [Hash] ({})

Additional non-standard params you may want appended to the
provisioning URI. Ex. `image: 'https://example.com/icon.png'`

@returns [OTP] OTP instantiation

# File lib/rotp/otp.rb, line 23
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

Public Instance Methods

generate_otp(input) click to toggle source

@param [Integer] input the number used seed the HMAC Usually either the counter, or the computed integer based on the Unix timestamp

# File lib/rotp/otp.rb, line 35
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

Private Instance Methods

byte_secret() click to toggle source
# File lib/rotp/otp.rb, line 60
def byte_secret
  Base32.decode(@secret)
end
int_to_bytestring(int, padding = 8) click to toggle source

Turns an integer to the OATH specified bytestring, which is fed to the HMAC along with the secret

# File lib/rotp/otp.rb, line 68
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
time_constant_compare(a, b) click to toggle source

constant-time compare the strings

# File lib/rotp/otp.rb, line 82
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
verify(input, generated) click to toggle source
# File lib/rotp/otp.rb, line 53
def verify(input, generated)
  raise ArgumentError, '`otp` should be a String' unless
      input.is_a?(String)

  time_constant_compare(input, generated)
end