Introduction
Module was originally written by Egor Skriptunoff and distributed under an MIT license.
It can be found here: pure_lua_SHA/sha2.lua at master · Egor-Skriptunoff/pure_lua_SHA · GitHub
That version was around 3000 lines long, and supported Lua versions 5.1, 5.2, 5.3, 5.4, and LuaJIT.
Although that is super cool, Roblox only uses Lua 5.1, so that was extreme overkill.
I worked (with @howmanysmaII’s guidance) to port it to Roblox in a way that doesn’t overcomplicate it with support of unreachable cases. then, howmanysmall came in to do some final optimizations that really squeeze out the most performance possible.
After quite a bit of work and benchmarking, this is what we were left with.
Enjoy!
Features
This module contains functions to calculate SHA digest:
- MD5, SHA-1,
- SHA-224, SHA-256, SHA-512/224, SHA-512/256, SHA-384, SHA-512,
- SHA3-224, SHA3-256, SHA3-384, SHA3-512, SHAKE128, SHAKE256,
- HMAC
Additionally, it has a few extra utility functions:
- hex_to_bin
- base64_to_bin
- bin_to_base64
Usage
Input data should be a string
Result (SHA digest) is returned in hexadecimal representation as a string of lowercase hex digits.
Simplest usage example:
local HashLib = require(script.HashLib)
local your_hash = HashLib.sha256("your string")
API
HashLib.md5
HashLib.sha1
SHA2 hash functions:
HashLib.sha224
HashLib.sha256
HashLib.sha512_224
HashLib.sha512_256
HashLib.sha384
HashLib.sha512
SHA3 hash functions:
HashLib.sha3_224
HashLib.sha3_256
HashLib.sha3_384
HashLib.sha3_512
HashLib.shake128
HashLib.shake256
Misc utilities:
HashLib.hmac (Applicable to any hash function from this module except SHAKE*)
HashLib.hex_to_bin
HashLib.base64_to_bin
HashLib.bin_to_base64
Benchmarking and Comparison
For the benchmarking test, I could only do 350 hashes per test, because with higher amounts the unoptimized version caused a script timeout. So, that’s a sign that I did a really good job.
Benchmark Script
--[=[
Messy code to run some tests
Note that CPU usage goes through the roof while doing this
--]=]
local HashLib = require(script.HashLib)
local HashLib_Unoptimized = require(script.HashLib_Unoptimized)
local ipairs = ipairs
local TestAmount = 350
-------------------------------------------------------------------------------------------------
local function TestCompare(FunctionName, Arg)
wait(0.1)
print("\n### Testing",FunctionName)
-- test my version
local Times = {}
for i=1, TestAmount do
local t = tick()
local hash = HashLib[FunctionName](Arg)
local TimeTaken = tick()-t
Times[#Times+1] = TimeTaken
end
local Total = 0
for _,Time in ipairs(Times) do
Total = Total+Time
end
print("* Optimized HashLib took `".. string.format("%.10f", Total).. "` to do ".. TestAmount .." "..FunctionName.." hashes with each one taking `".. string.format("%.10f", Total/#Times) .. "` on average")
-- test original
local UnoptTimes = {}
for i=1, TestAmount do
local t = tick()
local hash = HashLib_Unoptimized[FunctionName]("This is my input")
UnoptTimes[#UnoptTimes+1] = tick()-t
end
local UnoptTotal = 0
for _,UnoptTime in ipairs(UnoptTimes) do
UnoptTotal = UnoptTotal+UnoptTime
end
print("* Unoptimized HashLib took `".. string.format("%.10f", UnoptTotal).. "` to do ".. TestAmount .." "..FunctionName.." hashes with each one taking `".. string.format("%.10f", UnoptTotal/#UnoptTimes) .. "` on average")
end
wait(2) -- Let game state finish initializing before we eat CPU
TestCompare("md5", "hash this string please")
TestCompare("sha1", "hash this string please")
TestCompare("sha224", "hash this string please")
TestCompare("sha256", "hash this string please")
TestCompare("sha512_224", "hash this string please")
TestCompare("sha512_256", "hash this string please")
TestCompare("sha384", "hash this string please")
TestCompare("sha512", "hash this string please")
TestCompare("sha3_224", "hash this string please")
TestCompare("sha3_256", "hash this string please")
TestCompare("sha3_384", "hash this string please")
TestCompare("sha3_512", "hash this string please")
Benchmark Results
Testing md5
- Optimized HashLib took
0.0990054607
to do 350 md5 hashes with each one taking0.0002828727
on average - Unoptimized HashLib took
0.4256963730
to do 350 md5 hashes with each one taking0.0012162754
on average
Testing sha1
- Optimized HashLib took
0.1464135647
to do 350 sha1 hashes with each one taking0.0004183245
on average - Unoptimized HashLib took
1.1473040581
to do 350 sha1 hashes with each one taking0.0032780116
on average
Testing sha224
- Optimized HashLib took
0.2236335278
to do 350 sha224 hashes with each one taking0.0006389529
on average - Unoptimized HashLib took
2.3714523315
to do 350 sha224 hashes with each one taking0.0067755781
on average
Testing sha256
- Optimized HashLib took
0.2243692875
to do 350 sha256 hashes with each one taking0.0006410551
on average - Unoptimized HashLib took
2.3787267208
to do 350 sha256 hashes with each one taking0.0067963621
on average
Testing sha512_224
- Optimized HashLib took
0.7043654919
to do 350 sha512_224 hashes with each one taking0.0020124728
on average - Unoptimized HashLib took
6.3255631924
to do 350 sha512_224 hashes with each one taking0.0180730377
on average
Testing sha512_256
- Optimized HashLib took
0.7125756741
to do 350 sha512_256 hashes with each one taking0.0020359305
on average - Unoptimized HashLib took
6.3216791153
to do 350 sha512_256 hashes with each one taking0.0180619403
on average
Testing sha384
- Optimized HashLib took
0.7102406025
to do 350 sha384 hashes with each one taking0.0020292589
on average - Unoptimized HashLib took
6.3122458458
to do 350 sha384 hashes with each one taking0.0180349881
on average
Testing sha512
- Optimized HashLib took
0.7212450504
to do 350 sha512 hashes with each one taking0.0020607001
on average - Unoptimized HashLib took
6.3489837646
to do 350 sha512 hashes with each one taking0.0181399536
on average
Testing sha3_224
- Optimized HashLib took
0.8299937248
to do 350 sha3_224 hashes with each one taking0.0023714106
on average - Unoptimized HashLib took
13.0032684803
to do 350 sha3_224 hashes with each one taking0.0371521957
on average
Testing sha3_256
- Optimized HashLib took
0.8421859741
to do 350 sha3_256 hashes with each one taking0.0024062456
on average - Unoptimized HashLib took
12.9854648113
to do 350 sha3_256 hashes with each one taking0.0371013280
on average
Testing sha3_384
- Optimized HashLib took
0.8211791515
to do 350 sha3_384 hashes with each one taking0.0023462261
on average - Unoptimized HashLib took
12.9778954983
to do 350 sha3_384 hashes with each one taking0.0370797014
on average
Testing sha3_512
- Optimized HashLib took
0.8204553127
to do 350 sha3_512 hashes with each one taking0.0023441580
on average - Unoptimized HashLib took
12.8966257572
to do 350 sha3_512 hashes with each one taking0.0368475022
on average
The optimized version is many times faster in almost all cases.