Giter Site home page Giter Site logo

rotp's Introduction

Webauthn and the future of 2FA

Although this library will continue to be maintained, if you're implementing a 2FA solution today, you should take a look at Webauthn. It doesn't involve shared secrets and it's supported by most modern browsers and operating systems.

Ruby resources for Webauthn


The Ruby One Time Password Library

Build Status Gem Version Documentation License

A ruby library for generating and validating one time passwords (HOTP & TOTP) according to RFC 4226 and RFC 6238.

ROTP is compatible with Google Authenticator available for Android and iPhone and any other TOTP based implementations.

Many websites use this for multi-factor authentication, such as GMail, Facebook, Amazon EC2, WordPress, and Salesforce. You can find a more complete list here.

Dependencies

  • OpenSSL
  • Ruby 2.3 or higher

Breaking changes

Breaking changes in >= 6.0

  • Dropping support for Ruby <2.3

Breaking changes in >= 5.0

  • ROTP::Base32.random_base32 is now ROTP::Base32.random and the argument has changed from secret string length to byte length to allow for more precision. There is an alias to allow for random_base32 for the time being.
  • Cleaned up the Base32 implementation to match Google Authenticator's version.

Breaking changes in >= 4.0

  • Simplified API
    • verify now takes options for drift and after,padding is no longer an option
    • verify returns a timestamp if true, nil if false
  • Dropping support for Ruby < 2.0
  • Docs for 3.x can be found here

Installation

gem install rotp

Library Usage

Time based OTP's

totp = ROTP::TOTP.new("base32secret3232", issuer: "My Service")
totp.now # => "492039"

# OTP verified for current time - returns timestamp of the current interval
# period.
totp.verify("492039") # => 1474590700

sleep 30

# OTP fails to verify - returns nil
totp.verify("492039") # => nil

Counter based OTP's

hotp = ROTP::HOTP.new("base32secretkey3232")
hotp.at(0) # => "786922"
hotp.at(1) # => "595254"
hotp.at(1401) # => "259769"

# OTP verified with a counter
hotp.verify("259769", 1401) # => 1401
hotp.verify("259769", 1402) # => nil

Preventing reuse of Time based OTP's

By keeping track of the last time a user's OTP was verified, we can prevent token reuse during the interval window (default 30 seconds)

The following is an example of this in action:

user = User.find(someUserID)
totp = ROTP::TOTP.new(user.otp_secret)
totp.now # => "492039"

# Let's take a look at the last time the user authenticated with an OTP
user.last_otp_at # => 1432703530

# Verify the OTP
last_otp_at = totp.verify("492039", after: user.last_otp_at) #=> 1472145760
# ROTP returns the timestamp(int) of the current period

# Store this on the user's account
user.update(last_otp_at: last_otp_at)

# Someone attempts to reuse the OTP inside the 30s window
last_otp_at = totp.verify("492039", after: user.last_otp_at) #=> nil
# It fails to verify because we are still in the same 30s interval window

Verifying a Time based OTP with drift

Some users may enter a code just after it has expired. By adding 'drift' you can allow for a recently expired token to remain valid.

totp = ROTP::TOTP.new("base32secret3232")
now = Time.at(1474590600) #2016-09-23 00:30:00 UTC
totp.at(now) # => "250939"

# OTP verified for current time along with 15 seconds earlier
# ie. User enters a code just after it expired
totp.verify("250939", drift_behind: 15, at: now + 35) # => 1474590600
# User waits too long. Fails to validate previous OTP
totp.verify("250939", drift_behind: 15, at: now + 45) # => nil

Generating a Base32 Secret key

ROTP::Base32.random  # returns a 160 bit (32 character) base32 secret. Compatible with Google Authenticator

Note: The Base32 format conforms to RFC 4648 Base32

Generating QR Codes for provisioning mobile apps

Provisioning URI's generated by ROTP are compatible with most One Time Password applications, including Google Authenticator.

totp = ROTP::TOTP.new("base32secret3232", issuer: "My Service")
totp.provisioning_uri("[email protected]") # => 'otpauth://totp/My%20Service:alice%40google.com?secret=base32secret3232&issuer=My%20Service'

hotp = ROTP::HOTP.new("base32secret3232", issuer: "My Service")
hotp.provisioning_uri("[email protected]", 0) # => 'otpauth://hotp/My%20Service:alice%40google.com?secret=base32secret3232&issuer=My%20Service&counter=0'

This can then be rendered as a QR Code which the user can scan using their mobile phone and the appropriate application.

Working example

Scan the following barcode with your phone, using Google Authenticator

QR Code for OTP

Now run the following and compare the output

require 'rubygems'
require 'rotp'
totp = ROTP::TOTP.new("JBSWY3DPEHPK3PXP")
p "Current OTP: #{totp.now}"

Testing

bundle install
bundle exec rspec

Testing with Docker

In order to make it easier to test against different ruby version, ROTP comes with a set of Dockerfiles for each version that we test against in Travis

docker build -f Dockerfile-2.6 -t rotp_2.6 .
docker run --rm -v $(pwd):/usr/src/app rotp_2.6

Alternately, you may use docker-compose to run all the tests:

docker-compose up

Executable Usage

The rotp rubygem includes CLI version to help with testing and debugging

# Try this to get an overview of the commands
rotp --help

# Examples
rotp --secret p4ssword                       # Generates a time-based one-time password
rotp --hmac --secret p4ssword --counter 42   # Generates a counter-based one-time password

Contributors

Have a look at the contributors graph on Github.

License

MIT Copyright (C) 2019 by Mark Percival, see LICENSE for details.

Other implementations

A list can be found at Wikipedia.

