-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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 ✔ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 | | |
+---------------+-----------------------+------------------+--------+--------+ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Pretty sure
'%0.16x' % (time.to_i / @interval).to_s(16).hex
has redundant code. Why not just:
'%0.16x' % (time.to_i / @interval)