PBKDF2 Lua Implementation Issue - lua

I am trying to write a PBKDF2 implementation in pure lua. I am writing it because I want to use it in a sandboxed lua environment that does not allow outside libraries. I had a look at the standard document from the IETF and had at it. Below is the code I have come up with:
do
package.preload["pbkdf2"] = function()
local hmac = require 'hmac'
local len = string.len
local gsub = string.gsub
local format = string.format
local byte = string.byte
local char = string.char
local concat = table.concat
local ceil = math.ceil
local function toBytes(str)
local tmp = {}
for i = 1, len(str) do
tmp[i] = byte(str, i)
end
return tmp
end
local function toString(bArray)
local tmp = {}
for i = 1, #bArray do
tmp[i] = char(bArray[i])
end
tmp = concat(tmp)
return tmp
end
-- transform a string of bytes in a string of hexadecimal digits
local function asHex(s)
local h = gsub(s, ".", function(c)
return format("%02x", byte(c))
end)
return h
end
local num2string = function(l, n)
local s = {}
for i = 1, n do
local idx = (n + 1) - i
s[idx] = char(l & 255)
l = l >> 8
end
s = concat(s)
return s
end
local buildBlock = function(hFun, password, salt, c, int)
local tmp
local tmp2
for i = 1, c do
if i == 1 then
print(int)
print(salt .. int)
-- PRF(password, salt || INT_32_BE(i)
-- return result of hash as a byte string
tmp = hmac.hash(hFun, password, salt .. num2string(int, 4), true)
else
-- returns result of hash as byte string
tmp2 = hmac.hash(hFun, password, tmp, true)
-- transform to byte arrays
tmp2 = toBytes(tmp2)
tmp = toBytes(tmp)
assert(#tmp == #tmp2)
-- apply XOR over bytes in both arrays
-- save results to final array
for j = 1, #tmp do
-- perform XOR operation on both elements in the respective arrays
tmp[j] = tmp[j] ~ tmp2[j]
end
-- transform back into byte string to pass to next hash
tmp = toString(tmp)
end
end
return tmp
end
local truncate = function(str, pos)
return string.sub(str, 1, pos)
end
local deriveKey = function(hFun, message, salt, c, dLen)
local hLen = hFun.outputSize
-- the derived key cannot be larger than (2^32 * hLen)
if dLen > (2^32) * hLen then error("The derived key cannot be larger than 2^32 times the output size of the hash function.") end
-- the block size is the desired key length divided by the output size of the underlying hash function, rounded up
local blockSize = ceil(dLen/hLen)
-- to store our blocks
local final = {}
for i = 1, blockSize do
-- lets make our blocks in here
final[i] = buildBlock(hFun, message, salt, c, i)
end
local result
if #final == 1 then
result = final[1] -- we only have one block
else
result = concat(final) -- turns final into a bytestring to be outputted
end
--if #result > dLen then truncate(final, dLen) end
assert(#result == dLen)
return asHex(result) -- outputs as a hex value
end
return {deriveKey = deriveKey}
end
end
This code is not getting the correct answers. Testing this code with test vectors provided here, assuming that the underlying PRF is HMAC-SHA256, the output is below:
key: "password"
salt: "salt"
c: 1
dkLen: 32
Got: 13463842ec330934dc124494b40d8baade465b72f3fcadad741f2d0e052fd2f5
Expected: 120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b
key: "password"
salt: "salt"
c: 2
dkLen: 32
Got: 8b82aed26f503effdbc6c14bc7f0338b2b90e387f14ac1f91f9ad74e618f9558
Expected: AE4D0C95AF6B46D32D0ADFF928F06DD02A303F8EF3C251DFD6E2D85A95474C43
I believe it may have something to do with the string to byte encoding, but I cannot pinpoint what exactly is causing the issue. When I was testing my HMAC code, I had to rely on online generators because I couldn't find vectors for HMAC-SHA224 and HMAC-SHA256. Some calculators would give me completely different output values for the same key, message combination. That could be because of how they are processing the inputs, but I am not sure. I would appreciate it if someone more experienced could help me out with this.
EDIT: This problem is solved. Seems that all that was needed was to pass int as a binary string of length 4. I updated the code with the fixes.
EDIT 2: I read the standard again to realize the solution was in my face the entire time (standard says to encode i as a 32-bit big endian integer).

The solution was to convert int to a binary string of length 4. Thanks to #EgorSkriptunoff for his insight.

Related

Reliable way of getting the exact decimals from any number

I'm having problem returning spesific amount of decimal numbers from this function, i would like it to get that info from "dec" argument, but i'm stuck with this right now.
Edit: Made it work with the edited version bellow but isn't there a better way?
local function remove_decimal(t, dec)
if type(dec) == "number" then
for key, num in pairs(type(t) == "table" and t or {}) do
if type(num) == "number" then
local num_to_string = tostring(num)
local mod, d = math.modf(num)
-- find only decimal numbers
local num_dec = num_to_string:sub(#tostring(mod) + (mod == 0 and num < 0 and 3 or 2))
if dec <= #num_dec then
-- return amount of deciamls in the num by dec
local r = d < 0 and "-0." or "0."
local r2 = r .. num_dec:sub(1, dec)
t[key] = mod + tonumber(r2)
end
end
end
end
return t
end
By passing the function bellow i want a result like this:
result[1] > 0.12
result[2] > -0.12
result[3] > 123.45
result[4] > -1.23
local result = remove_decimal({0.123, -0.123, 123.456, -1.234}, 2)
print(result[1])
print(result[2])
print(result[3])
print(result[4])
I tried this but it seems to only work with one integer numbers and if number is 12.34 instead of 1.34 e.g, the decimal place will be removed and become 12.3. Using other methods
local d = dec + (num < 0 and 2 or 1)
local r = tonumber(num_to_string:sub(1, -#num_to_string - d)) or 0
A good approach is to find the position of the decimal point (the dot, .) and then extract a substring starting from the first character to the dot's position plus how many digits you want:
local function truncate(number, dec)
local strnum = tostring(number)
local i, j = string.find(strnum, '%.')
if not i then
return number
end
local strtrn = string.sub(strnum, 1, i+dec)
return tonumber(strtrn)
end
Call it like this:
print(truncate(123.456, 2))
print(truncate(1234567, 2))
123.45
1234567
To bulk-truncate a set of numbers:
local function truncate_all(t, dec)
for key, value in pairs(t) do
t[key] = truncate(t[key], dec)
end
return t
end
Usage:
local result = truncate_all({0.123, -0.123, 123.456, -1.234}, 2)
for key, value in pairs(result) do
print(key, value)
end
1 0.12
2 -0.12
3 123.45
4 -1.23
One could use the function string.format which is similar to the printf functions from C language. If one use the format "%.2f" the resulting string will contain 2 decimals, if one use "%.3f" the resulting string will be contain 3 decimals, etc. The idea is to dynamically create the format "%.XXXf" corresponding to the number of decimal needed by the function. Then call the function string.format with the newly created format string to generate the string "123.XXX". The last step would be to convert back the string to a number with the function tonumber.
Note that if one want the special character % to be preserved when string.format is called, you need to write %%.
function KeepDecimals (Number, DecimalCount)
local FloatFormat = string.format("%%.%df", DecimalCount)
local String = string.format(FloatFormat, Number)
return tonumber(String)
end
The behavior seems close to what the OP is looking for:
for Count = 1, 5 do
print(KeepDecimals(1.123456789, Count))
end
This code should print the following:
1.1
1.12
1.123
1.1235
1.12346
Regarding the initial code, it's quite straight-forward to integrate the provided solution. Note that I renamed the function to keep_decimal because in my understanding, the function will keep the requested number of decimals, and discard the rest.
function keep_decimal (Table, Count)
local NewTable = {}
local NewIndex = 1
for Index = 1, #Table do
NewTable[NewIndex] = KeepDecimal(Table[Index], Count)
NewIndex = NewIndex + 1
end
return NewTable
end
Obviously, the code could be tested easily, simply by copy and pasting into a Lua interpreter.
Result = keep_decimal({0.123, -0.123, 123.456, -1.234}, 2)
for Index = 1, #Result do
print(Result[Index])
end
This should print the following:
0.12
-0.12
123.46
-1.23
Edit due to the clarification of the need of truncate:
function Truncate (Number, Digits)
local Divider = Digits * 10
local TruncatedValue = math.floor(Number * Divider) / Divider
return TruncatedValue
end
On my computer, the code is working as expected:
> Truncate(123.456, 2)
123.45

Generating star pattern in LUA

I am new to programming in LUA. And I am not able to solve this question below.
Given a number N, generate a star pattern such that on the first line there are N stars and on the subsequent lines the number of stars decreases by 1.
The pattern generated should have N rows. In every row, every fifth star (*) is replaced with a hash (#). Every row should have the required number of stars (*) and hash (#) symbols.
Sample input and output, where the first line is the number of test cases
This is what I tried.. And I am not able to move further
function generatePattern()
n = tonumber(io.read())
i = n
while(i >= 1)
do
j = 1
while(j<=i)
do
if(j<=i)
then
if(j%5 == 0)
then
print("#");
else
print("*");
end
print(" ");
end
j = j+1;
end
print("\n");
i = i-1;
end
end
tc = tonumber(io.read())
for i=1,tc
do
generatePattern()
end
First, just the stars without hashes. This part is easy:
local function pattern(n)
for i=n,1,-1 do
print(string.rep("*", i))
end
end
To replace each 5th asterisk with a hash, you can extend the expression with the following substitution:
local function pattern(n)
for i=n,1,-1 do
print((string.rep("*", i):gsub("(%*%*%*%*)%*", "%1#")))
end
end
The asterisks in the pattern need to be escaped with a %, since * holds special meaning within Lua patterns.
Note that string.gsub returns 2 values, but they can be truncated to one value by adding an extra set of parentheses, leading to the somewhat awkward-looking form print((..)).
Depending on Lua version the metamethod __index holding rep for repeats...
--- Lua 5.3
n=10
asterisk='*'
print(asterisk:rep(n))
-- puts out: **********
#! /usr/bin/env lua
for n = arg[1], 1, -1 do
local char = ''
while #char < n do
if #char %5 == 4 then char = char ..'#'
else char = char ..'*'
end -- mod 5
end -- #char
print( char )
end -- arg[1]
chmod +x asterisk.lua
./asterisk.lua 15
Please do not follow this answer since it is bad coding style! I would delete it but SO won't let me. See comment and other answers for better solutions.
My Lua print adds newlines to each printout, therefore I concatenate each character in a string and print the concatenated string out afterwards.
function generatePattern()
n = tonumber(io.read())
i = n
while(i >= 1)
do
ouput = ""
j = 1
while(j<=i)
do
if(j%5 == 0)
then
ouput=ouput .. "#";
else
ouput=ouput .. "*";
end
j = j+1;
end
print(ouput);
i = i-1;
end
end
Also this code is just yours minimal transformed to give the correct output. There are plenty of different ways to solve the task, some are faster or more intuitive than others.

Rate limiting with lua

We have implemented redis based rate limiting for our web service which has been taken from here. I am duplicating the relevant code here.
local limits = cjson.decode(ARGV[1])
local now = tonumber(ARGV[2])
local weight = tonumber(ARGV[3] or '1')
local longest_duration = limits[1][1] or 0
local saved_keys = {}
-- handle cleanup and limit checks
for i, limit in ipairs(limits) do
local duration = limit[1]
longest_duration = math.max(longest_duration, duration)
local precision = limit[3] or duration
precision = math.min(precision, duration)
local blocks = math.ceil(duration / precision)
local saved = {}
table.insert(saved_keys, saved)
saved.block_id = math.floor(now / precision)
saved.trim_before = saved.block_id - blocks + 1
saved.count_key = duration .. ':' .. precision .. ':'
saved.ts_key = saved.count_key .. 'o'
for j, key in ipairs(KEYS) do
local old_ts = redis.call('HGET', key, saved.ts_key)
old_ts = old_ts and tonumber(old_ts) or saved.trim_before
if old_ts > now then
-- don't write in the past
return 1
end
-- discover what needs to be cleaned up
local decr = 0
local dele = {}
local trim = math.min(saved.trim_before, old_ts + blocks)
for old_block = old_ts, trim - 1 do
local bkey = saved.count_key .. old_block
local bcount = redis.call('HGET', key, bkey)
if bcount then
decr = decr + tonumber(bcount)
table.insert(dele, bkey)
end
end
-- handle cleanup
local cur
if #dele > 0 then
redis.call('HDEL', key, unpack(dele))
cur = redis.call('HINCRBY', key, saved.count_key, -decr)
else
cur = redis.call('HGET', key, saved.count_key)
end
-- check our limits
if tonumber(cur or '0') + weight > limit[2] then
return 1
end
end
end
I am trying to figure out the meaning of the comment -- don't write in the past
I don't see how a case would be possible where old_ts is greater than now
I have put logs all over the lua code but without any success.
At maximum old_ts can be equal to saved.trim_before which in turn can be equal to now if precision is 1 and blocks is 1. But not greater .
It would be helpful if someone has insights on it.
If you look at the gist provided in the article
https://gist.github.com/josiahcarlson/80584b49da41549a7d5c
There is comment which asks
In over_limit_sliding_window_lua_, should
if old_ts > now then
at here be
if old_ts > saved.block_id then
And I agree to this, the old_ts is supposed to have the bucket and when the bucket jumps to the next slot, that is when old_ts will be greater then than the block_id

Pure Lua hashing method

This has been bothering be for a while now, I cannot seem to find a pure Lua implementation of a popular hashing method like SHA256, SHA512, or Whirlpool. I need it as I will be hashing the password client side before sending it of to a server. Speed isn't a worry here, I don't care if it takes 10 or so seconds to hash 10,000 times, I will be running it on a thread.
I have tried a couple before, which seemed like they worked perfectly fine at first, but when I tried a different input strings (usually longer ones), the hash comes out as a totally incorrect hash output.
I am using the LuaJIT version of Love2D, so it already has the BitOp library implemented. If any of you know any good implementations of these hashing methods or any similar secure ones then please let me know!
Thank you!
UPDATE: Here are some results!
Firstly this is the test code I am using.
https://github.com/JustAPerson/LuaCrypt
INPUT: Test string
OUTPUT: a3e49d843df13c2e2a7786f6ecd7e0d184f45d718d1ac1a8a63e570466e489dd
EXPECTED: a3e49d843df13c2e2a7786f6ecd7e0d184f45d718d1ac1a8a63e570466e489dd
INPUT: This is a test string to hash
OUTPUT: 05b4ac920d4130cb9d9bb046cac7476f35d7404cf116dc8d6d4a113c3c79d904
EXPECTED: f70b476ff948472f8e4e52793a5a2779e636c20dd5336d3a8a4455374318db35
https://bitbucket.org/Boolsheet/sil/raw/tip/hash.lua
INPUT: Test string
OUTPUT: 8f1a5b37fbe986953c343d5b839b14843c6c29d47a6a7e52f263cd82ad6141a3
EXPECTED: a3e49d843df13c2e2a7786f6ecd7e0d184f45d718d1ac1a8a63e570466e489dd
INPUT: This is a test string to hash
OUTPUT: 167bf7b9000442419b3016a6e1edfcc7c8d40b5f0b80518a31ddb0bbd388e87ac
EXPECTED: f70b476ff948472f8e4e52793a5a2779e636c20dd5336d3a8a4455374318db35
I would recommend against using SHA256 for passwords. They are easy to bruteforce nowadays, and the way you are using them is vulnerable to replay attacks.
Also if you must use SHA256, use the version from OpenSSL if possible (especially if your program already depends on OpenSSL.)
But if you must use it (and cannot link with OpenSSL, but can use FFI) here is a LuaJIT version of SHA256 (only) that I am using in one of my projects.
local bit = require 'bit'
local ffi = require 'ffi'
local type = type
local band, bnot, bswap, bxor, rol, ror, rshift, tobit =
bit.band, bit.bnot, bit.bswap, bit.bxor, bit.rol, bit.ror, bit.rshift, bit.tobit
local min, max = math.min, math.max
local C = ffi.C
local istype, new, fill, copy, cast, sizeof, ffi_string =
ffi.istype, ffi.new, ffi.fill, ffi.copy, ffi.cast, ffi.sizeof, ffi.string
local sha256 = {}
ffi.cdef [[
void *malloc(size_t size);
void free(void *ptr);
]]
local ctHashState = ffi.typeof 'uint32_t[8]'
local cbHashState = ffi.sizeof(ctHashState)
local ctBlock = ffi.typeof 'uint32_t[64]'
local cbBlock = ffi.sizeof(ctBlock)
local ctpu8 = ffi.typeof 'uint8_t *'
local ctpcu8 = ffi.typeof 'const uint8_t *'
local ctpu32 = ffi.typeof 'uint32_t *'
local ctpu64 = ffi.typeof 'uint64_t *'
-- This struct is used by the 'preprocess' iterator function. It keeps track
-- of the end of the input string + the total input length in bits + a pointer
-- to the block buffer (where expansion takes place.)
local ctBlockIter
local cmtBlockIter = {}
function cmtBlockIter.__sub(a, b)
if istype(ctBlockIter, a) then a = a.limit end
if istype(ctBlockIter, b) then b = b.limit end
return a - b
end
function cmtBlockIter:__tostring()
return string.format("<ctBlockIter: limit=%s; keyLength=%s>",
tostring(self.base), tostring(self.keyLength))
end
ctBlockIter = ffi.metatype([[
struct {
const uint8_t *limit;
uint32_t *blockBuffer;
uint64_t keyLength;
}
]], cmtBlockIter)
-- Initial state of the hash
local init_h = new('const uint32_t[8]', {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
})
-- Constants used in the add step of the compression function
local k = new('const uint32_t[64]', {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
})
-- Expand block from 512 to 2048 bits
local function expand(w)
for i = 16, 63 do
local s0 = bxor(ror(w[i-15], 7), ror(w[i-15], 18), rshift(w[i-15], 3))
local s1 = bxor(ror(w[i-2], 17), ror(w[i-2], 19), rshift(w[i-2], 10))
w[i] = w[i-16] + s0 + w[i-7] + s1
end
end
-- Process one expanded block and update the hash state
local function compress(hh, w)
local a, b, c, d, e, f, g, h =
hh[0],hh[1],hh[2],hh[3],hh[4],hh[5],hh[6],hh[7]
for i = 0, 63 do
local S1 = bxor(ror(e, 6), ror(e, 11), ror(e, 25))
local ch = bxor(band(e, f), band(bnot(e), g))
local t = tobit(h + S1 + ch + k[i] + w[i])
local S0 = bxor(ror(a, 2), ror(a, 13), ror(a, 22))
local maj = bxor(band(a, bxor(b, c)), band(b, c))
a, b, c, d, e, f, g, h =
tobit(t + S0 + maj),
a, b, c,
tobit(d + t),
e, f, g
end
hh[0],hh[1],hh[2],hh[3],hh[4],hh[5],hh[6],hh[7] =
hh[0]+a, hh[1]+b, hh[2]+c, hh[3]+d,
hh[4]+e, hh[5]+f, hh[6]+g, hh[7]+h
end
-- Take a 512-bit chunk from the input.
-- If it is the final chunk, also add padding
local keyLengthOfs = ffi.offsetof(ctBlockIter, 'keyLength')
local function nextBlock(state, input)
local w = state.blockBuffer
local cLen = min(state - input, 64)
if cLen < -8 then return nil end
fill(w, 256, 0)
copy(w, input, max(0, cLen))
if 0 <= cLen and cLen < 64 then
copy(cast(ctpu8, w)+cLen, '\128', 1)
end
for i = 0, 15 do w[i] = bswap(w[i]) end
if cLen <= (64-8-1) then
copy(cast(ctpu64, w) + 7, cast(ctpu8, state) + keyLengthOfs, 8)
w[14], w[15] = w[15], w[14]
end
input = input + 64
return input
end
-- Iterator that yields one block (possibly padded) at a time from the input
local function preprocess(input, len, w)
len = len or (type(input) == 'string' and #input or sizeof(input))
input = cast(ctpu8, input)
local it = new(ctBlockIter)
it.blockBuffer = w
it.limit = input+len
it.keyLength = len*8
return nextBlock, it, input
end
-- Compute a binary hash (32-byte binary string) from the input
function sha256.binFromBin(input, len)
local h = new(ctHashState)
local w = cast(ctpu32, C.malloc(cbBlock))
copy(h, init_h, cbHashState)
for _ in preprocess(input, len, w) do
expand(w)
compress(h, w)
end
for i = 0, 7 do h[i] = bswap(h[i]) end
C.free(w)
return ffi_string(h, 32)
end
local hexDigits = new('char[16]', "0123456789abcdef")
local hexOut = new('char[65]')
-- Compute the hash and convert to hexadecimal
function sha256.hexFromBin(input, len)
local h = new(ctHashState)
local w = cast(ctpu32, C.malloc(cbBlock))
copy(h, init_h, cbHashState)
for _ in preprocess(input, len, w) do
expand(w)
compress(h, w)
end
for i = 0, 7 do
local w = h[i]
for j = 0, 3 do
w = rol(w, 8)
hexOut[i*8 + j*2] = hexDigits[band(rshift(w, 4), 15)]
hexOut[i*8 + j*2 + 1] = hexDigits[band(w, 15)]
end
end
C.free(w)
return ffi_string(hexOut, 64)
end
return sha256
There is an implementation of SHA256 at the Lua User's Wiki. The page observes it is Lua 5.2. I would imagine that it would be practical to make that work in LuaJIT without too much trouble.
Do pay attention to the larger security issues surrounding passwords and authentication. The usual advice applies; rolling your own security rather than using an existing tested and supported implementation is not something to be done lightly.
Since you are using LuaJIT, you should be able to leverage its very powerful FFI capabilities to use crypto supplied on your native platform. That will likely require writing some FFI-flavored Lua that is platform specific to each platform on which your client expects to run, but from what I've seen by lurking in the LuaJIT mailing list that shouldn't be too painful.

How do I get the number of keys in a hash table in Lua?

myTable = {}
myTable["foo"] = 12
myTable["bar"] = "blah"
print(#myTable) -- this prints 0
Do I actually have to iterate through the items in the table to get the number of keys?
numItems = 0
for k,v in pairs(myTable) do
numItems = numItems + 1
end
print(numItems) -- this prints 2
I experimented with both the # operator and table.getn(). I thought table.getn() would do what you wanted but as it turns out it's returning the same value as #, namely 0. It appears that dictionaries insert nil placeholders as necessary.
Looping over the keys and counting them seems like the only way to get the dictionary size.
The length operator:
The length of a table t is defined to be any integer index n such that t[n] is not nil and t[n+1] is nil; moreover, if t[1] is nil, n can be zero. For a regular array, with non-nil values from 1 to a given n, its length is exactly that n, the index of its last value. If the array has "holes" (that is, nil values between other non-nil values), then #t can be any of the indices that directly precedes a nil value (that is, it may consider any such nil value as the end of the array).
so only way to get length is iterate over it.
Aside from iterating through the keys manually, it is simple to automatically keep track of it via metamethods. Considering you probably don't want to keep track of every table you make, you can just write a function that will allow you to convert any table into a key-countable object. The following isn't perfect, but I think it would illustrate the point:
function CountedTable(x)
assert(type(x) == 'table', 'bad parameter #1: must be table')
local mt = {}
-- `keys` will represent the number of non integral indexes
-- `indxs` will represent the number of integral indexes
-- `all` will represent the number of both
local keys, indxs, all = 0, 0, 0
-- Do an initial count of current assets in table.
for k, v in pairs(x) do
if (type(k) == 'number') and (k == math.floor(k)) then indxs = indxs + 1
else keys = keys + 1 end
all = all + 1
end
-- By using `__nexindex`, any time a new key is added, it will automatically be
-- tracked.
mt.__newindex = function(t, k, v)
if (type(k) == 'number') and (k == math.floor(k)) then indxs = indxs + 1
else keys = keys + 1 end
all = all + 1
t[k] = v
end
-- This allows us to have fields to access these datacounts, but won't count as
-- actual keys or indexes.
mt.__index = function(t, k)
if k == 'keyCount' then return keys
elseif k == 'indexCount' then return indxs
elseif k == 'totalCount' then return all end
end
return setmetatable(x, mt)
end
Examples of using this would include:
-- Note `36.35433` would NOT be counted as an integral index.
local foo = CountedTable { 1, 2, 3, 4, [36.35433] = 36.35433, [54] = 54 }
local bar = CountedTable { x = 23, y = 43, z = 334, [true] = true }
local foobar = CountedTable { 1, 2, 3, x = 'x', [true] = true, [64] = 64 }
print(foo.indexCount) --> 5
print(bar.keyCount) --> 4
print(foobar.totalCount) --> 6
Live Working Example
Hope this helped! :)
Lua stores table as two separates parts : a hash part and an array part, the len operator only deal with the array part, meaning value indexed by a number value, plus using rules mentioned below, so you don't have any choice for counting "hash" value you need to iterate over the table with pairs() function.

Resources