class ROTP::OTP
def byte_secret
def byte_secret Base32.decode(@secret) end
def generate_otp(input)
-
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)
-
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)
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