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

Walkthrough

Space play step click any line
Three takeaways
  1. 1TOTP turns a shared secret plus a time window into a short, reproducible code both sides can compute independently.
  2. 2Dynamic truncation extracts a stable 4-byte slice from the HMAC using the last nibble as an offset.
  3. 3Verification tolerates clock drift by checking neighboring time windows and compares codes in constant time.

Related explainers

Share this explainer

Here's the card — post it anywhere.

How TOTP one-time codes work in Ruby — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code