rotp's People

Contributors

amandameng avatar andrehjr avatar asio avatar asmod4n avatar atcruice avatar btalbot avatar dependabot[bot] avatar developius avatar douwem avatar dvrensk avatar github-actions[bot] avatar gogainda avatar halo avatar ipoval avatar isabanin avatar jeremyevans avatar johnnyshields avatar ksuh90 avatar mdp avatar mkdynamic avatar ohbarye avatar olleolleolle avatar petergoldstein avatar pocke avatar sbc100 avatar shaiguitar avatar simi avatar smridge avatar ssinghi avatar tristanmorgan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rotp's Issues

TOTP verification passes/fails randomly

Hi!

I have some issues where the verification seems to pass randomly when it should fail.

Here's a minimal script to show this issue:

# frozen_string_literal: true

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  gem "rotp"
  gem "pry"
end

class Foobar
  DRIFT = 60
  INTERVAL = 60

  attr_accessor(:key, :proof)

  def initialize
    @key = ROTP::Base32.random_base32
  end

  def generate_proof(time)
    @proof = totp_generator.at(time)
  end

  def verify_proof(time)
    !totp_generator.verify(proof, drift_behind: DRIFT, drift_ahead: DRIFT, at: time).nil?
  end

  def totp_generator
    @totp_generator ||= ROTP::TOTP.new(
      key,
      interval: INTERVAL,
      digits: 3,
      digest: "SHA256"
    )
  end
end

drift = 60 # seconds
interval = 60 # seconds
1000.times do
  # Align time at start of interval
  time = Time.at((Time.now.to_f / interval).floor * interval)
  foobar = Foobar.new

  foobar.generate_proof(time)

  raise "SHOULD BE FALSE" if foobar.verify_proof(time - drift - 2)
  raise "SHOULD BE TRUE" unless foobar.verify_proof(time - drift)
  raise "SHOULD BE TRUE" unless foobar.verify_proof(time)
  raise "SHOULD BE TRUE" unless foobar.verify_proof(time + interval + drift - 1)
  raise "SHOULD BE FALSE" if foobar.verify_proof(time + interval + drift)

  puts("All good")
end

Now, it most often fails on the last raise here. I first thought it might be some kind of small timing issue so I added even more time to it to make sure it fails, like this:

foobar.verify_proof(time + interval + drift + 1000)

It still passes sometimes, which seems very odd to me!

Is there a bug in here or am I doing something wrong? 🤔

Base32 decode

Why doesn't the Base32 decode function handle padding?

This doesn't work:

2.2.0 :012 > totp = ROTP::TOTP.new Base32.encode("dntqoaxas7rvtfuf")
 => #<ROTP::TOTP:0x007fdd4d062458 @interval=30, @issuer=nil, @digits=6, @digest="sha1", @secret="MRXHI4LPMF4GC4ZXOJ3HIZTVMY======">
2.2.0 :013 > totp.now
ROTP::Base32::Base32Error: Invalid Base32 Character - '='
    from /Users/bjohnson/.rvm/gems/ruby-2.2.0/gems/rotp-2.1.0/lib/rotp/base32.rb:43:in `decode_quint'
    from /Users/bjohnson/.rvm/gems/ruby-2.2.0/gems/rotp-2.1.0/lib/rotp/base32.rb:28:in `block in decode_block'
    from /Users/bjohnson/.rvm/gems/ruby-2.2.0/gems/rotp-2.1.0/lib/rotp/base32.rb:28:in `each_char'
    from /Users/bjohnson/.rvm/gems/ruby-2.2.0/gems/rotp-2.1.0/lib/rotp/base32.rb:28:in `each'
    from /Users/bjohnson/.rvm/gems/ruby-2.2.0/gems/rotp-2.1.0/lib/rotp/base32.rb:28:in `map'
    from /Users/bjohnson/.rvm/gems/ruby-2.2.0/gems/rotp-2.1.0/lib/rotp/base32.rb:28:in `decode_block'
    from /Users/bjohnson/.rvm/gems/ruby-2.2.0/gems/rotp-2.1.0/lib/rotp/base32.rb:10:in `block in decode'
    from /Users/bjohnson/.rvm/gems/ruby-2.2.0/gems/rotp-2.1.0/lib/rotp/base32.rb:9:in `each'
    from /Users/bjohnson/.rvm/gems/ruby-2.2.0/gems/rotp-2.1.0/lib/rotp/base32.rb:9:in `decode'
    from /Users/bjohnson/.rvm/gems/ruby-2.2.0/gems/rotp-2.1.0/lib/rotp/otp.rb:52:in `byte_secret'
    from /Users/bjohnson/.rvm/gems/ruby-2.2.0/gems/rotp-2.1.0/lib/rotp/otp.rb:26:in `generate_otp'
    from /Users/bjohnson/.rvm/gems/ruby-2.2.0/gems/rotp-2.1.0/lib/rotp/totp.rb:29:in `now'

But this works?

2.2.0 :014 > totp = ROTP::TOTP.new Base32.encode("dntqoaxas7rvtfuf").gsub(/=/, '')
 => #<ROTP::TOTP:0x007fdd4d045268 @interval=30, @issuer=nil, @digits=6, @digest="sha1", @secret="MRXHI4LPMF4GC4ZXOJ3HIZTVMY">
2.2.0 :015 > totp.now
 => "907944"

P.S.

Why is the Base32 decode hand rolled? Why not use the standard library?

Thanks!

No option to set drift/at in HOTP

I am using HOTP for the counter based verification and I want to use drift_behind, drfit_ahead and at to verify HOTP and it looks like there is no option to set those.

Is there any reason for not including those in HOTP? Is OTP generated through HOTP never expires?

Cannot verify OTP in a separate request

Hey Guys,

I am generating an OTP in one API call and verifying it in a separate call, but this doesn't seem to work properly. I have tried this by passing correct as well as wrong values but result is same. Maybe I am missing something or this shouldn't be used this way.

# 1st API call
otp = ROTP::TOTP.new('base32secret3232', issuer: 'MyApp', interval: 180)

Now we will use this OTP in a separate call to verify it.

# 2nd API call
otp = ROTP::TOTP.new('base32secret3232')
verified_otp_at = otp.verify(params[:otp]) # this produces nil

Version changes breaking GoogleAuthenticatorRails

Hey there,

I currently have ROTP 1.4.1 locked in the Gemspec for jaredonline/google-authenticator because bumping above that breaks my specs. I'm assuming it has to do with the Base64 encoding changes made in 1.4.5/1.4.6.

I figure I'll make the appropriate changes in my app, but wanted to know if this will cause backward incompatibility for users of the GoogleAuthenticatorRails gem.

Any insight would be helpful!

Specific Length of OTP Token

Hello,

I am in a situation that requirements are specifically asked for 4-digit OTP. Is there any possibility to generate 4-digit or any specific length OTP?

Your guidance will be much appreciated.

wrong code - oauthtool vs mdp/rotp

I recently had to add a new code. This one was much longer than the others. I found that mdp/rotp was giving a different code than Google Authenticator. I tested with the CLI and found that it was being handled correctly via oathtool but not via rotp:

This is correct:

oathtool --totp -v -b IFADSABWAA2AAMAAHFAFCABRAAWQAQIAIFADOABXAAWQANAAG4ADOACDAAWQAQQAIYAFIACFAAWQARIAGQAFFABVAA3AANIAHAAFIABYAAYAAQYAIMAA

Hex secret: 414039003600340030003940510031002d0041004140370037002d0034003700370043002d0042004600540045002d00450034005280350036003500380054003800300043004300
Base32 secret: IFADSABWAA2AAMAAHFAFCABRAAWQAQIAIFADOABXAAWQANAAG4ADOACDAAWQAQQAIYAFIACFAAWQARIAGQAFFABVAA3AANIAHAAFIABYAAYAAQYAIMAA====
Digits: 6
Window size: 0
Step size (seconds): 30
Start time: 1970-01-01 00:00:00 UTC (0)
Current time: 2019-05-09 15:47:06 UTC (1557416826)
Counter: 0x31824A6 (51913894)

861917

Run at the same time, this produces a different code:

puts ROTP::TOTP.new('IFADSABWAA2AAMAAHFAFCABRAAWQAQIAIFADOABXAAWQANAAG4ADOACDAAWQAQQAIYAFIACFAAWQARIAGQAFFABVAA3AANIAHAAFIABYAAYAAQYAIMAA').now
# 413689

I'm using the latest version of mdp/rotp and ruby 2.4.1. What am I doing wrong?

algorithm (digest) is missing from provisioning_uri

The key uri format documents indicate that the algorithm should be populated especially if it's not 'SHA1'. The algorithm is currently never populated.

https://github.com/google/google-authenticator/wiki/Key-Uri-Format

When using google authenticator which only supports SHA1 this is not an issue, but for users not using google authenticator, the missing algorithm makes it harder to share the secret.

The fix is a one-liner in the #provisioning_uri method (https://github.com/mdp/rotp/blob/master/lib/rotp/totp.rb#L85) when building the params hash.

params {
...
algorithm: digest.upcase == 'SHA1' ? nil : digest.upcase,
...
}

Let me know if you need a PR for that.

Number of digits are not consistent

Hi I am using "active_model_otp" gem and that uses internally "rotp" gem. As per the active_model_otp gem default code it should be generating 6 digit code. While I am doing extensive testing, I that found many times 3 or 4 digit code is getting generated.
And then it fails in VERIFY section. It is causing alot of trouble. Infact the developer of active_model_otp has also implemented PADDING by default but still it is not doing any padding and I am still getting 3 digit code.

For reference here is the link where I have posted question on active_model_otp
heapsource/active_model_otp#12

https://github.com/heapsource/active_model_otp/blob/4ec7c5686211be6cf9a08dc022c69ca7eddf077a/lib/active_model/one_time_password.rb#L24

https://github.com/heapsource/active_model_otp/blob/4ec7c5686211be6cf9a08dc022c69ca7eddf077a/lib/active_model/one_time_password.rb#L41

Thanks
Shivani

Clarifications regarding RFCs

The README references "HOTP" and links to the draft which became "TOTP RFC 6238".

Can you clarify which time-based RFC is implemented? Does the README require updating?

Warning after upgrading to Ruby version 2.7.2

After upgrading to Ruby version 2.7.2 I get the following notice when running rails test

/Users/fydelio/.rvm/gems/ruby-2.7.2/gems/rotp-2.1.2/lib/rotp/totp.rb:56: warning: URI.escape is obsolete

Bildschirmfoto 2020-12-15 um 14 11 07

ROTP 4.0 Proposal

The API has gotten a bit verbose with non-breaking changes. For 4.0 I'd like to simplify it a bit more.

totp = ROTP::TOTP.new 'wrn3pqx5uqxqvnqr'
totp.verify(code, {drift: 30, prior: 0, at: Time.now }) #=> timestamp

Thoughts and ideas?

How to regenerate token if verify_with_drift_and_prior was verified?

Hello!

otp = ROTP::TOTP.new(secret).now
...
rotp = ROTP::TOTP.new(secret)
rotp.verify_with_drift_and_prior(otp, 30, last_verify_at) => true

