Skip to content

Instantly share code, notes, and snippets.

@fudanchii
Last active March 23, 2022 10:28
Show Gist options
  • Save fudanchii/46f1f7151eb7600796b875f70bc48c0a to your computer and use it in GitHub Desktop.
Save fudanchii/46f1f7151eb7600796b875f70bc48c0a to your computer and use it in GitHub Desktop.
require 'openssl'
# ref: https://datatracker.ietf.org/doc/html/rfc6238
class TOTP
def initialize(secret:, t0: 0, x: 30, len: 10)
raise TOTP::Error::InvalidSecret if secret.nil?
@secret = secret
@t0 = t0
@x = x
@len = len
end
def generate
_generate(current_timestamp)
end
private def _generate(timestamp)
hex_i((timestamp - @t0) / @x)
.then { |t| OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), @secret, t) }
.then { |digest| dt(digest) }
.then { |preotp| preotp % (10**@len) }
.then { |otp| otp.to_s.rjust(@len, "0") }
end
def verify(otp:)
otp1 = generate
otp2 = _generate(current_timestamp - @x)
otp == otp1 || otp == otp2
end
private def dt(val)
offset = byte(val[val.length - 1]) & 0x0f
((byte(val[offset]) & 0x7f) << 24) |
((byte(val[offset+1]) & 0xff) << 16) |
((byte(val[offset+2]) & 0xff) << 8) |
(byte(val[offset+3]) & 0xff)
end
private def hex_i(i)
[i].pack("Q>")
end
private def byte(ch)
ch.unpack("C")[0]
end
private def current_timestamp
Time.now.to_i
end
module Error
class InvalidSecret
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment