-- Lua uses at least 8 bytes to represent a number -- Base64 minimum processable unit is represented by four 6-bit chunks which is 24-bits => 3 bytes -- Hence we can safely use bit operations on number type for multiplexing and extraction -- Define function to extract 8-bit ASCII char from 24-bit base64 unit local function extractChar(unit, idx) -- Skip mask for first ASCII char since shifting will destroy other two parts if idx == 2 then local charCode = (unit) >> (8 * idx) return string.char(charCode) end -- Extract specific 8-bit local mask = (2^8 - 1) << (8 * idx) -- Move 8-bit to the beginning so it becomes a char code local charCode = (unit & mask) >> (8 * idx) -- Convert to string return string.char(charCode) end -- Define mapping of base64 character to number local mapping = { A=0, B=1, C=2, D=3, E=4, F=5, G=6, H=7, I=8, J=9, K=10, L=11, M=12, N=13, O=14, P=15, Q=16, R=17, S=18, T=19, U=20, V=21, W=22, X=23, Y=24, Z=25, a=26, b=27, c=28, d=29, e=30, f=31, g=32, h=33, i=34, j=35, k=36, l=37, m=38, n=39, o=40, p=41, q=42, r=43, s=44, t=45, u=46, v=47, w=48, x=49, y=50, z=51, ["0"]=52, ["1"]=53, ["2"]=54, ["3"]=55, ["4"]=56, ["5"]=57, ["6"]=58, ["7"]=59, ["8"]=60, ["9"]=61, ["+"]=62, ["/"]=63 } local padding = "=" local missing = "" local function atob(encodedString) -- Get four 6-bit chunks which forms one unit -- There must be at least two 6-bit chunks to cover at least one 8-bit chunk local decodedString = encodedString:gsub("(.)(.)(.?)(.?)", function(c1, c2, c3, c4) -- Convert each 6-bit chunk to number local b1 = mapping[c1] --24,23,22,21,20,19 local b2 = mapping[c2] --18,17,16,15,14,13 local b3 = mapping[c3] --12,11,10,09,08,07 local b4 = mapping[c4] --06,05,04,03,02,01 -- Set optional chunks to 0 to be able to multiplex if b3 == nil then b3 = 0 end if b4 == nil then b4 = 0 end -- Multiplex onto 24-bit number local muxedUnit = (b1 << (6 * 3)) + (b2 << (6 * 2)) + (b3 << (6 * 1)) + (b4 << (6 * 0)) -- Extract 8-bit chunks -- Use empty string if unit is not full or it is padded -- and handle A base64 characters at the end of encodedString local r1 = extractChar(muxedUnit, 2) -- if c3 is padding or missing then we can skip r2 local r2 = (c3 == padding or c3 == missing) and "" or extractChar(muxedUnit, 1) -- if c4 is padding or missing then we can skip r3 local r3 = (c4 == padding or c4 == missing) and "" or extractChar(muxedUnit, 0) -- Concatanate return r1..r2..r3 end) return decodedString end return atob -- Test plan assert(atob("bGlnaHQgd28=") == "light wo", "full units, single padding") assert(atob("bGlnaHQgd28A") == "light wo\x00", "full units, explicit A") assert(atob("bGlnaHQgd28") == "light wo", "2/3 of unit") assert(atob("bGlnaHQgdw==") == "light w", "full unit, double padding") assert(atob("bGlnaHQgdw") == "light w", "1/3 of unit") assert(atob("bGlnaHQgd28=") == "light wo", "full units, single padding") assert(atob("bGlnaHQgd29y") == "light wor", "full units, no padding")