When I logout and try to login within 30 seconds I get error because otp was verified already. And I have to wait 0-30 seconds to try with new otp.

Is it possible to do ROTP::TOTP.new(secret).now to generate new value that would be valid with rotp.verify_with_drift_and_prior(otp, 30, last_verify_at)?

Can't find "verify_with_drift_and_prior" in the codebase

I am trying to follow the installation instructions on verifying OTP tokens that have not been used already, using the suggested method totp.verify_with_drift_and_prior (as described in the README) but it doesn't work and I can't find the method in the gem's codebase.

Is this method in some new branch? I'm using v3.1.0 of the gem.

Cheers, Dan

Modulo Bias in Generated Secrets with `random_base32`

There is a slight bias in secrets generated using random_base32 due to the use of modulous. The bias is quite small and unlikely to be exploitable, but for piece of mind generated secrets should be as random as possible.

Additional Information

counter base demo fail in ruby 2.0 & 1.9

just show the code & error msg (mac osx 10.8 & rvm) & irb

require 'rotp'
hotp = ROTP::HOTP.new("base32secretkey3232")
hotp.at(0)

NoMethodError: undefined method `>>' for nil:NilClass
from /home/.rvm/gems/ruby-1.9.3-p392/gems/rotp-1.4.4/lib/rotp/base32.rb:31:in `decode_block'
from /home/.rvm/gems/ruby-1.9.3-p392/gems/rotp-1.4.4/lib/rotp/base32.rb:9:in `block in decode'
from /home/.rvm/gems/ruby-1.9.3-p392/gems/rotp-1.4.4/lib/rotp/base32.rb:8:in `each'
from /home/.rvm/gems/ruby-1.9.3-p392/gems/rotp-1.4.4/lib/rotp/base32.rb:8:in `decode'
from /home/.rvm/gems/ruby-1.9.3-p392/gems/rotp-1.4.4/lib/rotp/otp.rb:49:in `byte_secret'
from /home/.rvm/gems/ruby-1.9.3-p392/gems/rotp-1.4.4/lib/rotp/otp.rb:26:in `generate_otp'
from /home/.rvm/gems/ruby-1.9.3-p392/gems/rotp-1.4.4/lib/rotp/hotp.rb:8:in `at'
from (irb):3
from /home/.rvm/rubies/ruby-1.9.3-p392/bin/irb:16:in `<main>'

& it can be use base32 gem?

Proposal: create backup code function

Thanks

Thank you for user create marvelous gem.

Proposal

I think the backup code feature is useful, so suggest implementing the backup code function.

Counter based OTP's example don't work

I'm tried the "Counter based OTP's" example on Ruby 2.4.1 and got vastly different results. I just added a puts in front of each line, so that I can see the output:

require 'rubygems'
require 'rotp'

hotp = ROTP::HOTP.new("base32secretkey3232")
puts hotp.at(0) # => "260182"
puts hotp.at(1) # => "055283"
puts hotp.at(1401) # => "316439"

# OTP verified with a counter
puts hotp.verify("316439", 1401) # => true
puts hotp.verify("316439", 1402) # => false

Output:

786922
595254
259769
false
false

For me, I get "259769" for hotp.at(1401).
Maybe you want to update your example.

Tested this on:
Ruby version: ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-darwin16]
ropt: 3.3.0

How to generate manual key for Google Authenticator?

Google Authenticator provides the option to manually enter a 16-digit key as opposed to scanning the QR code. The "secret" field in the provisioning uri is much longer than 16 digits, so that's not it...I've spent hours Googling and pawing through source code and can't find an answer to this seemingly straightforward question - hoping you can be of help.

Proposal: Add retries option

It would be useful if there was a counter retry function built into the HOTP method, in which case the successful counter value would be returned like so:

# current
# OTP verified with a counter
hotp.verify(316439, 1401) # => true
hotp.verify(316439, 1402) # => false

# proposed
RETRIES = 20
hotp.verify(316439, 1390, RETRIES) # => 1401  (the counter value that succeeded)
hotp.verify(316439, 1380, RETRIES) # => false

Wrong number of arguments error

Hi Team,
we are facing issue while trying to call otp_code method.

$gem_library_root/vendor/bundle/ruby/2.3.0/gems/rotp-4.1.0/lib/rotp/totp.rb:17
$gem_library_root/vendor/bundle/ruby/2.3.0/gems/active_model_otp-1.2.0/lib/active_model/one_time_password.rb:77

as far as I dig into, I found this.
the issue is in arguments, it seems the latest rotp gem have updated below method to accept only 1 argument insted of 2 as compared to older version (3.3.1)
as per rotp-4.1.0
otp_code_rotp_gem_screenshot
`

   def at(time)
      generate_otp(timecode(time))
    end

`

as per rotp-3.3.1
`

  def at(time, padding=true)
      unless time.class == Time
           time = Time.at(time.to_i)
      end	  
      generate_otp(timecode(time), padding)
 end

`

since the optional parameter has been removed, the active_model_otp-1.20 gem is giving error at line number 77

otp_code_error_screenshot

I can make changes in the gem but not sure what else will break, active-model gem was last updated in 2015, and rotp updated few months back.
please suggest suitable method, if I revert back to previous gem version of rotp then what else would break.

kinldy help.

Missing ":" in Label in Provisioning URI

I'm looking at the examples here: https://daplie.github.io/browser-authenticator/
In concert with this spec from google: https://github.com/google/google-authenticator/wiki/Key-Uri-Format

Here is their example:

otpauth://totp/ACME%20Co:[email protected]?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30

It appears that their label is

The spec says that the label should be composed of

  issuer ":" name

The rotp implementation seems to omit the ":" in between the two values.
https://github.com/mdp/rotp/blob/master/lib/rotp/totp.rb#L67

    encode_params("otpauth://totp/#{issuer_string}#{URI.encode(name)}", params)

I'm more than happy to put in a PR to fix this, but wondering if others have seen this / decided not to fix it, etc. Is the correct approach to just put the ":" on the front of the name as you pass it into the provisioning_uri method?

TOTP wrong code

It seems like verify works wrong sometimes. I'm trying to verify code at the moment it was created:

totp = ROTP::TOTP.new(ROTP::Base32.random)

loop.with_index do |_, i|
  interval = totp.verify(totp.now)

  unless interval
    puts "break at #{i}"
    break
  end
end

# receive
break at 184357

While same code with interval: 1 and drift_behind: 30 works perfect even with verify at: (Time.now+29):

totp = ROTP::TOTP.new(ROTP::Base32.random, interval: 1)

loop.with_index do |_, i|
  interval = totp.verify(totp.now,
                         at: Time.now + 29,
                         drift_behind: 30 )

  unless interval
    puts "break at #{i}"
    break
  end
end
#no breaks for 1,5+ millions attempts

Maybe it is related to integer division at timecode method?

Please explain what happened to #random_base32

Hello,

It's helpful to see on your README that "ROTP::Base32.random_base32 is now ROTP::Base32.random and the argument has changed from secret string length to byte length to allow for more precision."

However, it is unlcear what is meant by "has changed from secret string length to byte length". Will you please explain?

Also, it looks like the rdoc for this class is still out of date.

Undefined method `&' for (486311991/10):Rational

Hey,

I'm using devise-two-factor in an application, which uses this.

When I try to log in, I get:

