Skip to content

Instantly share code, notes, and snippets.

@jurisgalang
Last active November 17, 2017 00:46
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jurisgalang/7637348 to your computer and use it in GitHub Desktop.
Save jurisgalang/7637348 to your computer and use it in GitHub Desktop.
Ruby implementation of the Time-Based One-Time Password (TOTP) Algorithm described at http://tools.ietf.org/html/rfc6238
[jgalang@rashomon:~/Code/skunkworks/2fa]
∴ ruby totp_demo.rb
Time(sec) Time (UTC format) Value of T(Hex) TOTP Mode OK
------------------------------------------------------------------------------------------
59 1970-01-01 00:00:59 0000000000000001 94287082 SHA1 ✔
59 1970-01-01 00:00:59 0000000000000001 46119246 SHA256 ✔
59 1970-01-01 00:00:59 0000000000000001 90693936 SHA512 ✔
1111111109 2005-03-18 01:58:29 00000000023523EC 07081804 SHA1 ✔
1111111109 2005-03-18 01:58:29 00000000023523EC 68084774 SHA256 ✔
1111111109 2005-03-18 01:58:29 00000000023523EC 25091201 SHA512 ✔
1111111111 2005-03-18 01:58:31 00000000023523ED 14050471 SHA1 ✔
1111111111 2005-03-18 01:58:31 00000000023523ED 67062674 SHA256 ✔
1111111111 2005-03-18 01:58:31 00000000023523ED 99943326 SHA512 ✔
1234567890 2009-02-13 23:31:30 000000000273EF07 89005924 SHA1 ✔
1234567890 2009-02-13 23:31:30 000000000273EF07 91819424 SHA256 ✔
1234567890 2009-02-13 23:31:30 000000000273EF07 93441116 SHA512 ✔
2000000000 2033-05-18 03:33:20 0000000003F940AA 69279037 SHA1 ✔
2000000000 2033-05-18 03:33:20 0000000003F940AA 90698825 SHA256 ✔
2000000000 2033-05-18 03:33:20 0000000003F940AA 38618901 SHA512 ✔
20000000000 2603-10-11 11:33:20 0000000027BC86AA 65353130 SHA1 ✔
20000000000 2603-10-11 11:33:20 0000000027BC86AA 77737706 SHA256 ✔
20000000000 2603-10-11 11:33:20 0000000027BC86AA 47863826 SHA512 ✔
# Ruby implementation of the Time-Based One-Time Password (TOTP)
# Algorithm described at http://tools.ietf.org/html/rfc6238
#
# Copyright (c) 2013 Juris Galang
# MIT Licence: http://opensource.org/licenses/MIT
#
require 'base64'
require 'openssl'
class TOTP
DEFAULT_INTERVAL = 30
DEFAULT_LENGTH = 8
DEFAULT_CRYPTO = 'sha1'
def initialize secret, interval: DEFAULT_INTERVAL, length: DEFAULT_LENGTH, crypto: DEFAULT_CRYPTO
@secret = secret
@interval = interval
@length = length
@crypto = crypto
end
def at time = Time.now
"%0.#{@length}d" % otp(step time)
end
private
def otp step
msg = [step].pack('H*')
key = [@secret].pack('H*')
hash = OpenSSL::HMAC.digest(@crypto, key, msg)
offset = hash[hash.length - 1].ord & 0xf
hash = hash[offset .. offset + 3].bytes
value = ((hash[0] & 0x7f) << 24) |
((hash[1] & 0xff) << 16) |
((hash[2] & 0xff) << 8) |
(hash[3] & 0xff)
(value % (10 ** @length))
end
def step time
'%0.16x' % (time.to_i / @interval).to_s(16).hex
end
end
# Sample usage and verification against the reference implementation's
# output as described at http://tools.ietf.org/html/rfc6238#appendix-B
#
# Copyright (c) 2013 Juris Galang
# MIT Licence: http://opensource.org/licenses/MIT
#
require './totp.rb'
SECRETS = {
'SHA1' => '3132333435363738393031323334353637383930',
'SHA256' => '3132333435363738393031323334353637383930313233343536373839303132',
'SHA512' => '31323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334'
}
lines = DATA.read.split("\n")
header = lines[1].split('|').map!{ |n| n.strip }.delete_if{ |n| n.empty? }
input = lines[3..-1].delete_if{ |l| l =~ /\+/ }
puts
puts sprintf("%-11s\t%-13s\t%-16s\t%-8s\t%-6s\t%-2s", *header, 'OK')
puts '-' * 90
input.each do |line|
t_sec, t_utc, t_hex, expected, crypto = *line.split('|').map!{ |n| n.strip }.delete_if{ |n| n.empty? }
time = Time.utc(*t_utc.split(/ |:|-/))
secret = SECRETS[crypto]
totp = TOTP.new(secret, crypto: crypto)
matched = expected == totp.at(time) ? '✔' : '✘'
puts sprintf("%-11s\t%s\t%s\t%s\t%-6s\t%2s", t_sec, t_utc, t_hex, expected, crypto, matched)
end
__END__
+---------------+-----------------------+------------------+--------+--------+
| Time(sec) | Time (UTC format) | Value of T(Hex) | TOTP | Mode |
+---------------+-----------------------+------------------+--------+--------+
| 59 | 1970-01-01 00:00:59 | 0000000000000001 |94287082| SHA1 |
| 59 | 1970-01-01 00:00:59 | 0000000000000001 |46119246| SHA256 |
| 59 | 1970-01-01 00:00:59 | 0000000000000001 |90693936| SHA512 |
+---------------+-----------------------+------------------+--------+--------+
| 1111111109 | 2005-03-18 01:58:29 | 00000000023523EC |07081804| SHA1 |
| 1111111109 | 2005-03-18 01:58:29 | 00000000023523EC |68084774| SHA256 |
| 1111111109 | 2005-03-18 01:58:29 | 00000000023523EC |25091201| SHA512 |
+---------------+-----------------------+------------------+--------+--------+
| 1111111111 | 2005-03-18 01:58:31 | 00000000023523ED |14050471| SHA1 |
| 1111111111 | 2005-03-18 01:58:31 | 00000000023523ED |67062674| SHA256 |
| 1111111111 | 2005-03-18 01:58:31 | 00000000023523ED |99943326| SHA512 |
+---------------+-----------------------+------------------+--------+--------+
| 1234567890 | 2009-02-13 23:31:30 | 000000000273EF07 |89005924| SHA1 |
| 1234567890 | 2009-02-13 23:31:30 | 000000000273EF07 |91819424| SHA256 |
| 1234567890 | 2009-02-13 23:31:30 | 000000000273EF07 |93441116| SHA512 |
+---------------+-----------------------+------------------+--------+--------+
| 2000000000 | 2033-05-18 03:33:20 | 0000000003F940AA |69279037| SHA1 |
| 2000000000 | 2033-05-18 03:33:20 | 0000000003F940AA |90698825| SHA256 |
| 2000000000 | 2033-05-18 03:33:20 | 0000000003F940AA |38618901| SHA512 |
+---------------+-----------------------+------------------+--------+--------+
| 20000000000 | 2603-10-11 11:33:20 | 0000000027BC86AA |65353130| SHA1 |
| 20000000000 | 2603-10-11 11:33:20 | 0000000027BC86AA |77737706| SHA256 |
| 20000000000 | 2603-10-11 11:33:20 | 0000000027BC86AA |47863826| SHA512 |
+---------------+-----------------------+------------------+--------+--------+
@carlosjhr64
Copy link

Pretty sure
'%0.16x' % (time.to_i / @interval).to_s(16).hex
has redundant code. Why not just:
'%0.16x' % (time.to_i / @interval)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment