Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@

# Documentation

## otp.lua
This file exposes the main part of the library in which the hotp and totp conventions are concieved.

function otp.new(secret, digits, digest, ...)
Returns a new OTP object used to pass to other OTP functions.
required string secret - base32 encoded string in which OTP codes are generated based on
optional number digits default 6 - how many digits is returned from an OTP generation
optional number digest default "sha1" - the hash used for code generation
situational number {...}[1] - a second interval for timeblocks for generation verification

function otp.generate_otp(instance, input)
Returns an OTP code. Returns nil if input < 0.
required object instance - otp instance from otp.new()
required input - a value > -1

function otp.byte_secret(instance)
Pads % 8 the secret key in instance state. Returns array of decoded secret base32 bytes.
required object instance - otp instance from otp.new()

function otp.int_to_bytestring(i, padding)
Converts 4-byte integers into a binary string.
required number i - integer to convert
optional number padding default 8 - pad bytes out byte groups to multiples of this number

## totp.lua
This file extends the file otp.lua in a polymorphism way. It exposes timeblock based code generation.

functop totp.at(instance, for_time, counter_offset)
Returns a totp code at a specific time based on state.
required object instance - otp instance from otp.new()
required number for_time - time to generate code from in seconds
optional number counter_offset default 0 - skip this many timeblocks in code generation

function totp.now(instance, override)
Returns a totp code at the current time based on os.time() ignored in favor for override.
required object instance - otp instance from otp.new()
optional number override default os.time() - replacement for os.time()

function totp.valid_until(instance, for_time, valid_window)
Returns the time in seconds that the specified for_time will be invalidated.
required object instance - otp instance from otp.new()
required number for_time - time in seconds for finding out the next invalidation period
optional number valid_window default 0 - timeblocks a code should be active for

function totp.verify(instance, key, for_time, valid_window)
Returns true if key is verified, or within the timeblock specified by valid_window from for_time.
required object instance - otp instance from otp.new()
required number key - otp key to verify
optional number for_time default os.time() - time in seconds to verify the current
optional number valid_window default 0 - timeblocks a code should be active for

## hotp.lua
This file extends the file otp.lua in a polymorphism way. It exposes counter based code generation.

function hotp.at(instance, counter)
Returns the current otp code at a specified counter count.
required object instance - otp instance from otp.new()
required number counter - counter to generate otp code from

function hotp.verify(instance, key, counter)
Returns true if key is verified, or the key code matches the generated otp with counter.
required object instance - otp instance from otp.new()
required number key - otp key to verify
required number counter - counter to generate otp code from to match against key

## util.lua
These are your general util functions for generating a OTP uri and internally used functions.

LinearArray util.default_chars[32]
Base32 characters without 0 and 1 (for confusion sakes).

string util.base_uri[17]
A format containing 3 string replacements for an otpauth uri.

function util.build_args(arr)
Given key-value pairs in arr, creates a uri parameters section in the form of ?key=arr[key]&key2=arr[key2].
required dictionary arr - key-value pairs of uri arguments

function util.encode_url(url)
URL-encodes string url to be url-safe.
required string url - the url to make url-safe

function build_uri(secret, name, initial_count, issuer_name, algorithm, digits, period)
Returns a uri built based on otp data for importing in 3rd party apps or to generate QR codes.
required string secret - base32 secret string for otp code generation
required string name - name to append to the issuer_name with a :
optional number initial_count default nil - if method is hotp, this is a counter, if not then should always be nil
required string issuer_name - the issuer of the otp code
required string algorithm - the hash algorithm used to generate the blob to hmac for the otp code
required number digits - how many digits an otp code should have
required number period - if method is totp, this the timeblock an otp is generated for

function util.arr_reverse(tab)
Reverses a linear array. Returns a new table.
required LinearArray tab - a linear array to reverse the elements in

function util.byte_arr_tostring(arr)
Converts a linear array of numbers into characters into a string. Returns a new table.
required LinearArray arr - a linear array of numbers to convert to char and put in a linear array

function util.str_to_byte(str)
Converts a string into a linear array of bytes. Returns a new table.
required string str - string to break down into a byte array

function util.random_base32(length, chars)
Given charset chars, generates and returns a random base32 string with a length of length.
optional number length default 16 - the length of the base32 string
optional LinearArray chars default util.default_chars - the base32 charset to use

2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2017 Cody Tilkins
Copyright (c) 2021 Cody Tilkins

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ A simple One Time Password (OTP) library in lua

Compatible with Authy and Google Authenticator. Full support for QR code url is provided.

Copyright (c) 2021 Cody Tilkins MIT


## Libraries Needed

Expand All @@ -30,11 +32,6 @@ You will need to configure the paths of the requires of this library. I am guess
When it comes down to it, this library will convert your integer numbers to string and do a comparison byte by byte. There is no need for expensive testing - nobody knows what is going on except the key holders and the key can't be reversed because we only send a small part of the hmac. That being said, there is no support for digits > 9 yet - as this is half an int's limit.


