diff options
| author | Frank Denis <github@pureftpd.org> | 2020-10-09 14:33:16 +0200 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2020-10-15 20:57:16 -0400 |
| commit | 51a3d0603c116d99c0a93dd451a69c79dd0cbca2 (patch) | |
| tree | fde3b301f1e668a35ebf62fd5fc809b41a4a9790 /lib/std | |
| parent | f701459f04e24a46faf3edc5121f81fbf132f436 (diff) | |
| download | zig-51a3d0603c116d99c0a93dd451a69c79dd0cbca2.tar.gz zig-51a3d0603c116d99c0a93dd451a69c79dd0cbca2.zip | |
std.rand: set DefaultCsprng to Gimli, and require a larger seed
`DefaultCsprng` is documented as a cryptographically secure RNG.
While `ISAAC` is a CSPRNG, the variant we have, `ISAAC64` is not.
A 64 bit seed is a bit small to satisfy that claim.
We also saw it being used with the current date as a seed, that
also defeats the point of a CSPRNG.
Set `DefaultCsprng` to `Gimli` instead of `ISAAC64`, rename
the parameter from `init_s` to `secret_seed` + add a comment to
clarify what kind of seed is expected here.
Instead of directly touching the internals of the Gimli implementation
(which can change/be architecture-specific), add an `init()` function
to the state.
Our Gimli-based CSPRNG was also not backtracking resistant. Gimli
is a permutation; it can be reverted. So, if the state was ever leaked,
future secrets, but also all the previously generated ones could be
recovered. Clear the rate after a squeeze in order to prevent this.
Finally, a dumb test was added just to exercise `DefaultCsprng` since
we don't use it anywhere.
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/crypto/gimli.zig | 9 | ||||
| -rw-r--r-- | lib/std/rand.zig | 36 |
2 files changed, 35 insertions, 10 deletions
diff --git a/lib/std/crypto/gimli.zig b/lib/std/crypto/gimli.zig index 10e8a7dff0..07cbb5d40b 100644 --- a/lib/std/crypto/gimli.zig +++ b/lib/std/crypto/gimli.zig @@ -28,6 +28,15 @@ pub const State = struct { const Self = @This(); + pub fn init(initial_state: [State.BLOCKBYTES]u8) Self { + var data: [BLOCKBYTES / 4]u32 = undefined; + var i: usize = 0; + while (i < State.BLOCKBYTES) : (i += 4) { + data[i / 4] = mem.readIntLittle(u32, initial_state[i..][0..4]); + } + return Self{ .data = data }; + } + /// TODO follow the span() convention instead of having this and `toSliceConst` pub fn toSlice(self: *Self) []u8 { return mem.sliceAsBytes(self.data[0..]); diff --git a/lib/std/rand.zig b/lib/std/rand.zig index cad76f2da5..04de23c886 100644 --- a/lib/std/rand.zig +++ b/lib/std/rand.zig @@ -34,7 +34,7 @@ const maxInt = std.math.maxInt; pub const DefaultPrng = Xoroshiro128; /// Cryptographically secure random numbers. -pub const DefaultCsprng = Isaac64; +pub const DefaultCsprng = Gimli; pub const Random = struct { fillFn: fn (r: *Random, buf: []u8) void, @@ -749,29 +749,35 @@ pub const Gimli = struct { random: Random, state: std.crypto.core.Gimli, - pub fn init(init_s: u64) Gimli { + pub const secret_seed_length = 32; + + /// The seed must be uniform, secret and `secret_seed_length` bytes long. + /// It can be generated using `std.crypto.randomBytes()`. + pub fn init(secret_seed: [secret_seed_length]u8) Gimli { + var initial_state: [std.crypto.core.Gimli.BLOCKBYTES]u8 = undefined; + mem.copy(u8, initial_state[0..secret_seed_length], &secret_seed); + mem.set(u8, initial_state[secret_seed_length..], 0); var self = Gimli{ .random = Random{ .fillFn = fill }, - .state = std.crypto.core.Gimli{ - .data = [_]u32{0} ** (std.crypto.gimli.State.BLOCKBYTES / 4), - }, + .state = std.crypto.core.Gimli.init(initial_state), }; - self.state.data[0] = @truncate(u32, init_s >> 32); - self.state.data[1] = @truncate(u32, init_s); return self; } fn fill(r: *Random, buf: []u8) void { const self = @fieldParentPtr(Gimli, "random", r); - self.state.squeeze(buf); + if (buf.len != 0) { + self.state.squeeze(buf); + } else { + self.state.permute(); + } + mem.set(u8, self.state.toSlice()[0..std.crypto.core.Gimli.RATE], 0); } }; // ISAAC64 - http://www.burtleburtle.net/bob/rand/isaacafa.html // -// CSPRNG -// // Follows the general idea of the implementation from here with a few shortcuts. // https://doc.rust-lang.org/rand/src/rand/prng/isaac64.rs.html pub const Isaac64 = struct { @@ -1139,6 +1145,16 @@ fn testRangeBias(r: *Random, start: i8, end: i8, biased: bool) void { } } +test "CSPRNG" { + var secret_seed: [DefaultCsprng.secret_seed_length]u8 = undefined; + try std.crypto.randomBytes(&secret_seed); + var csprng = DefaultCsprng.init(secret_seed); + const a = csprng.random.int(u64); + const b = csprng.random.int(u64); + const c = csprng.random.int(u64); + assert(a ^ b ^ c != 0); +} + test "" { std.testing.refAllDecls(@This()); } |
