Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
source 'https://rubygems.org'

# Specify your gem's dependencies in rotpl.gemspec
gemspec

group :development, :test do
gem "rspec"
gem "rspec-given"
gem "pry"
gem "base32"
end
63 changes: 35 additions & 28 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,43 +1,50 @@
PATH
remote: .
specs:
rotpl (0.1.0)
base32 (~> 0.3)

GEM
remote: https://rubygems.org/
specs:
base32 (0.3.2)
coderay (1.1.1)
diff-lcs (1.3)
given_core (3.8.0)
base32 (0.3.4)
coderay (1.1.3)
diff-lcs (1.6.2)
given_core (3.8.2)
sorcerer (>= 0.3.7)
method_source (0.8.2)
pry (0.10.4)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
rspec (3.5.0)
rspec-core (~> 3.5.0)
rspec-expectations (~> 3.5.0)
rspec-mocks (~> 3.5.0)
rspec-core (3.5.4)
rspec-support (~> 3.5.0)
rspec-expectations (3.5.0)
method_source (1.1.0)
pry (0.15.2)
coderay (~> 1.1)
method_source (~> 1.0)
rake (13.3.1)
rspec (3.13.2)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.6)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.5.0)
rspec-given (3.8.0)
given_core (= 3.8.0)
rspec-support (~> 3.13.0)
rspec-given (3.8.2)
given_core (= 3.8.2)
rspec (>= 2.14.0)
rspec-mocks (3.5.0)
rspec-mocks (3.13.7)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.5.0)
rspec-support (3.5.0)
slop (3.6.0)
sorcerer (1.0.2)
rspec-support (~> 3.13.0)
rspec-support (3.13.6)
sorcerer (2.0.1)

PLATFORMS
ruby
x86_64-linux

DEPENDENCIES
base32
pry
rspec
rspec-given
rake (~> 13.0)
rotpl!
rspec (~> 3.0)
rspec-given (~> 3.8)

BUNDLED WITH
1.12.5
2.7.2
105 changes: 84 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# ROTPL - Ruby OTP-Two-Factor-authentication Library