NoMethodError - undefined method `&' for (486311991/10):Rational:
  rotp (2.1.1) lib/rotp/otp.rb:62:in `int_to_bytestring'
  rotp (2.1.1) lib/rotp/otp.rb:27:in `generate_otp'
  rotp (2.1.1) lib/rotp/totp.rb:23:in `at'
  devise-two-factor (2.1.0) lib/devise_two_factor/models/two_factor_authenticatable.rb:38:in `current_otp'
  app/models/user.rb:15:in `send_two_factor_authentication_code'
  app/controllers/users/sessions_controller.rb:30:in `send_2fa_code'
  actionpack (4.2.5.2) lib/action_controller/metal/implicit_render.rb:4:in `send_action'
  actionpack (4.2.5.2) lib/abstract_controller/base.rb:198:in `process_action'
  actionpack (4.2.5.2) lib/action_controller/metal/rendering.rb:10:in `process_action'
  actionpack (4.2.5.2) lib/abstract_controller/callbacks.rb:20:in `block in process_action'
  activesupport (4.2.5.2) lib/active_support/callbacks.rb:117:in `call'
  activesupport (4.2.5.2) lib/active_support/callbacks.rb:555:in `block (2 levels) in compile'
  activesupport (4.2.5.2) lib/active_support/callbacks.rb:505:in `call'
  activesupport (4.2.5.2) lib/active_support/callbacks.rb:92:in `__run_callbacks__'
  activesupport (4.2.5.2) lib/active_support/callbacks.rb:778:in `_run_process_action_callbacks'
  activesupport (4.2.5.2) lib/active_support/callbacks.rb:81:in `run_callbacks'
  actionpack (4.2.5.2) lib/abstract_controller/callbacks.rb:19:in `process_action'
  actionpack (4.2.5.2) lib/action_controller/metal/rescue.rb:29:in `process_action'
  actionpack (4.2.5.2) lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'
  activesupport (4.2.5.2) lib/active_support/notifications.rb:164:in `block in instrument'
  activesupport (4.2.5.2) lib/active_support/notifications/instrumenter.rb:20:in `instrument'
  activesupport (4.2.5.2) lib/active_support/notifications.rb:164:in `instrument'
  actionpack (4.2.5.2) lib/action_controller/metal/instrumentation.rb:30:in `process_action'
  actionpack (4.2.5.2) lib/action_controller/metal/params_wrapper.rb:250:in `process_action'
  searchkick (1.2.1) lib/searchkick/logging.rb:153:in `process_action'
  activerecord (4.2.5.2) lib/active_record/railties/controller_runtime.rb:18:in `process_action'
  actionpack (4.2.5.2) lib/abstract_controller/base.rb:137:in `process'
  actionview (4.2.5.2) lib/action_view/rendering.rb:30:in `process'
  actionpack (4.2.5.2) lib/action_controller/metal.rb:196:in `dispatch'
  actionpack (4.2.5.2) lib/action_controller/metal/rack_delegation.rb:13:in `dispatch'
  actionpack (4.2.5.2) lib/action_controller/metal.rb:237:in `block in action'
  actionpack (4.2.5.2) lib/action_dispatch/routing/route_set.rb:74:in `dispatch'
  actionpack (4.2.5.2) lib/action_dispatch/routing/route_set.rb:43:in `serve'
  actionpack (4.2.5.2) lib/action_dispatch/routing/mapper.rb:49:in `serve'
  actionpack (4.2.5.2) lib/action_dispatch/journey/router.rb:43:in `block in serve'
  actionpack (4.2.5.2) lib/action_dispatch/journey/router.rb:30:in `serve'
  actionpack (4.2.5.2) lib/action_dispatch/routing/route_set.rb:815:in `call'
  warden (1.2.6) lib/warden/manager.rb:35:in `block in call'
  warden (1.2.6) lib/warden/manager.rb:34:in `call'
  rack (1.6.4) lib/rack/etag.rb:24:in `call'
  rack (1.6.4) lib/rack/conditionalget.rb:38:in `call'
  rack (1.6.4) lib/rack/head.rb:13:in `call'
  actionpack (4.2.5.2) lib/action_dispatch/middleware/params_parser.rb:27:in `call'
  actionpack (4.2.5.2) lib/action_dispatch/middleware/flash.rb:260:in `call'
  rack (1.6.4) lib/rack/session/abstract/id.rb:225:in `context'
  rack (1.6.4) lib/rack/session/abstract/id.rb:220:in `call'
  actionpack (4.2.5.2) lib/action_dispatch/middleware/cookies.rb:560:in `call'
  activerecord (4.2.5.2) lib/active_record/query_cache.rb:36:in `call'
  activerecord (4.2.5.2) lib/active_record/connection_adapters/abstract/connection_pool.rb:653:in `call'
  activerecord (4.2.5.2) lib/active_record/migration.rb:377:in `call'
  actionpack (4.2.5.2) lib/action_dispatch/middleware/callbacks.rb:29:in `block in call'
  activesupport (4.2.5.2) lib/active_support/callbacks.rb:88:in `__run_callbacks__'
  activesupport (4.2.5.2) lib/active_support/callbacks.rb:778:in `_run_call_callbacks'
  activesupport (4.2.5.2) lib/active_support/callbacks.rb:81:in `run_callbacks'
  actionpack (4.2.5.2) lib/action_dispatch/middleware/callbacks.rb:27:in `call'
  actionpack (4.2.5.2) lib/action_dispatch/middleware/reloader.rb:73:in `call'
  actionpack (4.2.5.2) lib/action_dispatch/middleware/remote_ip.rb:78:in `call'
  better_errors (2.1.1) lib/better_errors/middleware.rb:84:in `protected_app_call'
  better_errors (2.1.1) lib/better_errors/middleware.rb:79:in `better_errors_call'
  better_errors (2.1.1) lib/better_errors/middleware.rb:57:in `call'
  actionpack (4.2.5.2) lib/action_dispatch/middleware/debug_exceptions.rb:17:in `call'
  web-console (2.3.0) lib/web_console/middleware.rb:28:in `block in call'
  web-console (2.3.0) lib/web_console/middleware.rb:18:in `call'
  actionpack (4.2.5.2) lib/action_dispatch/middleware/show_exceptions.rb:30:in `call'
  railties (4.2.5.2) lib/rails/rack/logger.rb:38:in `call_app'
  railties (4.2.5.2) lib/rails/rack/logger.rb:20:in `block in call'
  activesupport (4.2.5.2) lib/active_support/tagged_logging.rb:68:in `block in tagged'
  activesupport (4.2.5.2) lib/active_support/tagged_logging.rb:26:in `tagged'
  activesupport (4.2.5.2) lib/active_support/tagged_logging.rb:68:in `tagged'
  railties (4.2.5.2) lib/rails/rack/logger.rb:20:in `call'
  actionpack (4.2.5.2) lib/action_dispatch/middleware/request_id.rb:21:in `call'
  rack (1.6.4) lib/rack/methodoverride.rb:22:in `call'
  rack (1.6.4) lib/rack/runtime.rb:18:in `call'
  activesupport (4.2.5.2) lib/active_support/cache/strategy/local_cache_middleware.rb:28:in `call'
  rack (1.6.4) lib/rack/lock.rb:17:in `call'
  actionpack (4.2.5.2) lib/action_dispatch/middleware/static.rb:116:in `call'
  rack (1.6.4) lib/rack/sendfile.rb:113:in `call'
  railties (4.2.5.2) lib/rails/engine.rb:518:in `call'
  railties (4.2.5.2) lib/rails/application.rb:165:in `call'
  rack (1.6.4) lib/rack/lock.rb:17:in `call'
  rack (1.6.4) lib/rack/content_length.rb:15:in `call'
  rack (1.6.4) lib/rack/handler/webrick.rb:88:in `service'
  /Users/chris/.rbenv/versions/2.2.4/lib/ruby/2.2.0/webrick/httpserver.rb:138:in `service'
  /Users/chris/.rbenv/versions/2.2.4/lib/ruby/2.2.0/webrick/httpserver.rb:94:in `run'
  /Users/chris/.rbenv/versions/2.2.4/lib/ruby/2.2.0/webrick/server.rb:294:in `block in start_thread'

Here's a more interactive look at the behavior:

screen shot 2016-03-25 at 3 02 40 pm

Now, I've noticed behavior like this from another gem I'm also using: gem 'ruby-units', '~> 2.0'

I'm posting here because it looks like just calling int.to_i would fix it and someone else might run into this. But also out of some curiosity: what default behavior is being messed with here?

Donation of Feitian hardware tokens c100/c200

Dear all,

This message is for ROTP developers.
GOOZE is an active contributor in free software.

To help ROTP, GOOZE would like to donate a set of hardware tokens compatible with HOTP and TOTP to your project:

Would you accept a donation of (free) tokens and where shall we ship them?
Please contact me on my private add: jmpoure ATATATAT gooze.eu

Kind regards,
Jean-Michel POURE

TOTP time_left

Is there some way to know how much time is left for the latest totp token to expire? Or is it simply just time to next :00 or :30 (or configured interval)?

Undefined method verify_with_drift

Working on upgrading devise-two-factor from 3.x to 4.x and a part of that was upgrading the OTP gem to 6.0, however now I'm getting the error mentioned in the title

undefined method 'verify_with_drift' for u003cROTP::TOTP

from looking at the code base it seems that the method was removed but the CHANGELOG doesn't mention anything on deprecating the method or replacing it.
I also cant seem to find any other issues related to this. Any help would be appreciate

Is it possible to only have one valid OTP at a given time?

Hi. I'd like to implement something like what PayPal does, but I haven't been able to figure out if it's possible with rotp.

When you request an initial OTP from PayPal to be sent to your mobile phone, you receive the code and a message that it will expire in 5 minutes. If you then request a new OTP, the old one immediately becomes invalid, but the new one remains valid for 5 minutes.

How can this be achieved with rotp?

Thanks!

Interval doesn't work as expected

Hello, I'm trying to set an interval of 30 days, but when I check if that key is still valid on that interval I'm getting false as return.

Example:

THIRTY_DAYS_IN_SECONDS = 2592000
start_date = Time.now
rotp = ROTP::HOTP.new("test", interval: THIRTY_DAYS_IN_SECONDS, digits: 8)
key = rotp.at(start_date)
sleep(5)
rotp2 = ROTP::TOTP.new("test", interval: THIRTY_DAYS_IN_SECONDS, digits: 8)
rotp.verify(key)
=> true
rotp2.verify(key)
=> true
rotp2.verify(key, start_date)
=> true
rotp2.verify(key, start_date + 2591000.seconds)
=> false
rotp2.verify(key, start_date + 2101000.seconds)
=> true
rotp2.verify(key, 26.days.from_now)
 => false 
rotp2.verify(key, 25.days.from_now)
 => true 

My guess is that the problem is with timecode because it's a integer division and some time is lost.

What you think? Am I doing something wrong?

ROTP Giving wrong code

I have implemented Google authenticator by ROTP 2 Years back and it stopped working few months back. Code generated by Google and by ROTP on my server are different. I have checked the server timings, my mobile timings and also updated the Gem with latest version, even then its not working. I have also tried this sample snippet of code

require 'rubygems'
require 'rotp'
totp = ROTP::TOTP.new("JBSWY3DPEHPK3PXP")
p "Current OTP: #{totp.now}"

But it is giving different codes all the time, Please suggest ????

TOTP deemed invalid within the `interval`

Hi. Apologies if I misunderstood something, but I thought that the interval parameter defines how long a TOTP should be valid. However, I'm seeing results that don't conform to this understanding...

Using ROTP v6.1.0 on Ruby 2.7, adapting the example in the README slightly:

totp = ROTP::TOTP.new("base32secret3232", interval: 54)
now = Time.at(1474590600) # 2016-09-23 00:30:00 UTC
code = totp.at(now)
totp.verify(code, at: now + 35) # => 1474590582
totp.verify(code, at: now + 36) # => nil

Shouldn't the code be valid for 54 seconds?

Another example:

now = Time.at(1661254317) # => 2022-08-23 11:31:57 +0000
totp = ROTP::TOTP.new("base32secret3232", interval: 54)
code = totp.at(now) # => "110562"
totp.verify(code, at: now + 8) # => 1661254272
totp.verify(code, at: now + 9) # => nil

ROTP::Base32 doesn't properly handle padding

Padding using = is supported by RFC3548.

Example:

irb(main):003:0> require "rotp"
=> true
irb(main):004:0> require "base32"
=> true
irb(main):005:0> Base32.encode("thisstringresultsinpadding")
=> "ORUGS43TORZGS3THOJSXG5LMORZWS3TQMFSGI2LOM4======"
irb(main):006:0> Base32.decode("ORUGS43TORZGS3THOJSXG5LMORZWS3TQMFSGI2LOM4======")
=> "thisstringresultsinpadding"
irb(main):007:0> ROTP::Base32.decode("ORUGS43TORZGS3THOJSXG5LMORZWS3TQMFSGI2LOM4======")
ROTP::Base32::Base32Error: Invalid Base32 Character - '='
    from /Users/pstengel/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/rotp-1.6.1/lib/rotp/base32.rb:46:in `decode_quint'
    from /Users/pstengel/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/rotp-1.6.1/lib/rotp/base32.rb:28:in `block in decode_block'
    from /Users/pstengel/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/rotp-1.6.1/lib/rotp/base32.rb:28:in `each_char'
    from /Users/pstengel/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/rotp-1.6.1/lib/rotp/base32.rb:28:in `each'
    from /Users/pstengel/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/rotp-1.6.1/lib/rotp/base32.rb:28:in `map'
    from /Users/pstengel/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/rotp-1.6.1/lib/rotp/base32.rb:28:in `decode_block'
    from /Users/pstengel/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/rotp-1.6.1/lib/rotp/base32.rb:10:in `block in decode'
    from /Users/pstengel/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/rotp-1.6.1/lib/rotp/base32.rb:9:in `each'
    from /Users/pstengel/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/rotp-1.6.1/lib/rotp/base32.rb:9:in `decode'
    from (irb):7
    from /Users/pstengel/.rbenv/versions/2.1.2/bin/irb:11:in `<main>'
irb(main):008:0> ROTP::Base32.decode("ORUGS43TORZGS3THOJSXG5LMORZWS3TQMFSGI2LOM4")
=> "thisstringresultsinpadding"

Generated otp lags 30 seconds behind app

I am using the google authenticator app on an android device, when I log the otp shown on the screen of the app and compare it to a log of generated otp's using the gem, the otp shown on the app appears some 30 odd seconds before it is generated by the app.

It won't help to check if there has been drift presumably because the app isn't showing an otp that was previously generated at some earlier time, it is showing an otp that will be generated in the future.

Must not be able to sucessfully validate a given TOTP twice

Description

From RFC 6238:

Note that a prover may send the same OTP inside a given time-step
window multiple times to a verifier. The verifier MUST NOT accept
the second attempt of the OTP after the successful validation has
been issued for the first OTP, which ensures one-time only use of an
OTP.

Once ROTP successfully verifies a TOPT, it should no longer accept subsequent verifications of the same value.

I couldn't find any reference to this requirement for HOTP in RFC 4226. Only TOPT appears to apply.

Expected

otp = ROTP::TOTP.new("base32secret3232")
value = foo.now
foo.verify(value) #=> true
foo.verify(value) #=> false
foo.verify(value) #=> false

Actual

otp = ROTP::TOTP.new("base32secret3232")
value = foo.now
foo.verify(value) #=> true
foo.verify(value) #=> true
foo.verify(value) #=> true

Security Impact

In a two-factor authentication context, an attacker could Man-in-The-Middle the connection between the verifier and provider, obtain the username, password, & OTP values, and log in with the credentials within the current time step (a 30 second window, if defaults are used). Arguably, this defeats the two-factor authentication since the OTP can be replayed multiple times.

Alternatively, an attacker could “shoulder surf” the victim’s second factor device in lieu of compromising the connection.

Not compatible with Google Authenticator on Android

What steps will reproduce the problem?

  1. open Google Authenticator;

  2. scan the example barcode given on README;

  3. run following codes(saved to a ruby file):

    require 'rubygems'
    require 'rotp'
    totp = ROTP::TOTP.new("JBSWY3DPEHPK3PXP")
    p "Current OTP: #{totp.now}"

What is the expected output? What do you see instead?

Different from the code given from Google Authenticator.

What version of the product are you using? On what operating system?

Google Authenticator 2.15
Android 2.1

CVE Alert due to bundling jQuery 1.4.2

When installing this gem there's an older jQuery 1.4.2 js file under this path that triggers CVE-2011-4969 alerts during security scans.

vendor/bundle/ruby/2.7.0/gems/rotp-6.2.0/doc/js
Cross-site scripting (XSS) vulnerability in jQuery before 1.6.3, when using location.hash to select elements, allows remote attackers to inject arbitrary web script or HTML via a crafted tag.

Is it possible to remove this jQuery file dependency altogether?, or update it for a newer version, something later than the CVE version suggests? > 1.6.3

spaces double-encode in issuer parameter field

Hi there, I noticed since 6.0.0 we have different encoding on the params, which is nice, but I suspect there may be some double encoding issues:

totp = ROTP::TOTP.new("secret", issuer: "hi hi")
totp.provisioning_uri("[email protected]")
=> "otpauth://totp/hi%20hi:[email protected]?secret=secret&issuer=hi%2520hi"

I believe the expected issuer should be hi%20hi.

It looks like the double encoding was added here: It encodes the issuer here: https://github.com/mdp/rotp/pull/94/files#diff-ed386b7f6dadc0ef09bf81c543cffbfbR65
using a newer encoding schema to replace + with %20. It then double encodes here:

params_str << "#{k}=#{CGI.escape(v.to_s)}&" if v

How to save a secret code to a user

It doesn't seem it's possible in Rails (using Rails 5) to persist a rotp object between requests? I must be missing something because this seems like the most basic usecase of this library. I want a user to sign up with their phone number and then we generate a pin and send it back to them.

You would think you could do something like this:

class User < ApplicationRecord
    before_validation :generate_otp_secret_key, on: :create
    def generate_otp_secret_key
	self.otp_secret_key = ROTP::TOTP.new("base32secret3232", issuer: "Tap")
    end
end

But since ROTP::TOTP.new returns its own object type, you can't save it to the database. And since you require the original object to verify the pin with .verify, it seems the functionality of generating a pin for a user to verify them is impossible with this library. Given that this is the purpose of the library, I'M SURE I'M MISSING SOMETHING BASIC. Is there a method you're supposed to put in the model that's not mentioned in the readme? Can someone tell me what I'm missing?

The readme gives this example:

User.find(someUserID)
totp = ROTP::TOTP.new(user.otp_secret)

but they never say where user actually comes from. I tried adding user = to the beginning of the first line but that doesn't seem to work either.

Empty string is verified as correct due to Integer coercion

Run this to illustrate what happens:

require 'rotp'

loop do
  totp = ROTP::TOTP.new(ROTP::Base32.random_base32, digits: 4)
  raise totp.now.inspect if totp.verify("")
end

I cannot think of why this would pose any security risk, but it was surprising initially. Although now I understand why it behaves like this after scoping the source, it still seems odd.

Thoughts?

Dropbox: Codes don't match`

I tried using the gem by obtaining a secret from dropbox (removing spaces). The code does not match the one generated by Google Authenticator. However, I can confirm that on the same computer the codes do match for Google App accounts.

Any idea what could be causing this?

verification code doesn't match

I generated a secret key using ROTP::Base32.random_base32, then create totp using ROTP::TOTP.new(secret_key). When I manually input the secret key in Google Authenticator, its verification code doesn't match the TOTP generated using totp.now.

Issuer causes invalid barcode

When adding an issuer, the barcode generated is invalid according to Google Authenticator.

I've tried it with just the issuer parameter.

base32secret = ROTP::Base32.random_base32
totp = ROTP::TOTP.new(base32secret, issuer: 'Example Issuer')
qrcode = totp.provisioning_uri('[email protected]')

img_0601

I've tried it with a issuer label prefix and a issuer parameter, per Google.

base32secret = ROTP::Base32.random_base32
totp = ROTP::TOTP.new(base32secret, issuer: 'Example Issuer)
qrcode = totp.provisioning_uri('Example Issuer:[email protected]')

img_0602

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.