Commit f62e3b8c0d
Changed files (5)
lib
std
lib/std/crypto/ascon.zig
@@ -0,0 +1,227 @@
+//! Ascon is a 320-bit permutation, selected as new standard for lightweight cryptography
+//! in the NIST Lightweight Cryptography competition (2019–2023).
+//! https://csrc.nist.gov/News/2023/lightweight-cryptography-nist-selects-ascon
+//!
+//! The permutation is compact, and optimized for timing and side channel resistance,
+//! making it a good choice for embedded applications.
+//!
+//! It is not meant to be used directly, but as a building block for symmetric cryptography.
+
+const std = @import("std");
+const builtin = std.builtin;
+const debug = std.debug;
+const mem = std.mem;
+const testing = std.testing;
+const rotr = std.math.rotr;
+
+/// An Ascon state.
+///
+/// The state is represented as 5 64-bit words.
+///
+/// The NIST submission (v1.2) serializes these words as big-endian,
+/// but software implementations are free to use native endianness.
+pub fn State(comptime endian: builtin.Endian) type {
+ return struct {
+ const Self = @This();
+
+ /// Number of bytes in the state.
+ pub const block_bytes = 40;
+
+ const Block = [5]u64;
+
+ st: Block,
+
+ /// Initialize the state from a slice of bytes.
+ pub fn init(initial_state: [block_bytes]u8) Self {
+ var state = Self{ .st = undefined };
+ mem.copy(u8, state.asBytes(), &initial_state);
+ state.endianSwap();
+ return state;
+ }
+
+ /// Initialize the state from u64 words in native endianness.
+ pub fn initFromWords(initial_state: [5]u64) Self {
+ var state = Self{ .st = initial_state };
+ return state;
+ }
+
+ /// Initialize the state for Ascon XOF
+ pub fn initXof() Self {
+ return Self{ .st = Block{
+ 0xb57e273b814cd416,
+ 0x2b51042562ae2420,
+ 0x66a3a7768ddf2218,
+ 0x5aad0a7a8153650c,
+ 0x4f3e0e32539493b6,
+ } };
+ }
+
+ /// Initialize the state for Ascon XOFa
+ pub fn initXofA() Self {
+ return Self{ .st = Block{
+ 0x44906568b77b9832,
+ 0xcd8d6cae53455532,
+ 0xf7b5212756422129,
+ 0x246885e1de0d225b,
+ 0xa8cb5ce33449973f,
+ } };
+ }
+
+ /// A representation of the state as bytes. The byte order is architecture-dependent.
+ pub fn asBytes(self: *Self) *[block_bytes]u8 {
+ return mem.asBytes(&self.st);
+ }
+
+ /// Byte-swap the entire state if the architecture doesn't match the required endianness.
+ pub fn endianSwap(self: *Self) void {
+ for (self.st) |*w| {
+ w.* = mem.toNative(u64, w.*, endian);
+ }
+ }
+
+ /// Set bytes starting at the beginning of the state.
+ pub fn setBytes(self: *Self, bytes: []const u8) void {
+ var i: usize = 0;
+ while (i + 8 <= bytes.len) : (i += 8) {
+ self.st[i / 8] = mem.readInt(u64, bytes[i..][0..8], endian);
+ }
+ if (i < bytes.len) {
+ var padded = [_]u8{0} ** 8;
+ mem.copy(u8, padded[0 .. bytes.len - i], bytes[i..]);
+ self.st[i / 8] = mem.readInt(u64, padded[0..], endian);
+ }
+ }
+
+ /// XOR a byte into the state at a given offset.
+ pub fn addByte(self: *Self, byte: u8, offset: usize) void {
+ const z = switch (endian) {
+ .Big => 64 - 8 - 8 * @truncate(u6, offset % 8),
+ .Little => 8 * @truncate(u6, offset % 8),
+ };
+ self.st[offset / 8] ^= @as(u64, byte) << z;
+ }
+
+ /// XOR bytes into the beginning of the state.
+ pub fn addBytes(self: *Self, bytes: []const u8) void {
+ var i: usize = 0;
+ while (i + 8 <= bytes.len) : (i += 8) {
+ self.st[i / 8] ^= mem.readInt(u64, bytes[i..][0..8], endian);
+ }
+ if (i < bytes.len) {
+ var padded = [_]u8{0} ** 8;
+ mem.copy(u8, padded[0 .. bytes.len - i], bytes[i..]);
+ self.st[i / 8] ^= mem.readInt(u64, padded[0..], endian);
+ }
+ }
+
+ /// Extract the first bytes of the state.
+ pub fn extractBytes(self: *Self, out: []u8) void {
+ var i: usize = 0;
+ while (i + 8 <= out.len) : (i += 8) {
+ mem.writeInt(u64, out[i..][0..8], self.st[i / 8], endian);
+ }
+ if (i < out.len) {
+ var padded = [_]u8{0} ** 8;
+ mem.writeInt(u64, padded[0..], self.st[i / 8], endian);
+ mem.copy(u8, out[i..], padded[0 .. out.len - i]);
+ }
+ }
+
+ /// XOR the first bytes of the state into a slice of bytes.
+ pub fn xorBytes(self: *Self, out: []u8, in: []const u8) void {
+ debug.assert(out.len == in.len);
+
+ var i: usize = 0;
+ while (i + 8 <= in.len) : (i += 8) {
+ const x = mem.readIntNative(u64, in[i..][0..8]) ^ mem.nativeTo(u64, self.st[i / 8], endian);
+ mem.writeIntNative(u64, out[i..][0..8], x);
+ }
+ if (i < in.len) {
+ var padded = [_]u8{0} ** 8;
+ mem.copy(u8, padded[0 .. in.len - i], in[i..]);
+ const x = mem.readIntNative(u64, &padded) ^ mem.nativeTo(u64, self.st[i / 8], endian);
+ mem.writeIntNative(u64, &padded, x);
+ mem.copy(u8, out[i..], padded[0 .. in.len - i]);
+ }
+ }
+
+ /// Set the words storing the bytes of a given range to zero.
+ pub fn clear(self: *Self, from: usize, to: usize) void {
+ mem.set(u64, self.st[from / 8 .. (to + 7) / 8], 0);
+ }
+
+ /// Clear the entire state, disabling compiler optimizations.
+ pub fn secureZero(self: *Self) void {
+ std.crypto.utils.secureZero(u64, &self.st);
+ }
+
+ /// Apply a reduced-round permutation to the state.
+ pub inline fn permuteR(state: *Self, comptime rounds: u4) void {
+ const rks = [12]u64{ 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b };
+ inline for (rks[rks.len - rounds ..]) |rk| {
+ state.round(rk);
+ }
+ }
+
+ /// Apply a full-round permutation to the state.
+ pub inline fn permute(state: *Self) void {
+ state.permuteR(12);
+ }
+
+ // Core Ascon permutation.
+ inline fn round(state: *Self, rk: u64) void {
+ const x = &state.st;
+ x[2] ^= rk;
+
+ x[0] ^= x[4];
+ x[4] ^= x[3];
+ x[2] ^= x[1];
+ var t: Block = .{
+ x[0] ^ (~x[1] & x[2]),
+ x[1] ^ (~x[2] & x[3]),
+ x[2] ^ (~x[3] & x[4]),
+ x[3] ^ (~x[4] & x[0]),
+ x[4] ^ (~x[0] & x[1]),
+ };
+ t[1] ^= t[0];
+ t[3] ^= t[2];
+ t[0] ^= t[4];
+
+ x[2] = t[2] ^ rotr(u64, t[2], 6 - 1);
+ x[3] = t[3] ^ rotr(u64, t[3], 17 - 10);
+ x[4] = t[4] ^ rotr(u64, t[4], 41 - 7);
+ x[0] = t[0] ^ rotr(u64, t[0], 28 - 19);
+ x[1] = t[1] ^ rotr(u64, t[1], 61 - 39);
+ x[2] = t[2] ^ rotr(u64, x[2], 1);
+ x[3] = t[3] ^ rotr(u64, x[3], 10);
+ x[4] = t[4] ^ rotr(u64, x[4], 7);
+ x[0] = t[0] ^ rotr(u64, x[0], 19);
+ x[1] = t[1] ^ rotr(u64, x[1], 39);
+ x[2] = ~x[2];
+ }
+ };
+}
+
+test "ascon" {
+ const Ascon = State(.Big);
+ const bytes = [_]u8{0x01} ** Ascon.block_bytes;
+ var st = Ascon.init(bytes);
+ var out: [Ascon.block_bytes]u8 = undefined;
+ st.permute();
+ st.extractBytes(&out);
+ const expected1 = [_]u8{ 148, 147, 49, 226, 218, 221, 208, 113, 186, 94, 96, 10, 183, 219, 119, 150, 169, 206, 65, 18, 215, 97, 78, 106, 118, 81, 211, 150, 52, 17, 117, 64, 216, 45, 148, 240, 65, 181, 90, 180 };
+ try testing.expectEqualSlices(u8, &expected1, &out);
+ st.clear(0, 10);
+ st.extractBytes(&out);
+ const expected2 = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 169, 206, 65, 18, 215, 97, 78, 106, 118, 81, 211, 150, 52, 17, 117, 64, 216, 45, 148, 240, 65, 181, 90, 180 };
+ try testing.expectEqualSlices(u8, &expected2, &out);
+ st.addByte(1, 5);
+ st.addByte(2, 5);
+ st.extractBytes(&out);
+ const expected3 = [_]u8{ 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 169, 206, 65, 18, 215, 97, 78, 106, 118, 81, 211, 150, 52, 17, 117, 64, 216, 45, 148, 240, 65, 181, 90, 180 };
+ try testing.expectEqualSlices(u8, &expected3, &out);
+ st.addBytes(&bytes);
+ st.extractBytes(&out);
+ const expected4 = [_]u8{ 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 168, 207, 64, 19, 214, 96, 79, 107, 119, 80, 210, 151, 53, 16, 116, 65, 217, 44, 149, 241, 64, 180, 91, 181 };
+ try testing.expectEqualSlices(u8, &expected4, &out);
+}
lib/std/crypto/isap.zig
@@ -1,9 +1,11 @@
const std = @import("std");
+const crypto = std.crypto;
const debug = std.debug;
const mem = std.mem;
const math = std.math;
const testing = std.testing;
-const AuthenticationError = std.crypto.errors.AuthenticationError;
+const Ascon = crypto.core.Ascon(.Big);
+const AuthenticationError = crypto.errors.AuthenticationError;
/// ISAPv2 is an authenticated encryption system hardened against side channels and fault attacks.
/// https://csrc.nist.gov/CSRC/media/Projects/lightweight-cryptography/documents/round-2/spec-doc-rnd2/isap-spec-round2.pdf
@@ -25,90 +27,26 @@ pub const IsapA128A = struct {
const iv2 = [_]u8{ 0x02, 0x80, 0x40, 0x01, 0x0c, 0x01, 0x06, 0x0c };
const iv3 = [_]u8{ 0x03, 0x80, 0x40, 0x01, 0x0c, 0x01, 0x06, 0x0c };
- const Block = [5]u64;
-
- block: Block,
-
- fn round(isap: *IsapA128A, rk: u64) void {
- var x = &isap.block;
- x[2] ^= rk;
- x[0] ^= x[4];
- x[4] ^= x[3];
- x[2] ^= x[1];
- var t = x.*;
- x[0] = t[0] ^ ((~t[1]) & t[2]);
- x[2] = t[2] ^ ((~t[3]) & t[4]);
- x[4] = t[4] ^ ((~t[0]) & t[1]);
- x[1] = t[1] ^ ((~t[2]) & t[3]);
- x[3] = t[3] ^ ((~t[4]) & t[0]);
- x[1] ^= x[0];
- t[1] = x[1];
- x[1] = math.rotr(u64, x[1], 39);
- x[3] ^= x[2];
- t[2] = x[2];
- x[2] = math.rotr(u64, x[2], 1);
- t[4] = x[4];
- t[2] ^= x[2];
- x[2] = math.rotr(u64, x[2], 5);
- t[3] = x[3];
- t[1] ^= x[1];
- x[3] = math.rotr(u64, x[3], 10);
- x[0] ^= x[4];
- x[4] = math.rotr(u64, x[4], 7);
- t[3] ^= x[3];
- x[2] ^= t[2];
- x[1] = math.rotr(u64, x[1], 22);
- t[0] = x[0];
- x[2] = ~x[2];
- x[3] = math.rotr(u64, x[3], 7);
- t[4] ^= x[4];
- x[4] = math.rotr(u64, x[4], 34);
- x[3] ^= t[3];
- x[1] ^= t[1];
- x[0] = math.rotr(u64, x[0], 19);
- x[4] ^= t[4];
- t[0] ^= x[0];
- x[0] = math.rotr(u64, x[0], 9);
- x[0] ^= t[0];
- }
-
- fn p12(isap: *IsapA128A) void {
- const rks = [12]u64{ 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b };
- inline for (rks) |rk| {
- isap.round(rk);
- }
- }
-
- fn p6(isap: *IsapA128A) void {
- const rks = [6]u64{ 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b };
- inline for (rks) |rk| {
- isap.round(rk);
- }
- }
-
- fn p1(isap: *IsapA128A) void {
- isap.round(0x4b);
- }
+ st: Ascon,
fn absorb(isap: *IsapA128A, m: []const u8) void {
- var block = &isap.block;
var i: usize = 0;
while (true) : (i += 8) {
const left = m.len - i;
if (left >= 8) {
- block[0] ^= mem.readIntBig(u64, m[i..][0..8]);
- isap.p12();
+ isap.st.addBytes(m[i..][0..8]);
+ isap.st.permute();
if (left == 8) {
- block[0] ^= 0x8000000000000000;
- isap.p12();
+ isap.st.addByte(0x80, 0);
+ isap.st.permute();
break;
}
} else {
var padded = [_]u8{0} ** 8;
mem.copy(u8, padded[0..left], m[i..]);
padded[left] = 0x80;
- block[0] ^= mem.readIntBig(u64, padded[0..]);
- isap.p12();
+ isap.st.addBytes(&padded);
+ isap.st.permute();
break;
}
}
@@ -116,65 +54,59 @@ pub const IsapA128A = struct {
fn trickle(k: [16]u8, iv: [8]u8, y: []const u8, comptime out_len: usize) [out_len]u8 {
var isap = IsapA128A{
- .block = Block{
+ .st = Ascon.initFromWords(.{
mem.readIntBig(u64, k[0..8]),
mem.readIntBig(u64, k[8..16]),
mem.readIntBig(u64, iv[0..8]),
0,
0,
- },
+ }),
};
- isap.p12();
+ isap.st.permute();
var i: usize = 0;
while (i < y.len * 8 - 1) : (i += 1) {
const cur_byte_pos = i / 8;
const cur_bit_pos = @truncate(u3, 7 - (i % 8));
- const cur_bit = @as(u64, ((y[cur_byte_pos] >> cur_bit_pos) & 1) << 7);
- isap.block[0] ^= cur_bit << 56;
- isap.p1();
+ const cur_bit = ((y[cur_byte_pos] >> cur_bit_pos) & 1) << 7;
+ isap.st.addByte(cur_bit, 0);
+ isap.st.permuteR(1);
}
- const cur_bit = @as(u64, (y[y.len - 1] & 1) << 7);
- isap.block[0] ^= cur_bit << 56;
- isap.p12();
+ const cur_bit = (y[y.len - 1] & 1) << 7;
+ isap.st.addByte(cur_bit, 0);
+ isap.st.permute();
var out: [out_len]u8 = undefined;
- var j: usize = 0;
- while (j < out_len) : (j += 8) {
- mem.writeIntBig(u64, out[j..][0..8], isap.block[j / 8]);
- }
- std.crypto.utils.secureZero(u64, &isap.block);
+ isap.st.extractBytes(&out);
+ isap.st.secureZero();
return out;
}
fn mac(c: []const u8, ad: []const u8, npub: [16]u8, key: [16]u8) [16]u8 {
var isap = IsapA128A{
- .block = Block{
+ .st = Ascon.initFromWords(.{
mem.readIntBig(u64, npub[0..8]),
mem.readIntBig(u64, npub[8..16]),
mem.readIntBig(u64, iv1[0..]),
0,
0,
- },
+ }),
};
- isap.p12();
+ isap.st.permute();
isap.absorb(ad);
- isap.block[4] ^= 1;
+ isap.st.addByte(1, Ascon.block_bytes - 1);
isap.absorb(c);
var y: [16]u8 = undefined;
- mem.writeIntBig(u64, y[0..8], isap.block[0]);
- mem.writeIntBig(u64, y[8..16], isap.block[1]);
+ isap.st.extractBytes(&y);
const nb = trickle(key, iv2, y[0..], 16);
- isap.block[0] = mem.readIntBig(u64, nb[0..8]);
- isap.block[1] = mem.readIntBig(u64, nb[8..16]);
- isap.p12();
+ isap.st.setBytes(&nb);
+ isap.st.permute();
var tag: [16]u8 = undefined;
- mem.writeIntBig(u64, tag[0..8], isap.block[0]);
- mem.writeIntBig(u64, tag[8..16], isap.block[1]);
- std.crypto.utils.secureZero(u64, &isap.block);
+ isap.st.extractBytes(&tag);
+ isap.st.secureZero();
return tag;
}
@@ -183,34 +115,31 @@ pub const IsapA128A = struct {
const nb = trickle(key, iv3, npub[0..], 24);
var isap = IsapA128A{
- .block = Block{
+ .st = Ascon.initFromWords(.{
mem.readIntBig(u64, nb[0..8]),
mem.readIntBig(u64, nb[8..16]),
mem.readIntBig(u64, nb[16..24]),
mem.readIntBig(u64, npub[0..8]),
mem.readIntBig(u64, npub[8..16]),
- },
+ }),
};
- isap.p6();
+ isap.st.permuteR(6);
var i: usize = 0;
while (true) : (i += 8) {
const left = in.len - i;
if (left >= 8) {
- mem.writeIntNative(u64, out[i..][0..8], mem.bigToNative(u64, isap.block[0]) ^ mem.readIntNative(u64, in[i..][0..8]));
+ isap.st.xorBytes(out[i..][0..8], in[i..][0..8]);
if (left == 8) {
break;
}
- isap.p6();
+ isap.st.permuteR(6);
} else {
- var pad = [_]u8{0} ** 8;
- mem.copy(u8, pad[0..left], in[i..][0..left]);
- mem.writeIntNative(u64, pad[i..][0..8], mem.bigToNative(u64, isap.block[0]) ^ mem.readIntNative(u64, pad[i..][0..8]));
- mem.copy(u8, out[i..][0..left], pad[0..left]);
+ isap.st.xorBytes(out[i..], in[i..]);
break;
}
}
- std.crypto.utils.secureZero(u64, &isap.block);
+ isap.st.secureZero();
}
pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) void {
@@ -220,12 +149,9 @@ pub const IsapA128A = struct {
pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) AuthenticationError!void {
var computed_tag = mac(c, ad, npub, key);
- var acc: u8 = 0;
- for (computed_tag) |_, j| {
- acc |= (computed_tag[j] ^ tag[j]);
- }
- std.crypto.utils.secureZero(u8, &computed_tag);
- if (acc != 0) {
+ const res = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
+ crypto.utils.secureZero(u8, &computed_tag);
+ if (!res) {
return error.AuthenticationFailed;
}
xor(m, c, npub, key);
lib/std/rand/Ascon.zig
@@ -0,0 +1,45 @@
+//! CSPRNG based on the Ascon XOFa construction
+
+const std = @import("std");
+const min = std.math.min;
+const mem = std.mem;
+const Random = std.rand.Random;
+const Self = @This();
+
+state: std.crypto.core.Ascon(.Little),
+
+const rate = 8;
+pub const secret_seed_length = 32;
+
+/// The seed must be uniform, secret and `secret_seed_length` bytes long.
+pub fn init(secret_seed: [secret_seed_length]u8) Self {
+ var state = std.crypto.core.Ascon(.Little).initXofA();
+ var i: usize = 0;
+ while (i + rate <= secret_seed.len) : (i += rate) {
+ state.addBytes(secret_seed[i..][0..rate]);
+ state.permuteR(8);
+ }
+ const left = secret_seed.len - i;
+ if (left > 0) state.addBytes(secret_seed[i..]);
+ state.addByte(0x80, left);
+ state.permute();
+ return Self{ .state = state };
+}
+
+pub fn random(self: *Self) Random {
+ return Random.init(self, fill);
+}
+
+pub fn fill(self: *Self, buf: []u8) void {
+ var i: usize = 0;
+ while (true) {
+ const left = buf.len - i;
+ const n = min(left, rate);
+ self.state.extractBytes(buf[i..][0..n]);
+ if (left == 0) break;
+ self.state.permuteR(8);
+ i += n;
+ }
+ self.state.clear(0, rate);
+ self.state.permuteR(8);
+}
lib/std/crypto.zig
@@ -46,6 +46,7 @@ pub const auth = struct {
/// Core functions, that should rarely be used directly by applications.
pub const core = struct {
pub const aes = @import("crypto/aes.zig");
+ pub const Ascon = @import("crypto/ascon.zig").State;
pub const Gimli = @import("crypto/gimli.zig").State;
pub const Xoodoo = @import("crypto/xoodoo.zig").State;
@@ -205,7 +206,9 @@ test {
_ = auth.siphash;
_ = core.aes;
+ _ = core.Ascon;
_ = core.Gimli;
+ _ = core.Xoodoo;
_ = core.modes;
_ = dh.X25519;
lib/std/rand.zig
@@ -18,8 +18,9 @@ const maxInt = std.math.maxInt;
pub const DefaultPrng = Xoshiro256;
/// Cryptographically secure random numbers.
-pub const DefaultCsprng = Xoodoo;
+pub const DefaultCsprng = Ascon;
+pub const Ascon = @import("rand/Ascon.zig");
pub const Isaac64 = @import("rand/Isaac64.zig");
pub const Xoodoo = @import("rand/Xoodoo.zig");
pub const Pcg = @import("rand/Pcg.zig");