## Description

This was actually a spawn off pyotp, but I would necessarily say the code was copied. Things in python aren't in lua, therefore I had to make the methods myself. However, credits will go to the module for providing a guideline of what to do. [Here](https://github.com/pyotp/pyotp) you can find pyotp and realize how different it really is.


_____________

## License
Expand All @@ -58,5 +55,4 @@ To use this library, pick either TOTP or HOTP then use the provided files - givi
* Add comments - there are lacking comments, should match up to COTP's style
* Remove the dependancy on basexx - I have an implementation found in COTP and JOTP that can be ported.
* bit32 isn't actually an external dependancy, but depending on the version you prefer, it is
* sha1 make sure we aren't infringing anything with this

27 changes: 25 additions & 2 deletions hotp.lua
Original file line number Diff line number Diff line change
@@ -1,11 +1,34 @@
--[[
MIT License

Copyright (c) 2021 Cody Tilkins

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]

-- please point this to proper location
local otp = require("otp")

local hotp = {}

hotp.at = function(instance, count)
return otp.generate_otp(instance, count)
hotp.at = function(instance, counter)
return otp.generate_otp(instance, counter)
end

hotp.verify = function(instance, key, counter)
Expand Down
37 changes: 27 additions & 10 deletions otp.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
--[[
MIT License

Copyright (c) 2021 Cody Tilkins

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]

-- please point these to proper location
local basexx = require("basexx")
Expand Down Expand Up @@ -39,6 +62,10 @@ end


otp.generate_otp = function(instance, input)
if (input < 0) then
return nil
end

local hash = sha1.hmac_binary(otp.byte_secret(instance), otp.int_to_bytestring(input))
local offset = bit32.band(string.byte(hash:sub(-1, -1)), 0xF) + 1

Expand Down Expand Up @@ -76,14 +103,4 @@ otp.int_to_bytestring = function(i, padding)
return string.rep('\0', math.max(0, (padding or 8) - #bytes)) .. util.byte_arr_tostring(util.arr_reverse(bytes))
end

otp.random_base32 = function(length, chars)
length = length or 16
chars = chars or util.default_chars
local out = ""
for i=1, length do
out = out .. chars[math.random(1, #chars)]
end
return out
end

return otp
26 changes: 25 additions & 1 deletion test.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
--[[
MIT License

Copyright (c) 2021 Cody Tilkins

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]

----------------------------------------------------------------
-- Initialization Stuff --
Expand All @@ -14,6 +37,7 @@ local DIGEST = "SHA1";
local OTP = require("otp")
local TOTP = require("totp")
local HOTP = require("hotp")
local UTIL = require("util")


-- Create OTPData struct, which decides the environment
Expand Down Expand Up @@ -74,7 +98,7 @@ math.random(1, 2)

local base32_len = 16

local base32_new_secret = OTP.random_base32(base32_len, OTP.util.default_chars)
local base32_new_secret = UTIL.random_base32(base32_len, OTP.util.default_chars)
print("Generated BASE32 Secret: `" .. base32_new_secret .. "`")

print("") -- line break for readability
Expand Down
30 changes: 25 additions & 5 deletions totp.lua
Original file line number Diff line number Diff line change
@@ -1,18 +1,38 @@
--[[
MIT License

Copyright (c) 2021 Cody Tilkins

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]

-- please point this to proper location
local otp = require("otp")

local totp = {}

totp.at = function(instance, for_time, counter_offset)
if (for_time == nil) then
error("No for_time supplied.")
end
return otp.generate_otp(instance, totp.timecode(instance, tonumber(for_time)) + (counter_offset or 0))
end

totp.now = function(instance)
return otp.generate_otp(instance, totp.timecode(instance, os.time()))
totp.now = function(instance, override)
return otp.generate_otp(instance, totp.timecode(instance, override or os.time()))
end

totp.valid_until = function(instance, for_time, valid_window)
Expand Down
35 changes: 34 additions & 1 deletion util.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
--[[
MIT License

Copyright (c) 2021 Cody Tilkins

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]

local util = {}

Expand Down Expand Up @@ -41,7 +64,7 @@ util.build_uri = function(secret, name, initial_count, issuer_name, algorithm, d
algorithm = algorithm and string.upper(algorithm) or ""

local url_args = {
secret = tostringsecret,
secret = tostring(secret),
issuer = issuer_name,
counter = tostring(initial_count),
algorithm = algorithm,
Expand Down Expand Up @@ -77,4 +100,14 @@ util.str_to_byte = function(str)
return out
end

util.random_base32 = function(length, chars)
length = length or 16
chars = chars or util.default_chars
local out = ""
for i=1, length do
out = out .. chars[math.random(1, #chars)]
end
return out
end

return util