Improve random byte generator
This commit is contained in:
parent
7d45646aa0
commit
0af58b5e2d
107
random.lua
107
random.lua
|
@ -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)
|
--
|
||||||
|
-- @tparam string data The input bytes.
|
||||||
|
--
|
||||||
|
local function reseed(data)
|
||||||
|
local acc = accumulator
|
||||||
|
local len = accumulatorLen
|
||||||
|
|
||||||
|
-- Append to the accumulator.
|
||||||
|
acc[#acc + 1] = data
|
||||||
|
len = len + #data
|
||||||
|
|
||||||
|
if len < 64 then
|
||||||
|
accumulatorLen = len
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local function stir(n)
|
-- Concatenate.
|
||||||
-- Collect samples from jitter.
|
local cat = table.concat(acc)
|
||||||
local epoch = os.epoch
|
|
||||||
local acc = {}
|
-- Align by 64-byte block.
|
||||||
local byte = 0
|
local rlen = len % 64
|
||||||
for i = 1, n do
|
local blen = len - rlen
|
||||||
local t0 = epoch("utc")
|
|
||||||
repeat byte = byte + 1 until epoch("utc") ~= t0
|
-- Digest.
|
||||||
acc[i] = byte % 256
|
state = blake3.digestKeyed(state, cat:sub(1, blen))
|
||||||
|
accumulator = {cat:sub(blen + 1)}
|
||||||
|
accumulatorLen = rlen
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Extract into the new state.
|
do -- Load entropy from disk.
|
||||||
seed(string.char(table.unpack(acc)))
|
local file = fs.open("/.random", "rb")
|
||||||
|
if file then
|
||||||
|
reseed(file.read(32) or "")
|
||||||
|
file.close()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function random(len)
|
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,
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue