Skip to content

Instantly share code, notes, and snippets.

@metatablecat
Last active November 22, 2025 14:27
Show Gist options
  • Select an option

  • Save metatablecat/1f6cd6f4495f95700eb1a686de4ebe5e to your computer and use it in GitHub Desktop.

Select an option

Save metatablecat/1f6cd6f4495f95700eb1a686de4ebe5e to your computer and use it in GitHub Desktop.
A Lua Base64 Impl based around strings.
local SEQ = {
[0] = "A", "B", "C", "D", "E", "F", "G", "H",
"I", "J", "K", "L", "M", "N", "O", "P",
"Q", "R", "S", "T", "U", "V", "W", "X",
"Y", "Z", "a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n",
"o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "0", "1", "2", "3",
"4", "5", "6", "7", "8", "9", "+", "/",
}
local STRING_FAST = {}
local INDEX = {[61] = 0, [65] = 0}
for key, val in ipairs(SEQ) do
-- memoization
INDEX[string.byte(val)] = key
end
-- string.char has a MASSIVE overhead, its faster to precompute
-- the values for performance
for i = 0, 255 do
local c = string.char(i)
STRING_FAST[i] = c
end
local b64 = {}
function b64.encode(str)
local len = string.len(str)
local output = table.create(math.ceil(len/4)*4)
local index = 1
for i = 1, len, 3 do
local b0, b1, b2 = string.byte(str, i, i + 2)
local b = bit32.lshift(b0, 16) + bit32.lshift(b1 or 0, 8) + (b2 or 0)
output[index] = SEQ[bit32.extract(b, 18, 6)]
output[index + 1] = SEQ[bit32.extract(b, 12, 6)]
output[index + 2] = b1 and SEQ[bit32.extract(b, 6, 6)] or "="
output[index + 3] = b2 and SEQ[bit32.band(b, 63)] or "="
index += 4
end
return table.concat(output)
end
function b64.decode(hash)
-- given a 24 bit word (4 6-bit letters), decode 3 bytes from it
local len = string.len(hash)
local output = table.create(len * 0.75)
local index = 1
for i = 1, len, 4 do
local c0, c1, c2, c3 = string.byte(hash, i, i + 3)
local b =
bit32.lshift(INDEX[c0], 18)
+ bit32.lshift(INDEX[c1], 12)
+ bit32.lshift(INDEX[c2], 6)
+ (INDEX[c3])
output[index] = STRING_FAST[bit32.extract(b, 16, 8)]
output[index + 1] = c2 ~= "=" and STRING_FAST[bit32.extract(b, 8, 8)] or "="
output[index + 2] = c3 ~= "=" and STRING_FAST[bit32.band(b, 0xFF)] or "="
index += 3
end
return table.concat(output)
end
return b64

NOTE: This would probably be a lot faster if it relied on buffer, rather than strings.q

The following benchmark was compared against the Base64 library available here using Boatbomber's plugin

Data was tested with 100,000 char strings

decode encode AGF decode AGF encode
10th % 2.751ms 4.755ms 5.185ms 7.554ms
50th % 2.997ms 5.123ms 7.474ms 9.015ms
90th % 3.482ms 5.864ms 8.847ms 10.030ms
Avg 8.934ms 11.329ms 27.008ms 20.659ms
Min 2.519ms 4.468ms 4.417ms 6.623ms
Max 15.348ms 18.191ms 49.599ms 34.694ms
Average bytes per second 111,931,945.377 88,269,044.046 37,026,066.350 48,405,053.487
@phoriah
Copy link

phoriah commented Nov 17, 2025

@daily3014 Can you provide the benchmark code? (I have the plugin)
Because I'm getting different results
(both tested on --!native & --!optimize 2)
image

tested on string.rep("\1\0\0\0\1\2\3\4\5\6\7", 50) for 1e3 iterations

image

tested on string.rep("\1\0\0\0\1\2\3\4\5\6", 1e4) (100k) for 2 iterations

both modules called with a string by converting it to buffer then back to string after encoding

local rbxb64 = require(workspace.Base64).Encode
local reselimb64 = require(workspace.Reselim).encode
local rbxcrypt = function(str)
	return buffer.tostring(rbxb64(buffer.fromstring(str)))
end
local reselim = function(str)
	return buffer.tostring(reselimb64(buffer.fromstring(str)))
end

@daily3014
Copy link

daily3014 commented Nov 19, 2025

@phoriah

Edit: Sorry for the confusion, there was an update to our Base64 that seemed to have slowed it down
grafik
Base64 = Latest version of rbx-cryptography
Reselim = Reselim's
Base64 Old = daily3014/rbx-cryptography@2861d51

Last edit: We fixed the issue (available in v2.5.5), additionally we managed to shave off another 0.100ms off the old one
grafik

@metatablecat
Copy link
Author

metatablecat commented Nov 20, 2025

I think an important note to add here is this is using strings, since its targeting Lua 5.1, not Luau, which will naturally always be slower than a buffer approach. There isn't a really point in using Luau-written Base64 libs now that EncodingService is a thing, which can take advantage of not being bound to a VM.

When I wrote this I was comparing it against the AGF/Knit Base64 library which is hilariously slow so /shrug.

It definitely isn't the fastest algorithm anymore so I'll remove that.

@phoriah
Copy link

phoriah commented Nov 20, 2025

EncodingService beats everything by a mile (as seen on daily's benchmark, the purple Roblox entry). Since it's written in c++ and has minimal overhead

@daily3014
Copy link

I think an important note to add here is this is using strings, since its targeting Lua 5.1, not Luau, which will naturally always be slower than a buffer approach. There isn't a really point in using Luau-written Base64 libs now that EncodingService is a thing, which can take advantage of not being bound to a VM.

When I wrote this I was comparing it against the AGF/Knit Base64 library which is hilariously slow so /shrug.

It definitely isn't the fastest algorithm anymore so I'll remove that.

EncodingService is faster, but lacks a ton of "features", such as the Blake3 hash having a fixed size, you have to implement your own DigestKeyed and DeriveKey, and funnily enough EncodingService becomes slower than our luau implementation as the hash size grows

grafik (Looped 128 times to simulate a 32*128 byte hash)

For example, rbx-cryptography uses Blake3 to extend the ChaCha20 output since it's faster to call Blake3 once with the wanted output size than to repeatedly call ChaCha20

On the topic of Base64, our latest impl comes pretty close to EncodingService (not available in rbx-cryptography yet)
(Benched on Intel I7-12700, 'working' is the newest one)
grafik

@phoriah
Copy link

phoriah commented Nov 22, 2025

I only meant base64-wise, but yes I get what you mean.
Would be cool to see if it's possible to beat it with a luau implementation.

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