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] 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 i0 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 }