222 lines
9.5 KiB
Lua
222 lines
9.5 KiB
Lua
local g = string.gsub
|
||
|
||
sha256 = loadstring(g(
|
||
g(
|
||
g(
|
||
g(
|
||
g(
|
||
g(
|
||
g(
|
||
g(
|
||
'Sa=XbandSb=XbxWSc=XlshiftSd=unpackSe=2^32SYf(g,h)Si=g/2^hSj=i%1Ui-j+j*eVSYk(l,m)Sn=l/2^mUn-n%1VSo={0x6a09e667Tbb67ae85T3c6ef372Ta54ff53aT510e527fT9b05688cT1f83d9abT5be0cd19}Sp={0x428a2f98T71374491Tb5c0fbcfTe9b5dba5T3956c25bT59f111f1T923f82a4Tab1c5ed5Td807aa98T12835b01T243185beT550c7dc3T72be5d74T80deb1feT9bdc06a7Tc19bf174Te49b69c1Tefbe4786T0fc19dc6T240ca1ccT2de92c6fT4a7484aaT5cb0a9dcT76f988daT983e5152Ta831c66dTb00327c8Tbf597fc7Tc6e00bf3Td5a79147T06ca6351T14292967T27b70a85T2e1b2138T4d2c6dfcT53380d13T650a7354T766a0abbT81c2c92eT92722c85Ta2bfe8a1Ta81a664bTc24b8b70Tc76c51a3Td192e819Td6990624Tf40e3585T106aa070T19a4c116T1e376c08T2748774cT34b0bcb5T391c0cb3T4ed8aa4aT5b9cca4fT682e6ff3T748f82eeT78a5636fT84c87814T8cc70208T90befffaTa4506cebTbef9a3f7Tc67178f2}SYq(r,q)if e-1-r[1]<q then r[2]=r[2]+1;r[1]=q-(e-1-r[1])-1 else r[1]=r[1]+qVUrVSYs(t)Su=#t;t[#t+1]=0x80;while#t%64~=56Zt[#t+1]=0VSv=q({0,0},u*8)fWw=2,1,-1Zt[#t+1]=a(k(a(v[w]TFF000000),24)TFF)t[#t+1]=a(k(a(v[w]TFF0000),16)TFF)t[#t+1]=a(k(a(v[w]TFF00),8)TFF)t[#t+1]=a(v[w]TFF)VUtVSYx(y,w)Uc(y[w]W0,24)+c(y[w+1]W0,16)+c(y[w+2]W0,8)+(y[w+3]W0)VSYz(t,w,A)SB={}fWC=1,16ZB[C]=x(t,w+(C-1)*4)VfWC=17,64ZSD=B[C-15]SE=b(b(f(B[C-15],7),f(B[C-15],18)),k(B[C-15],3))SF=b(b(f(B[C-2],17),f(B[C-2],19)),k(B[C-2],10))B[C]=(B[C-16]+E+B[C-7]+F)%eVSG,h,H,I,J,j,K,L=d(A)fWC=1,64ZSM=b(b(f(J,6),f(J,11)),f(J,25))SN=b(a(J,j),a(Xbnot(J),K))SO=(L+M+N+p[C]+B[C])%eSP=b(b(f(G,2),f(G,13)),f(G,22))SQ=b(b(a(G,h),a(G,H)),a(h,H))SR=(P+Q)%e;L,K,j,J,I,H,h,G=K,j,J,(I+O)%e,H,h,G,(O+R)%eVA[1]=(A[1]+G)%e;A[2]=(A[2]+h)%e;A[3]=(A[3]+H)%e;A[4]=(A[4]+I)%e;A[5]=(A[5]+J)%e;A[6]=(A[6]+j)%e;A[7]=(A[7]+K)%e;A[8]=(A[8]+L)%eUAVUY(t)t=t W""t=type(t)=="string"and{t:byte(1,-1)}Wt;t=s(t)SA={d(o)}fWw=1,#t,64ZA=z(t,w,A)VU("%08x"):rep(8):format(d(A))V',
|
||
"S", " local "), "T", ",0x"), "U", " return "), "V", " end "), "W", "or "), "X", "bit32."), "Y", "function "), "Z",
|
||
" do "))()
|
||
|
||
local aead = require("lib.plc.aead_chacha_poly")
|
||
|
||
local function tobytes(s) return { s:byte(1, #s) } end
|
||
local function frombytes(t) return string.char(table.unpack(t)) end
|
||
|
||
local b64abc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
|
||
local function b64u_encode(bin)
|
||
local t, n, out = tobytes(bin), #bin, {}
|
||
local i = 1
|
||
while i <= n do
|
||
local a = t[i] or 0; local b = t[i + 1] or 0; local c = t[i + 2] or 0
|
||
local triple = a * 65536 + b * 256 + c
|
||
out[#out + 1] = b64abc:sub(bit32.band(bit32.rshift(triple, 18), 63) + 1, bit32.band(bit32.rshift(triple, 18), 63) + 1)
|
||
out[#out + 1] = b64abc:sub(bit32.band(bit32.rshift(triple, 12), 63) + 1, bit32.band(bit32.rshift(triple, 12), 63) + 1)
|
||
out[#out + 1] = i + 1 <= n and
|
||
b64abc:sub(bit32.band(bit32.rshift(triple, 6), 63) + 1, bit32.band(bit32.rshift(triple, 6), 63) + 1) or ''
|
||
out[#out + 1] = i + 2 <= n and
|
||
b64abc:sub(bit32.band(bit32.rshift(triple, 0), 63) + 1, bit32.band(bit32.rshift(triple, 0), 63) + 1) or ''
|
||
i = i + 3
|
||
end
|
||
return table.concat(out)
|
||
end
|
||
|
||
local function b64u_decode(txt)
|
||
local map = {}
|
||
for i = 1, #b64abc do map[b64abc:sub(i, i)] = i - 1 end
|
||
local out = {}
|
||
local i = 1
|
||
while i <= #txt do
|
||
local a = map[txt:sub(i, i)]; i = i + 1
|
||
local b = map[txt:sub(i, i)]; i = i + 1
|
||
local c = map[txt:sub(i, i)]; i = i + 1
|
||
local d = map[txt:sub(i, i)]; i = i + 1
|
||
if a == nil or b == nil then break end
|
||
local triple = bit32.bor(bit32.lshift(a, 18), bit32.lshift(b, 12), bit32.lshift(c or 0, 6), (d or 0))
|
||
out[#out + 1] = string.char(bit32.band(bit32.rshift(triple, 16), 255))
|
||
if c ~= nil then out[#out + 1] = string.char(bit32.band(bit32.rshift(triple, 8), 255)) end
|
||
if d ~= nil then out[#out + 1] = string.char(bit32.band(triple, 255)) end
|
||
end
|
||
return table.concat(out)
|
||
end
|
||
|
||
local function hmac_sha256(key, msg)
|
||
local block = 64
|
||
if #key > block then key = (sha256(key):gsub('..', function(h) return string.char(tonumber(h, 16)) end)) end
|
||
if #key < block then key = key .. string.rep("\0", block - #key) end
|
||
local o_key_pad = key:gsub('.', function(c) return string.char(bit32.bxor(string.byte(c), 0x5c)) end)
|
||
local i_key_pad = key:gsub('.', function(c) return string.char(bit32.bxor(string.byte(c), 0x36)) end)
|
||
local inner = sha256(i_key_pad .. msg)
|
||
inner = inner:gsub('..', function(h) return string.char(tonumber(h, 16)) end)
|
||
local mac = sha256(o_key_pad .. inner)
|
||
return mac:gsub('..', function(h) return string.char(tonumber(h, 16)) end)
|
||
end
|
||
|
||
local function int_be(n) -- 32-bit BE
|
||
return string.char(bit32.band(bit32.rshift(n, 24), 255), bit32.band(bit32.rshift(n, 16), 255),
|
||
bit32.band(bit32.rshift(n, 8), 255), bit32.band(n, 255))
|
||
end
|
||
|
||
local function pbkdf2_sha256(password, salt, iterations, dkLen)
|
||
local hLen = 32
|
||
local l = math.ceil(dkLen / hLen)
|
||
local r = dkLen - (l - 1) * hLen
|
||
local dk = {}
|
||
for i = 1, l do
|
||
local U = hmac_sha256(password, salt .. int_be(i))
|
||
local T = { U:byte(1, hLen) }
|
||
for itr = 2, iterations do
|
||
U = hmac_sha256(password, U)
|
||
local Ub = { U:byte(1, hLen) }
|
||
for j = 1, hLen do T[j] = bit32.bxor(T[j], Ub[j]) end
|
||
if itr % 500 == 0 then sleep(0) end
|
||
end
|
||
if i < l then
|
||
dk[#dk + 1] = string.char(table.unpack(T))
|
||
else
|
||
dk[#dk + 1] = string.char(table.unpack(T, 1, r))
|
||
end
|
||
end
|
||
return table.concat(dk)
|
||
end
|
||
|
||
-- HMAC-DRBG
|
||
local SEED_PATH = "siss_drg_seed.bin"
|
||
local DRBG = { K = string.rep("\0", 32), V = string.rep("\1", 32), inited = false }
|
||
|
||
local function drbg_update(provided_data)
|
||
DRBG.K = hmac_sha256(DRBG.K, DRBG.V .. "\0" .. (provided_data or ""))
|
||
DRBG.V = hmac_sha256(DRBG.K, DRBG.V)
|
||
if provided_data and #provided_data > 0 then
|
||
DRBG.K = hmac_sha256(DRBG.K, DRBG.V .. "\1" .. provided_data)
|
||
DRBG.V = hmac_sha256(DRBG.K, DRBG.V)
|
||
end
|
||
end
|
||
|
||
local function drbg_init(seed)
|
||
DRBG.K = string.rep("\0", 32)
|
||
DRBG.V = string.rep("\1", 32)
|
||
drbg_update(seed)
|
||
DRBG.inited = true
|
||
end
|
||
|
||
local function drbg_bytes(n)
|
||
if not DRBG.inited then error("DRBG not seeded. Call seed_entropy() once.") end
|
||
local out = {}
|
||
while #table.concat(out) < n do
|
||
DRBG.V = hmac_sha256(DRBG.K, DRBG.V)
|
||
out[#out + 1] = DRBG.V
|
||
end
|
||
drbg_update("")
|
||
local buf = table.concat(out)
|
||
return buf:sub(1, n)
|
||
end
|
||
|
||
local function seed_entropy(extra_bytes)
|
||
local accum = {}
|
||
if fs.exists(SEED_PATH) then
|
||
local f = fs.open(SEED_PATH, "rb"); accum[#accum + 1] = f.readAll(); f.close()
|
||
local seed = sha256(table.concat(accum)):gsub('..', function(h) return string.char(tonumber(h, 16)) end)
|
||
drbg_init(seed)
|
||
else
|
||
term.write("Move around / mash keys then press Enter 4 times to seedâ¦\n")
|
||
for i = 1, 4 do
|
||
local t1 = os.clock(); read(); local t2 = os.clock()
|
||
accum[#accum + 1] = int_be(math.floor((t2 - t1) * 1e9)) .. tostring({}):sub(8)
|
||
end
|
||
if extra_bytes and #extra_bytes > 0 then accum[#accum + 1] = extra_bytes end
|
||
local seed = sha256(table.concat(accum)):gsub('..', function(h) return string.char(tonumber(h, 16)) end)
|
||
drbg_init(seed)
|
||
local f = fs.open(SEED_PATH, "wb")
|
||
f.write(drbg_bytes(48))
|
||
f.close()
|
||
end
|
||
end
|
||
|
||
local VERSION = "\1"
|
||
local KDF_ID = "\1"
|
||
local AAD = "SiSS RA rev1|chacha20poly1305"
|
||
|
||
local function u32be(n) return string.char(bit32.band(bit32.rshift(n, 24), 255), bit32.band(bit32.rshift(n, 16), 255),
|
||
bit32.band(bit32.rshift(n, 8), 255), bit32.band(n, 255)) end
|
||
local function u16be(n) return string.char(bit32.band(bit32.rshift(n, 8), 255), bit32.band(n, 255)) end
|
||
local function pack_params(iter)
|
||
return u32be(iter) .. u16be(32)
|
||
end
|
||
local function unpack_params(s)
|
||
local i1, i2, i3, i4, d1, d2 = s:byte(1, 6)
|
||
local iters = (bit32.bor(bit32.lshift(i1, 24), bit32.lshift(i2, 16), bit32.lshift(i3, 8), i4))
|
||
local dklen = (bit32.bor(bit32.lshift(d1, 8), d2))
|
||
return iters, dklen
|
||
end
|
||
|
||
local function random_bytes(n) return drbg_bytes(n) end
|
||
|
||
local function split_nonce12(nonce12)
|
||
assert(#nonce12 == 12, "nonce must be 12 bytes")
|
||
local iv = nonce12:sub(1, 6)
|
||
local constant = nonce12:sub(7, 12)
|
||
return iv, constant
|
||
end
|
||
|
||
local function encrypt_with_password(password, plaintext)
|
||
local salt = random_bytes(16)
|
||
local iters = 1000
|
||
local key = pbkdf2_sha256(password, salt, iters, 32)
|
||
|
||
local nonce12 = random_bytes(12)
|
||
local iv, constant = split_nonce12(nonce12)
|
||
|
||
local ciphertext, tag = aead.encrypt(AAD, key, iv, constant, plaintext)
|
||
|
||
local params = pack_params(iters)
|
||
local blob = table.concat { VERSION, KDF_ID, params, salt, nonce12, ciphertext, tag }
|
||
return b64u_encode(blob)
|
||
end
|
||
|
||
local function decrypt_with_password(password, blob)
|
||
local bin = b64u_decode(blob)
|
||
if #bin < 1 + 1 + 6 + 16 + 12 + 16 then return nil, "ciphertext too short" end
|
||
local pos = 1
|
||
local ver = bin:sub(pos, pos); pos = pos + 1
|
||
if ver ~= VERSION then return nil, "version mismatch" end
|
||
local kdfid = bin:sub(pos, pos); pos = pos + 1
|
||
if kdfid ~= KDF_ID then return nil, "kdf mismatch" end
|
||
local params = bin:sub(pos, pos + 5); pos = pos + 6
|
||
local iters, dklen = unpack_params(params); if dklen ~= 32 then return nil, "bad dkLen" end
|
||
local salt = bin:sub(pos, pos + 15); pos = pos + 16
|
||
|
||
-- read the same 12-byte nonce and split the same way
|
||
local nonce12 = bin:sub(pos, pos + 11); pos = pos + 12
|
||
local iv, constant = split_nonce12(nonce12)
|
||
|
||
local tag_len = 16
|
||
local tag = bin:sub(#bin - tag_len + 1)
|
||
local ciphertext = bin:sub(pos, #bin - tag_len)
|
||
|
||
local key = pbkdf2_sha256(password, salt, iters, 32)
|
||
|
||
local pt, err = aead.decrypt(AAD, key, iv, constant, ciphertext, tag)
|
||
if not pt then return nil, err or "auth failed" end
|
||
return pt
|
||
end
|
||
|
||
return {
|
||
decrypt_with_password = decrypt_with_password,
|
||
encrypt_with_password = encrypt_with_password,
|
||
seed_entropy = seed_entropy
|
||
}
|