[![Gem Version](https://badge.fury.io/rb/rotpl.svg)](https://badge.fury.io/rb/rotpl)

A Ruby implementation of HMAC-based One-Time Password (HOTP) and Time-based One-Time Password (TOTP) algorithms, fully compatible with Google Authenticator.

## Features
Expand All @@ -10,32 +12,40 @@ A Ruby implementation of HMAC-based One-Time Password (HOTP) and Time-based One-
- Clock skew tolerance (±30 seconds by default)
- Clean, testable API with dependency injection
- RFC test vector validation
- Comprehensive test suite (58 passing specs)

## Installation

This library is currently distributed as source code. To use it in your project:
Add this line to your application's Gemfile:

```ruby
gem 'rotpl'
```

1. Copy the `lib/` directory into your project
2. Install the required dependency for Google Authenticator support:
And then execute:

```bash
gem install base32
bundle install
```

Or add to your Gemfile:
Or install it yourself as:

```ruby
gem 'base32'
```bash
gem install rotpl
```

Note: The `base32` gem is only required if you plan to use `GoogleAuthenticator`. The core `Hotp` and `Totp` classes have no external dependencies.
### Requirements

- Ruby >= 2.6.0
- Dependencies are automatically installed with the gem:
- `base32` (~> 0.3) - for Google Authenticator Base32 encoding

## Quick Start

### Google Authenticator (Most Common Use Case)

```ruby
require_relative 'lib/google_authenticator'
require 'rotpl'

# Secret from Google Authenticator QR code
secret = "JBSWY3DPEHPK3PXP"
Expand All @@ -58,7 +68,7 @@ end
### Time-based OTP (TOTP)

```ruby
require_relative 'lib/totp'
require 'rotpl'

# Use a binary secret (not Base32-encoded)
secret = "12345678901234567890"
Expand All @@ -80,7 +90,7 @@ codes = totp.generate_otp
### Counter-based OTP (HOTP)

```ruby
require_relative 'lib/hotp'
require 'rotpl'

secret = "12345678901234567890"

Expand All @@ -102,7 +112,7 @@ otp = Rotpl::Hotp.generate_otp(secret, 0, code_digits: 8)
### Building a Login System

```ruby
require_relative 'lib/google_authenticator'
require 'rotpl'

class TwoFactorAuth
def initialize(user_secret)
Expand All @@ -127,7 +137,9 @@ end
### Generating QR Code Secrets

```ruby
require 'base32'
require 'rotpl'
require 'securerandom'
require 'base32' # included as a dependency with the gem

# Generate a random secret for a new user
random_secret = Base32.encode(SecureRandom.random_bytes(20))
Expand All @@ -141,12 +153,17 @@ user.update(two_factor_secret: random_secret)
qr_url = "otpauth://totp/#{user.email}?secret=#{random_secret}&issuer=MyApp"

# Generate QR code from qr_url and display to user
# Use a QR code gem like 'rqrcode' to generate the actual QR image
```

### Handling Clock Skew

```ruby
require 'rotpl'

# TOTP returns 3 codes by default
secret = "12345678901234567890"
totp = Rotpl::Totp.new(secret)
codes = totp.generate_otp
# codes[0] = previous 30-second window
# codes[1] = current 30-second window
Expand All @@ -161,6 +178,10 @@ end
### Custom Time Windows

```ruby
require 'rotpl'

secret = "12345678901234567890"

# 60-second windows instead of 30
totp = Rotpl::Totp.new(secret, time_step: 60)

Expand All @@ -184,16 +205,40 @@ All classes and methods include YARD documentation. Key classes:
- `GoogleAuthenticator.new(base32_secret, time_step: 30)` - Initialize with Base32 secret
- `#generate_otp(time = Time.now, code_digits: 6)` - Generate codes (returns array of 3)

### Checking the Gem Version

```ruby
require 'rotpl'
puts Rotpl::VERSION # => "0.1.0"
```

## Testing

Run the test suite:
The gem includes a comprehensive test suite with 58 specs covering:
- RFC 4226 (HOTP) test vectors
- RFC 6238 (TOTP) test vectors
- Google Authenticator compatibility
- Clock skew tolerance
- Edge cases and performance

### Running Tests

```bash
# Clone the repository
git clone https://github.com/parasquid/rotpl.git
cd rotpl

# Install dependencies
bundle install

# Run all tests
rspec

# Run with documentation format
rspec --format documentation
```

Tests validate against official RFC 4226 and RFC 6238 test vectors.
All tests validate against official RFC 4226 and RFC 6238 test vectors.

## Security Considerations

Expand All @@ -204,18 +249,36 @@ Tests validate against official RFC 4226 and RFC 6238 test vectors.
- Consider requiring backup codes for account recovery
- Secrets should be at least 160 bits (20 bytes) for HMAC-SHA1

## Development

After checking out the repo, run `bundle install` to install dependencies. Then, run `rspec` to run the tests.

To install this gem onto your local machine, run:

```bash
gem build rotpl.gemspec
gem install rotpl-0.1.0.gem
```

To release a new version:
1. Update the version number in `lib/rotpl/version.rb`
2. Update the changelog
3. Run `gem build rotpl.gemspec`
4. Run `gem push rotpl-x.x.x.gem` (requires RubyGems account)

## Contributing

1. Fork it
1. Fork it (https://github.com/parasquid/rotpl/fork)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Don't forget tests!
6. Create new Pull Request
3. Write tests for your changes
4. Make your changes and ensure tests pass (`rspec`)
5. Commit your changes (`git commit -am 'Add some feature'`)
6. Push to the branch (`git push origin my-new-feature`)
7. Create a new Pull Request

## License

GNU LGPL v3 - See LICENSE file for details
GNU LGPL v3 or later - See LICENSE file for details

## Copyright

Expand Down
17 changes: 17 additions & 0 deletions lib/rotpl.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

require_relative "rotpl/version"
require_relative "rotpl/hotp"
require_relative "rotpl/totp"
require_relative "rotpl/google_authenticator"

# ROTPL - Ruby OTP-Two-Factor-authentication Library
#
# A Ruby implementation of HMAC-based One-Time Password (HOTP) and
# Time-based One-Time Password (TOTP) algorithms, fully compatible
# with Google Authenticator.
#
# @see https://tools.ietf.org/html/rfc4226 RFC 4226 (HOTP)
# @see https://tools.ietf.org/html/rfc6238 RFC 6238 (TOTP)
module Rotpl
end
File renamed without changes.
File renamed without changes.
File renamed without changes.
5 changes: 5 additions & 0 deletions lib/rotpl/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

module Rotpl
VERSION = "0.1.0"
end
39 changes: 39 additions & 0 deletions rotpl.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

require_relative "lib/rotpl/version"

Gem::Specification.new do |spec|
spec.name = "rotpl"
spec.version = Rotpl::VERSION
spec.authors = ["parasquid"]
spec.email = ["parasquid@gmail.com"]

spec.summary = "Ruby OTP-Two-Factor-authentication Library"
spec.description = "A Ruby implementation of HMAC-based One-Time Password (HOTP) and Time-based One-Time Password (TOTP) algorithms, fully compatible with Google Authenticator."
spec.homepage = "https://github.com/parasquid/rotpl"
spec.license = "LGPL-3.0-or-later"
spec.required_ruby_version = ">= 2.6.0"

spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = "#{spec.homepage}.git"
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"

# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(__dir__) do
`git ls-files -z`.split("\x0").reject do |f|
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
end
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

# Runtime dependencies
spec.add_dependency "base32", "~> 0.3"

# Development dependencies
spec.add_development_dependency "rspec", "~> 3.0"
spec.add_development_dependency "rspec-given", "~> 3.8"
spec.add_development_dependency "rake", "~> 13.0"
end
2 changes: 1 addition & 1 deletion spec/feature_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
require "spec_helper"
require_relative "../lib/hotp"
require "rotpl"

describe Rotpl::Hotp do
Given(:klass) { Rotpl::Hotp }
Expand Down
Loading