Improve random byte generator

This commit is contained in:
Miguel Oliveira 2022-12-13 18:51:38 -03:00
parent 7d45646aa0
commit 0af58b5e2d

View file

@ -1,3 +1,4 @@
local expect = require "cc.expect".expect
local blake3 = require "ccryptolib.blake3" local blake3 = require "ccryptolib.blake3"
local chacha20 = require "ccryptolib.chacha20" local chacha20 = require "ccryptolib.chacha20"
local packing = require "ccryptolib.internal.packing" local packing = require "ccryptolib.internal.packing"
@ -6,6 +7,7 @@ local u1x4, fmt1x4 = packing.compileUnpack("<I4")
-- Initialize from local context. -- Initialize from local context.
local ctx = { local ctx = {
"ccryptolib 2022-03-05T08:50:36Z random.lua initialization context",
os.epoch("utc"), os.epoch("utc"),
math.random(0, 2 ^ 24 - 1), math.random(0, 2 ^ 24 - 1),
math.random(0, 2 ^ 24 - 1), math.random(0, 2 ^ 24 - 1),
@ -13,28 +15,73 @@ local ctx = {
tostring({}), tostring({}),
} }
local state = blake3.digest(table.concat(ctx, "|"), 32) local state = blake3.digest(table.concat(ctx, "|"))
local accumulator = {}
local accumulatorLen = 0
local function seed(data) --- Adds data to the accumulator without context.
state = blake3.digestKeyed(state, data, 32) --
end -- @tparam string data The input bytes.
--
local function reseed(data)
local acc = accumulator
local len = accumulatorLen
local function stir(n) -- Append to the accumulator.
-- Collect samples from jitter. acc[#acc + 1] = data
local epoch = os.epoch len = len + #data
local acc = {}
local byte = 0 if len < 64 then
for i = 1, n do accumulatorLen = len
local t0 = epoch("utc") return
repeat byte = byte + 1 until epoch("utc") ~= t0
acc[i] = byte % 256
end end
-- Extract into the new state. -- Concatenate.
seed(string.char(table.unpack(acc))) local cat = table.concat(acc)
-- Align by 64-byte block.
local rlen = len % 64
local blen = len - rlen
-- Digest.
state = blake3.digestKeyed(state, cat:sub(1, blen))
accumulator = {cat:sub(blen + 1)}
accumulatorLen = rlen
end end
local function random(len) do -- Load entropy from disk.
local file = fs.open("/.random", "rb")
if file then
reseed(file.read(32) or "")
file.close()
end
end
local mod = {}
--- Adds entropy into the generator state.
--
-- @tparam string data The entropy data.
--
function mod.reseed(data)
expect(1, data, "string")
reseed(data)
end
--- Adds entropy from sampling system noise.
--
-- @tparam number n The number of iterations to spend extracting entropy.
--
function mod.stir(n)
expect(1, n, "number")
error("TODO") -- TODO
end
--- Generates random bytes.
--
-- @tparam number len The desired output length.
--
function mod.random(len)
local msg = ("\0"):rep(len + 32) local msg = ("\0"):rep(len + 32)
local nonce = ("\0"):rep(12) local nonce = ("\0"):rep(12)
local out = chacha20.crypt(state, nonce, msg, 8, 0) local out = chacha20.crypt(state, nonce, msg, 8, 0)
@ -42,28 +89,26 @@ local function random(len)
return out:sub(33) return out:sub(33)
end end
local function save() local random = mod.random
--- Saves the state to the filesystem.
--
-- This potentially adds security when starting the generator from boot. The
-- saved path is fixed and located at `/.random`.
--
function mod.save()
local file = fs.open("/.random", "wb") local file = fs.open("/.random", "wb")
file.write(random(32)) file.write(random(32))
file.close() file.close()
end end
-- Load.
if fs.exists("/.random") then
local file = fs.open("/.random", "rb")
seed(file.read(32) or "")
end
-- Add extra entropy. -- Add extra entropy.
stir(512) mod.stir(error("TODO")) -- TODO
-- Save. -- Save.
math.randomseed(u1x4(fmt1x4, random(4), 1)) mod.save()
save()
return { -- Regenerate the math.random seed.
seed = seed, math.randomseed(u1x4(fmt1x4, random(4), 1))
stir = stir,
random = random, return mod
save = save,
}