| author | |
| committer | |
| log | f62e3b8c0dd232bddb559405b00c1c1c4815f359 |
| tree | 3c3b2d6bc3ad5290db72f2bf545ff71a20f24a02 |
| parent | a8edd67d3d87b585ebbea226bb916ee1f7263458 |
Ascon has been selected as new standard for lightweight cryptography
in the NIST Lightweight Cryptography competition.
Ascon won over Gimli and Xoodoo.
The permutation is unlikely to change. However, NIST may tweak
the constructions (XOF, hash, authenticated encryption) before
standardizing them. For that reason, implementations of those
are better maintained outside the standard library for now.
In fact, we already had an Ascon implementation in Zig:
`std.crypto.aead.isap` is based on it. While the implementation was
here, there was no public API to access it directly.
So:
- The Ascon permutation is now available as `std.crypto.core.Ascon`,
with everything needed to use it in AEADs and other Ascon-based
constructions
- The ISAP implementation now uses std.crypto.core.Ascon instead of
keeping a private copy
- The default CSPRNG replaces Xoodoo with Ascon. And instead of an
ad-hoc construction, it's using the XOFa mode of the NIST submission.5 files changed, 317 insertions(+), 115 deletions(-)
lib/std/crypto.zig+3| ... | @@ -46,6 +46,7 @@ pub const auth = struct { | ... | @@ -46,6 +46,7 @@ pub const auth = struct { |
| 46 | /// Core functions, that should rarely be used directly by applications. | 46 | /// Core functions, that should rarely be used directly by applications. |
| 47 | pub const core = struct { | 47 | pub const core = struct { |
| 48 | pub const aes = @import("crypto/aes.zig"); | 48 | pub const aes = @import("crypto/aes.zig"); |
| 49 | pub const Ascon = @import("crypto/ascon.zig").State; | ||
| 49 | pub const Gimli = @import("crypto/gimli.zig").State; | 50 | pub const Gimli = @import("crypto/gimli.zig").State; |
| 50 | pub const Xoodoo = @import("crypto/xoodoo.zig").State; | 51 | pub const Xoodoo = @import("crypto/xoodoo.zig").State; |
| 51 | 52 | ||
| ... | @@ -205,7 +206,9 @@ test { | ... | @@ -205,7 +206,9 @@ test { |
| 205 | _ = auth.siphash; | 206 | _ = auth.siphash; |
| 206 | 207 | ||
| 207 | _ = core.aes; | 208 | _ = core.aes; |
| 209 | _ = core.Ascon; | ||
| 208 | _ = core.Gimli; | 210 | _ = core.Gimli; |
| 211 | _ = core.Xoodoo; | ||
| 209 | _ = core.modes; | 212 | _ = core.modes; |
| 210 | 213 | ||
| 211 | _ = dh.X25519; | 214 | _ = dh.X25519; |
lib/std/crypto/ascon.zig created+227| ... | @@ -0,0 +1,227 @@ | ||
| 1 | //! Ascon is a 320-bit permutation, selected as new standard for lightweight cryptography | ||
| 2 | //! in the NIST Lightweight Cryptography competition (2019–2023). | ||
| 3 | //! https://csrc.nist.gov/News/2023/lightweight-cryptography-nist-selects-ascon | ||
| 4 | //! | ||
| 5 | //! The permutation is compact, and optimized for timing and side channel resistance, | ||
| 6 | //! making it a good choice for embedded applications. | ||
| 7 | //! | ||
| 8 | //! It is not meant to be used directly, but as a building block for symmetric cryptography. | ||
| 9 | |||
| 10 | const std = @import("std"); | ||
| 11 | const builtin = std.builtin; | ||
| 12 | const debug = std.debug; | ||
| 13 | const mem = std.mem; | ||
| 14 | const testing = std.testing; | ||
| 15 | const rotr = std.math.rotr; | ||
| 16 | |||
| 17 | /// An Ascon state. | ||
| 18 | /// | ||
| 19 | /// The state is represented as 5 64-bit words. | ||
| 20 | /// | ||
| 21 | /// The NIST submission (v1.2) serializes these words as big-endian, | ||
| 22 | /// but software implementations are free to use native endianness. | ||
| 23 | pub fn State(comptime endian: builtin.Endian) type { | ||
| 24 | return struct { | ||
| 25 | const Self = @This(); | ||
| 26 | |||
| 27 | /// Number of bytes in the state. | ||
| 28 | pub const block_bytes = 40; | ||
| 29 | |||
| 30 | const Block = [5]u64; | ||
| 31 | |||
| 32 | st: Block, | ||
| 33 | |||
| 34 | /// Initialize the state from a slice of bytes. | ||
| 35 | pub fn init(initial_state: [block_bytes]u8) Self { | ||
| 36 | var state = Self{ .st = undefined }; | ||
| 37 | mem.copy(u8, state.asBytes(), &initial_state); | ||
| 38 | state.endianSwap(); | ||
| 39 | return state; | ||
| 40 | } | ||
| 41 | |||
| 42 | /// Initialize the state from u64 words in native endianness. | ||
| 43 | pub fn initFromWords(initial_state: [5]u64) Self { | ||
| 44 | var state = Self{ .st = initial_state }; | ||
| 45 | return state; | ||
| 46 | } | ||
| 47 | |||
| 48 | /// Initialize the state for Ascon XOF | ||
| 49 | pub fn initXof() Self { | ||
| 50 | return Self{ .st = Block{ | ||
| 51 | 0xb57e273b814cd416, | ||
| 52 | 0x2b51042562ae2420, | ||
| 53 | 0x66a3a7768ddf2218, | ||
| 54 | 0x5aad0a7a8153650c, | ||
| 55 | 0x4f3e0e32539493b6, | ||
| 56 | } }; | ||
| 57 | } | ||
| 58 | |||
| 59 | /// Initialize the state for Ascon XOFa | ||
| 60 | pub fn initXofA() Self { | ||
| 61 | return Self{ .st = Block{ | ||
| 62 | 0x44906568b77b9832, | ||
| 63 | 0xcd8d6cae53455532, | ||
| 64 | 0xf7b5212756422129, | ||
| 65 | 0x246885e1de0d225b, | ||
| 66 | 0xa8cb5ce33449973f, | ||
| 67 | } }; | ||
| 68 | } | ||
| 69 | |||
| 70 | /// A representation of the state as bytes. The byte order is architecture-dependent. | ||
| 71 | pub fn asBytes(self: *Self) *[block_bytes]u8 { | ||
| 72 | return mem.asBytes(&self.st); | ||
| 73 | } | ||
| 74 | |||
| 75 | /// Byte-swap the entire state if the architecture doesn't match the required endianness. | ||
| 76 | pub fn endianSwap(self: *Self) void { | ||
| 77 | for (self.st) |*w| { | ||
| 78 | w.* = mem.toNative(u64, w.*, endian); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | /// Set bytes starting at the beginning of the state. | ||
| 83 | pub fn setBytes(self: *Self, bytes: []const u8) void { | ||
| 84 | var i: usize = 0; | ||
| 85 | while (i + 8 <= bytes.len) : (i += 8) { | ||
| 86 | self.st[i / 8] = mem.readInt(u64, bytes[i..][0..8], endian); | ||
| 87 | } | ||
| 88 | if (i < bytes.len) { | ||
| 89 | var padded = [_]u8{0} ** 8; | ||
| 90 | mem.copy(u8, padded[0 .. bytes.len - i], bytes[i..]); | ||
| 91 | self.st[i / 8] = mem.readInt(u64, padded[0..], endian); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | /// XOR a byte into the state at a given offset. | ||
| 96 | pub fn addByte(self: *Self, byte: u8, offset: usize) void { | ||
| 97 | const z = switch (endian) { | ||
| 98 | .Big => 64 - 8 - 8 * @truncate(u6, offset % 8), | ||
| 99 | .Little => 8 * @truncate(u6, offset % 8), | ||
| 100 | }; | ||
| 101 | self.st[offset / 8] ^= @as(u64, byte) << z; | ||
| 102 | } | ||
| 103 | |||
| 104 | /// XOR bytes into the beginning of the state. | ||
| 105 | pub fn addBytes(self: *Self, bytes: []const u8) void { | ||
| 106 | var i: usize = 0; | ||
| 107 | while (i + 8 <= bytes.len) : (i += 8) { | ||
| 108 | self.st[i / 8] ^= mem.readInt(u64, bytes[i..][0..8], endian); | ||
| 109 | } | ||
| 110 | if (i < bytes.len) { | ||
| 111 | var padded = [_]u8{0} ** 8; | ||
| 112 | mem.copy(u8, padded[0 .. bytes.len - i], bytes[i..]); | ||
| 113 | self.st[i / 8] ^= mem.readInt(u64, padded[0..], endian); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | /// Extract the first bytes of the state. | ||
| 118 | pub fn extractBytes(self: *Self, out: []u8) void { | ||
| 119 | var i: usize = 0; | ||
| 120 | while (i + 8 <= out.len) : (i += 8) { | ||
| 121 | mem.writeInt(u64, out[i..][0..8], self.st[i / 8], endian); | ||
| 122 | } | ||
| 123 | if (i < out.len) { | ||
| 124 | var padded = [_]u8{0} ** 8; | ||
| 125 | mem.writeInt(u64, padded[0..], self.st[i / 8], endian); | ||
| 126 | mem.copy(u8, out[i..], padded[0 .. out.len - i]); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | /// XOR the first bytes of the state into a slice of bytes. | ||
| 131 | pub fn xorBytes(self: *Self, out: []u8, in: []const u8) void { | ||
| 132 | debug.assert(out.len == in.len); | ||
| 133 | |||
| 134 | var i: usize = 0; | ||
| 135 | while (i + 8 <= in.len) : (i += 8) { | ||
| 136 | const x = mem.readIntNative(u64, in[i..][0..8]) ^ mem.nativeTo(u64, self.st[i / 8], endian); | ||
| 137 | mem.writeIntNative(u64, out[i..][0..8], x); | ||
| 138 | } | ||
| 139 | if (i < in.len) { | ||
| 140 | var padded = [_]u8{0} ** 8; | ||
| 141 | mem.copy(u8, padded[0 .. in.len - i], in[i..]); | ||
| 142 | const x = mem.readIntNative(u64, &padded) ^ mem.nativeTo(u64, self.st[i / 8], endian); | ||
| 143 | mem.writeIntNative(u64, &padded, x); | ||
| 144 | mem.copy(u8, out[i..], padded[0 .. in.len - i]); | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | /// Set the words storing the bytes of a given range to zero. | ||
| 149 | pub fn clear(self: *Self, from: usize, to: usize) void { | ||
| 150 | mem.set(u64, self.st[from / 8 .. (to + 7) / 8], 0); | ||
| 151 | } | ||
| 152 | |||
| 153 | /// Clear the entire state, disabling compiler optimizations. | ||
| 154 | pub fn secureZero(self: *Self) void { | ||
| 155 | std.crypto.utils.secureZero(u64, &self.st); | ||
| 156 | } | ||
| 157 | |||
| 158 | /// Apply a reduced-round permutation to the state. | ||
| 159 | pub inline fn permuteR(state: *Self, comptime rounds: u4) void { | ||
| 160 | const rks = [12]u64{ 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b }; | ||
| 161 | inline for (rks[rks.len - rounds ..]) |rk| { | ||
| 162 | state.round(rk); | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | /// Apply a full-round permutation to the state. | ||
| 167 | pub inline fn permute(state: *Self) void { | ||
| 168 | state.permuteR(12); | ||
| 169 | } | ||
| 170 | |||
| 171 | // Core Ascon permutation. | ||
| 172 | inline fn round(state: *Self, rk: u64) void { | ||
| 173 | const x = &state.st; | ||
| 174 | x[2] ^= rk; | ||
| 175 | |||
| 176 | x[0] ^= x[4]; | ||
| 177 | x[4] ^= x[3]; | ||
| 178 | x[2] ^= x[1]; | ||
| 179 | var t: Block = .{ | ||
| 180 | x[0] ^ (~x[1] & x[2]), | ||
| 181 | x[1] ^ (~x[2] & x[3]), | ||
| 182 | x[2] ^ (~x[3] & x[4]), | ||
| 183 | x[3] ^ (~x[4] & x[0]), | ||
| 184 | x[4] ^ (~x[0] & x[1]), | ||
| 185 | }; | ||
| 186 | t[1] ^= t[0]; | ||
| 187 | t[3] ^= t[2]; | ||
| 188 | t[0] ^= t[4]; | ||
| 189 | |||
| 190 | x[2] = t[2] ^ rotr(u64, t[2], 6 - 1); | ||
| 191 | x[3] = t[3] ^ rotr(u64, t[3], 17 - 10); | ||
| 192 | x[4] = t[4] ^ rotr(u64, t[4], 41 - 7); | ||
| 193 | x[0] = t[0] ^ rotr(u64, t[0], 28 - 19); | ||
| 194 | x[1] = t[1] ^ rotr(u64, t[1], 61 - 39); | ||
| 195 | x[2] = t[2] ^ rotr(u64, x[2], 1); | ||
| 196 | x[3] = t[3] ^ rotr(u64, x[3], 10); | ||
| 197 | x[4] = t[4] ^ rotr(u64, x[4], 7); | ||
| 198 | x[0] = t[0] ^ rotr(u64, x[0], 19); | ||
| 199 | x[1] = t[1] ^ rotr(u64, x[1], 39); | ||
| 200 | x[2] = ~x[2]; | ||
| 201 | } | ||
| 202 | }; | ||
| 203 | } | ||
| 204 | |||
| 205 | test "ascon" { | ||
| 206 | const Ascon = State(.Big); | ||
| 207 | const bytes = [_]u8{0x01} ** Ascon.block_bytes; | ||
| 208 | var st = Ascon.init(bytes); | ||
| 209 | var out: [Ascon.block_bytes]u8 = undefined; | ||
| 210 | st.permute(); | ||
| 211 | st.extractBytes(&out); | ||
| 212 | 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 }; | ||
| 213 | try testing.expectEqualSlices(u8, &expected1, &out); | ||
| 214 | st.clear(0, 10); | ||
| 215 | st.extractBytes(&out); | ||
| 216 | 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 }; | ||
| 217 | try testing.expectEqualSlices(u8, &expected2, &out); | ||
| 218 | st.addByte(1, 5); | ||
| 219 | st.addByte(2, 5); | ||
| 220 | st.extractBytes(&out); | ||
| 221 | 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 }; | ||
| 222 | try testing.expectEqualSlices(u8, &expected3, &out); | ||
| 223 | st.addBytes(&bytes); | ||
| 224 | st.extractBytes(&out); | ||
| 225 | 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 }; | ||
| 226 | try testing.expectEqualSlices(u8, &expected4, &out); | ||
| 227 | } | ||
lib/std/crypto/isap.zig+40-114| ... | @@ -1,9 +1,11 @@ | ... | @@ -1,9 +1,11 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const crypto = std.crypto; | ||
| 2 | const debug = std.debug; | 3 | const debug = std.debug; |
| 3 | const mem = std.mem; | 4 | const mem = std.mem; |
| 4 | const math = std.math; | 5 | const math = std.math; |
| 5 | const testing = std.testing; | 6 | const testing = std.testing; |
| 6 | const AuthenticationError = std.crypto.errors.AuthenticationError; | 7 | const Ascon = crypto.core.Ascon(.Big); |
| 8 | const AuthenticationError = crypto.errors.AuthenticationError; | ||
| 7 | 9 | ||
| 8 | /// ISAPv2 is an authenticated encryption system hardened against side channels and fault attacks. | 10 | /// ISAPv2 is an authenticated encryption system hardened against side channels and fault attacks. |
| 9 | /// https://csrc.nist.gov/CSRC/media/Projects/lightweight-cryptography/documents/round-2/spec-doc-rnd2/isap-spec-round2.pdf | 11 | /// 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 { | ... | @@ -25,90 +27,26 @@ pub const IsapA128A = struct { |
| 25 | const iv2 = [_]u8{ 0x02, 0x80, 0x40, 0x01, 0x0c, 0x01, 0x06, 0x0c }; | 27 | const iv2 = [_]u8{ 0x02, 0x80, 0x40, 0x01, 0x0c, 0x01, 0x06, 0x0c }; |
| 26 | const iv3 = [_]u8{ 0x03, 0x80, 0x40, 0x01, 0x0c, 0x01, 0x06, 0x0c }; | 28 | const iv3 = [_]u8{ 0x03, 0x80, 0x40, 0x01, 0x0c, 0x01, 0x06, 0x0c }; |
| 27 | 29 | ||
| 28 | const Block = [5]u64; | 30 | st: Ascon, |
| 29 | |||
| 30 | block: Block, | ||
| 31 | |||
| 32 | fn round(isap: *IsapA128A, rk: u64) void { | ||
| 33 | var x = &isap.block; | ||
| 34 | x[2] ^= rk; | ||
| 35 | x[0] ^= x[4]; | ||
| 36 | x[4] ^= x[3]; | ||
| 37 | x[2] ^= x[1]; | ||
| 38 | var t = x.*; | ||
| 39 | x[0] = t[0] ^ ((~t[1]) & t[2]); | ||
| 40 | x[2] = t[2] ^ ((~t[3]) & t[4]); | ||
| 41 | x[4] = t[4] ^ ((~t[0]) & t[1]); | ||
| 42 | x[1] = t[1] ^ ((~t[2]) & t[3]); | ||
| 43 | x[3] = t[3] ^ ((~t[4]) & t[0]); | ||
| 44 | x[1] ^= x[0]; | ||
| 45 | t[1] = x[1]; | ||
| 46 | x[1] = math.rotr(u64, x[1], 39); | ||
| 47 | x[3] ^= x[2]; | ||
| 48 | t[2] = x[2]; | ||
| 49 | x[2] = math.rotr(u64, x[2], 1); | ||
| 50 | t[4] = x[4]; | ||
| 51 | t[2] ^= x[2]; | ||
| 52 | x[2] = math.rotr(u64, x[2], 5); | ||
| 53 | t[3] = x[3]; | ||
| 54 | t[1] ^= x[1]; | ||
| 55 | x[3] = math.rotr(u64, x[3], 10); | ||
| 56 | x[0] ^= x[4]; | ||
| 57 | x[4] = math.rotr(u64, x[4], 7); | ||
| 58 | t[3] ^= x[3]; | ||
| 59 | x[2] ^= t[2]; | ||
| 60 | x[1] = math.rotr(u64, x[1], 22); | ||
| 61 | t[0] = x[0]; | ||
| 62 | x[2] = ~x[2]; | ||
| 63 | x[3] = math.rotr(u64, x[3], 7); | ||
| 64 | t[4] ^= x[4]; | ||
| 65 | x[4] = math.rotr(u64, x[4], 34); | ||
| 66 | x[3] ^= t[3]; | ||
| 67 | x[1] ^= t[1]; | ||
| 68 | x[0] = math.rotr(u64, x[0], 19); | ||
| 69 | x[4] ^= t[4]; | ||
| 70 | t[0] ^= x[0]; | ||
| 71 | x[0] = math.rotr(u64, x[0], 9); | ||
| 72 | x[0] ^= t[0]; | ||
| 73 | } | ||
| 74 | |||
| 75 | fn p12(isap: *IsapA128A) void { | ||
| 76 | const rks = [12]u64{ 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b }; | ||
| 77 | inline for (rks) |rk| { | ||
| 78 | isap.round(rk); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | fn p6(isap: *IsapA128A) void { | ||
| 83 | const rks = [6]u64{ 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b }; | ||
| 84 | inline for (rks) |rk| { | ||
| 85 | isap.round(rk); | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | fn p1(isap: *IsapA128A) void { | ||
| 90 | isap.round(0x4b); | ||
| 91 | } | ||
| 92 | 31 | ||
| 93 | fn absorb(isap: *IsapA128A, m: []const u8) void { | 32 | fn absorb(isap: *IsapA128A, m: []const u8) void { |
| 94 | var block = &isap.block; | ||
| 95 | var i: usize = 0; | 33 | var i: usize = 0; |
| 96 | while (true) : (i += 8) { | 34 | while (true) : (i += 8) { |
| 97 | const left = m.len - i; | 35 | const left = m.len - i; |
| 98 | if (left >= 8) { | 36 | if (left >= 8) { |
| 99 | block[0] ^= mem.readIntBig(u64, m[i..][0..8]); | 37 | isap.st.addBytes(m[i..][0..8]); |
| 100 | isap.p12(); | 38 | isap.st.permute(); |
| 101 | if (left == 8) { | 39 | if (left == 8) { |
| 102 | block[0] ^= 0x8000000000000000; | 40 | isap.st.addByte(0x80, 0); |
| 103 | isap.p12(); | 41 | isap.st.permute(); |
| 104 | break; | 42 | break; |
| 105 | } | 43 | } |
| 106 | } else { | 44 | } else { |
| 107 | var padded = [_]u8{0} ** 8; | 45 | var padded = [_]u8{0} ** 8; |
| 108 | mem.copy(u8, padded[0..left], m[i..]); | 46 | mem.copy(u8, padded[0..left], m[i..]); |
| 109 | padded[left] = 0x80; | 47 | padded[left] = 0x80; |
| 110 | block[0] ^= mem.readIntBig(u64, padded[0..]); | 48 | isap.st.addBytes(&padded); |
| 111 | isap.p12(); | 49 | isap.st.permute(); |
| 112 | break; | 50 | break; |
| 113 | } | 51 | } |
| 114 | } | 52 | } |
| ... | @@ -116,65 +54,59 @@ pub const IsapA128A = struct { | ... | @@ -116,65 +54,59 @@ pub const IsapA128A = struct { |
| 116 | 54 | ||
| 117 | fn trickle(k: [16]u8, iv: [8]u8, y: []const u8, comptime out_len: usize) [out_len]u8 { | 55 | fn trickle(k: [16]u8, iv: [8]u8, y: []const u8, comptime out_len: usize) [out_len]u8 { |
| 118 | var isap = IsapA128A{ | 56 | var isap = IsapA128A{ |
| 119 | .block = Block{ | 57 | .st = Ascon.initFromWords(.{ |
| 120 | mem.readIntBig(u64, k[0..8]), | 58 | mem.readIntBig(u64, k[0..8]), |
| 121 | mem.readIntBig(u64, k[8..16]), | 59 | mem.readIntBig(u64, k[8..16]), |
| 122 | mem.readIntBig(u64, iv[0..8]), | 60 | mem.readIntBig(u64, iv[0..8]), |
| 123 | 0, | 61 | 0, |
| 124 | 0, | 62 | 0, |
| 125 | }, | 63 | }), |
| 126 | }; | 64 | }; |
| 127 | isap.p12(); | 65 | isap.st.permute(); |
| 128 | 66 | ||
| 129 | var i: usize = 0; | 67 | var i: usize = 0; |
| 130 | while (i < y.len * 8 - 1) : (i += 1) { | 68 | while (i < y.len * 8 - 1) : (i += 1) { |
| 131 | const cur_byte_pos = i / 8; | 69 | const cur_byte_pos = i / 8; |
| 132 | const cur_bit_pos = @truncate(u3, 7 - (i % 8)); | 70 | const cur_bit_pos = @truncate(u3, 7 - (i % 8)); |
| 133 | const cur_bit = @as(u64, ((y[cur_byte_pos] >> cur_bit_pos) & 1) << 7); | 71 | const cur_bit = ((y[cur_byte_pos] >> cur_bit_pos) & 1) << 7; |
| 134 | isap.block[0] ^= cur_bit << 56; | 72 | isap.st.addByte(cur_bit, 0); |
| 135 | isap.p1(); | 73 | isap.st.permuteR(1); |
| 136 | } | 74 | } |
| 137 | const cur_bit = @as(u64, (y[y.len - 1] & 1) << 7); | 75 | const cur_bit = (y[y.len - 1] & 1) << 7; |
| 138 | isap.block[0] ^= cur_bit << 56; | 76 | isap.st.addByte(cur_bit, 0); |
| 139 | isap.p12(); | 77 | isap.st.permute(); |
| 140 | 78 | ||
| 141 | var out: [out_len]u8 = undefined; | 79 | var out: [out_len]u8 = undefined; |
| 142 | var j: usize = 0; | 80 | isap.st.extractBytes(&out); |
| 143 | while (j < out_len) : (j += 8) { | 81 | isap.st.secureZero(); |
| 144 | mem.writeIntBig(u64, out[j..][0..8], isap.block[j / 8]); | ||
| 145 | } | ||
| 146 | std.crypto.utils.secureZero(u64, &isap.block); | ||
| 147 | return out; | 82 | return out; |
| 148 | } | 83 | } |
| 149 | 84 | ||
| 150 | fn mac(c: []const u8, ad: []const u8, npub: [16]u8, key: [16]u8) [16]u8 { | 85 | fn mac(c: []const u8, ad: []const u8, npub: [16]u8, key: [16]u8) [16]u8 { |
| 151 | var isap = IsapA128A{ | 86 | var isap = IsapA128A{ |
| 152 | .block = Block{ | 87 | .st = Ascon.initFromWords(.{ |
| 153 | mem.readIntBig(u64, npub[0..8]), | 88 | mem.readIntBig(u64, npub[0..8]), |
| 154 | mem.readIntBig(u64, npub[8..16]), | 89 | mem.readIntBig(u64, npub[8..16]), |
| 155 | mem.readIntBig(u64, iv1[0..]), | 90 | mem.readIntBig(u64, iv1[0..]), |
| 156 | 0, | 91 | 0, |
| 157 | 0, | 92 | 0, |
| 158 | }, | 93 | }), |
| 159 | }; | 94 | }; |
| 160 | isap.p12(); | 95 | isap.st.permute(); |
| 161 | 96 | ||
| 162 | isap.absorb(ad); | 97 | isap.absorb(ad); |
| 163 | isap.block[4] ^= 1; | 98 | isap.st.addByte(1, Ascon.block_bytes - 1); |
| 164 | isap.absorb(c); | 99 | isap.absorb(c); |
| 165 | 100 | ||
| 166 | var y: [16]u8 = undefined; | 101 | var y: [16]u8 = undefined; |
| 167 | mem.writeIntBig(u64, y[0..8], isap.block[0]); | 102 | isap.st.extractBytes(&y); |
| 168 | mem.writeIntBig(u64, y[8..16], isap.block[1]); | ||
| 169 | const nb = trickle(key, iv2, y[0..], 16); | 103 | const nb = trickle(key, iv2, y[0..], 16); |
| 170 | isap.block[0] = mem.readIntBig(u64, nb[0..8]); | 104 | isap.st.setBytes(&nb); |
| 171 | isap.block[1] = mem.readIntBig(u64, nb[8..16]); | 105 | isap.st.permute(); |
| 172 | isap.p12(); | ||
| 173 | 106 | ||
| 174 | var tag: [16]u8 = undefined; | 107 | var tag: [16]u8 = undefined; |
| 175 | mem.writeIntBig(u64, tag[0..8], isap.block[0]); | 108 | isap.st.extractBytes(&tag); |
| 176 | mem.writeIntBig(u64, tag[8..16], isap.block[1]); | 109 | isap.st.secureZero(); |
| 177 | std.crypto.utils.secureZero(u64, &isap.block); | ||
| 178 | return tag; | 110 | return tag; |
| 179 | } | 111 | } |
| 180 | 112 | ||
| ... | @@ -183,34 +115,31 @@ pub const IsapA128A = struct { | ... | @@ -183,34 +115,31 @@ pub const IsapA128A = struct { |
| 183 | 115 | ||
| 184 | const nb = trickle(key, iv3, npub[0..], 24); | 116 | const nb = trickle(key, iv3, npub[0..], 24); |
| 185 | var isap = IsapA128A{ | 117 | var isap = IsapA128A{ |
| 186 | .block = Block{ | 118 | .st = Ascon.initFromWords(.{ |
| 187 | mem.readIntBig(u64, nb[0..8]), | 119 | mem.readIntBig(u64, nb[0..8]), |
| 188 | mem.readIntBig(u64, nb[8..16]), | 120 | mem.readIntBig(u64, nb[8..16]), |
| 189 | mem.readIntBig(u64, nb[16..24]), | 121 | mem.readIntBig(u64, nb[16..24]), |
| 190 | mem.readIntBig(u64, npub[0..8]), | 122 | mem.readIntBig(u64, npub[0..8]), |
| 191 | mem.readIntBig(u64, npub[8..16]), | 123 | mem.readIntBig(u64, npub[8..16]), |
| 192 | }, | 124 | }), |
| 193 | }; | 125 | }; |
| 194 | isap.p6(); | 126 | isap.st.permuteR(6); |
| 195 | 127 | ||
| 196 | var i: usize = 0; | 128 | var i: usize = 0; |
| 197 | while (true) : (i += 8) { | 129 | while (true) : (i += 8) { |
| 198 | const left = in.len - i; | 130 | const left = in.len - i; |
| 199 | if (left >= 8) { | 131 | if (left >= 8) { |
| 200 | mem.writeIntNative(u64, out[i..][0..8], mem.bigToNative(u64, isap.block[0]) ^ mem.readIntNative(u64, in[i..][0..8])); | 132 | isap.st.xorBytes(out[i..][0..8], in[i..][0..8]); |
| 201 | if (left == 8) { | 133 | if (left == 8) { |
| 202 | break; | 134 | break; |
| 203 | } | 135 | } |
| 204 | isap.p6(); | 136 | isap.st.permuteR(6); |
| 205 | } else { | 137 | } else { |
| 206 | var pad = [_]u8{0} ** 8; | 138 | isap.st.xorBytes(out[i..], in[i..]); |
| 207 | mem.copy(u8, pad[0..left], in[i..][0..left]); | ||
| 208 | mem.writeIntNative(u64, pad[i..][0..8], mem.bigToNative(u64, isap.block[0]) ^ mem.readIntNative(u64, pad[i..][0..8])); | ||
| 209 | mem.copy(u8, out[i..][0..left], pad[0..left]); | ||
| 210 | break; | 139 | break; |
| 211 | } | 140 | } |
| 212 | } | 141 | } |
| 213 | std.crypto.utils.secureZero(u64, &isap.block); | 142 | isap.st.secureZero(); |
| 214 | } | 143 | } |
| 215 | 144 | ||
| 216 | pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) void { | 145 | 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 { | ... | @@ -220,12 +149,9 @@ pub const IsapA128A = struct { |
| 220 | 149 | ||
| 221 | 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 { | 150 | 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 { |
| 222 | var computed_tag = mac(c, ad, npub, key); | 151 | var computed_tag = mac(c, ad, npub, key); |
| 223 | var acc: u8 = 0; | 152 | const res = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); |
| 224 | for (computed_tag) |_, j| { | 153 | crypto.utils.secureZero(u8, &computed_tag); |
| 225 | acc |= (computed_tag[j] ^ tag[j]); | 154 | if (!res) { |
| 226 | } | ||
| 227 | std.crypto.utils.secureZero(u8, &computed_tag); | ||
| 228 | if (acc != 0) { | ||
| 229 | return error.AuthenticationFailed; | 155 | return error.AuthenticationFailed; |
| 230 | } | 156 | } |
| 231 | xor(m, c, npub, key); | 157 | xor(m, c, npub, key); |
lib/std/rand.zig+2-1| ... | @@ -18,8 +18,9 @@ const maxInt = std.math.maxInt; | ... | @@ -18,8 +18,9 @@ const maxInt = std.math.maxInt; |
| 18 | pub const DefaultPrng = Xoshiro256; | 18 | pub const DefaultPrng = Xoshiro256; |
| 19 | 19 | ||
| 20 | /// Cryptographically secure random numbers. | 20 | /// Cryptographically secure random numbers. |
| 21 | pub const DefaultCsprng = Xoodoo; | 21 | pub const DefaultCsprng = Ascon; |
| 22 | 22 | ||
| 23 | pub const Ascon = @import("rand/Ascon.zig"); | ||
| 23 | pub const Isaac64 = @import("rand/Isaac64.zig"); | 24 | pub const Isaac64 = @import("rand/Isaac64.zig"); |
| 24 | pub const Xoodoo = @import("rand/Xoodoo.zig"); | 25 | pub const Xoodoo = @import("rand/Xoodoo.zig"); |
| 25 | pub const Pcg = @import("rand/Pcg.zig"); | 26 | pub const Pcg = @import("rand/Pcg.zig"); |
lib/std/rand/Ascon.zig created+45| ... | @@ -0,0 +1,45 @@ | ||
| 1 | //! CSPRNG based on the Ascon XOFa construction | ||
| 2 | |||
| 3 | const std = @import("std"); | ||
| 4 | const min = std.math.min; | ||
| 5 | const mem = std.mem; | ||
| 6 | const Random = std.rand.Random; | ||
| 7 | const Self = @This(); | ||
| 8 | |||
| 9 | state: std.crypto.core.Ascon(.Little), | ||
| 10 | |||
| 11 | const rate = 8; | ||
| 12 | pub const secret_seed_length = 32; | ||
| 13 | |||
| 14 | /// The seed must be uniform, secret and `secret_seed_length` bytes long. | ||
| 15 | pub fn init(secret_seed: [secret_seed_length]u8) Self { | ||
| 16 | var state = std.crypto.core.Ascon(.Little).initXofA(); | ||
| 17 | var i: usize = 0; | ||
| 18 | while (i + rate <= secret_seed.len) : (i += rate) { | ||
| 19 | state.addBytes(secret_seed[i..][0..rate]); | ||
| 20 | state.permuteR(8); | ||
| 21 | } | ||
| 22 | const left = secret_seed.len - i; | ||
| 23 | if (left > 0) state.addBytes(secret_seed[i..]); | ||
| 24 | state.addByte(0x80, left); | ||
| 25 | state.permute(); | ||
| 26 | return Self{ .state = state }; | ||
| 27 | } | ||
| 28 | |||
| 29 | pub fn random(self: *Self) Random { | ||
| 30 | return Random.init(self, fill); | ||
| 31 | } | ||
| 32 | |||
| 33 | pub fn fill(self: *Self, buf: []u8) void { | ||
| 34 | var i: usize = 0; | ||
| 35 | while (true) { | ||
| 36 | const left = buf.len - i; | ||
| 37 | const n = min(left, rate); | ||
| 38 | self.state.extractBytes(buf[i..][0..n]); | ||
| 39 | if (left == 0) break; | ||
| 40 | self.state.permuteR(8); | ||
| 41 | i += n; | ||
| 42 | } | ||
| 43 | self.state.clear(0, rate); | ||
| 44 | self.state.permuteR(8); | ||
| 45 | } | ||