|
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 |
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
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)