ruby
46 lines · 7 steps
How TOTP one-time codes work in Ruby
A time-based one-time password generator that derives 6-digit codes from a shared secret and the current clock.
Explained by
highlit
1require "openssl"
2require "base32"
3
4class TOTP
5 DIGITS = 6
6 PERIOD = 30
7 ALGORITHM = "sha1"
8
9 def initialize(secret)
10 @secret = secret
11 end
12
13 def now(at: Time.now)
14 counter = (at.to_i / PERIOD).to_i
15 generate(counter)
16 end
17
18 def verify(code, at: Time.now, drift: 1)
19 counter = (at.to_i / PERIOD).to_i
20 (-drift..drift).any? do |offset|
21 secure_compare(generate(counter + offset), code.to_s)
22 end
23 end
24
25 private
26
27 def generate(counter)
28 key = Base32.decode(@secret)
29 message = [counter].pack("Q>")
30 hmac = OpenSSL::HMAC.digest(ALGORITHM, key, message)
31
32 offset = hmac[-1].ord & 0x0f
33 binary = (hmac[offset].ord & 0x7f) << 24 |
34 (hmac[offset + 1].ord & 0xff) << 16 |
35 (hmac[offset + 2].ord & 0xff) << 8 |
36 (hmac[offset + 3].ord & 0xff)
37
38 (binary % 10**DIGITS).to_s.rjust(DIGITS, "0")
39 end
40
41 def secure_compare(a, b)
42 return false unless a.bytesize == b.bytesize
43
44 OpenSSL.fixed_length_secure_compare(a, b)
45 end
46end
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1TOTP turns a shared secret plus a time window into a short, reproducible code both sides can compute independently.
- 2Dynamic truncation extracts a stable 4-byte slice from the HMAC using the last nibble as an offset.
- 3Verification tolerates clock drift by checking neighboring time windows and compares codes in constant time.
Related explainers
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
ruby
class LogAggregator BUCKET_FORMAT = "%Y-%m-%dT%H:%M" def initialize(entries)
Bucketing log entries by the minute in Ruby
aggregation
hashing
enumerable
Intermediate
5 steps
ruby
class WeeklySignupsReport DEFAULT_WEEKS = 12 def initialize(weeks: DEFAULT_WEEKS, source: User.all)
Building a weekly signups report in Rails
service object
aggregation
group by
Intermediate
7 steps
ruby
class ApplicationController < ActionController::Base EXPERIMENTS = { checkout_button_color: %w[control blue green], onboarding_flow: %w[control streamlined]
How A/B test cohorts are assigned in Rails
a-b-testing
cookies
hashing
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-totp-one-time-codes-work-in-ruby-explained-ruby-5c2f/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.