diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index a30ec9728e..6eb934473f 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -23,6 +23,8 @@ pub const aead = struct { pub const XChaCha20Poly1305 = @import("crypto/chacha20.zig").XChacha20Poly1305; }; + pub const isap = @import("crypto/isap.zig"); + pub const salsa_poly = struct { pub const XSalsa20Poly1305 = @import("crypto/salsa20.zig").XSalsa20Poly1305; }; @@ -157,6 +159,7 @@ test "crypto" { _ = @import("crypto/chacha20.zig"); _ = @import("crypto/gimli.zig"); _ = @import("crypto/hmac.zig"); + _ = @import("crypto/isap.zig"); _ = @import("crypto/md5.zig"); _ = @import("crypto/modes.zig"); _ = @import("crypto/pbkdf2.zig"); diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index e23622b519..7a0253861b 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -208,6 +208,7 @@ const aeads = [_]Crypto{ Crypto{ .ty = crypto.aead.aegis.Aegis256, .name = "aegis-256" }, Crypto{ .ty = crypto.aead.aes_gcm.Aes128Gcm, .name = "aes128-gcm" }, Crypto{ .ty = crypto.aead.aes_gcm.Aes256Gcm, .name = "aes256-gcm" }, + Crypto{ .ty = crypto.aead.isap.IsapA128A, .name = "isapa128a" }, }; pub fn benchmarkAead(comptime Aead: anytype, comptime bytes: comptime_int) !u64 { diff --git a/lib/std/crypto/isap.zig b/lib/std/crypto/isap.zig new file mode 100644 index 0000000000..fe392bd569 --- /dev/null +++ b/lib/std/crypto/isap.zig @@ -0,0 +1,246 @@ +const std = @import("std"); +const debug = std.debug; +const mem = std.mem; +const math = std.math; +const testing = std.testing; + +/// 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 +/// +/// Note that ISAP is not suitable for high-performance applications. +/// +/// However: +/// - if allowing physical access to the device is part of your threat model, +/// - or if you need resistance against microcode/hardware-level side channel attacks, +/// - or if software-induced fault attacks such as rowhammer are a concern, +/// +/// then you may consider ISAP for highly sensitive data. +pub const IsapA128A = struct { + pub const key_length = 16; + pub const nonce_length = 16; + pub const tag_length: usize = 16; + + const iv1 = [_]u8{ 0x01, 0x80, 0x40, 0x01, 0x0c, 0x01, 0x06, 0x0c }; + 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); + } + + 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(); + if (left == 8) { + block[0] ^= 0x8000000000000000; + isap.p12(); + 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(); + break; + } + } + } + + fn trickle(k: [16]u8, iv: [8]u8, y: []const u8, comptime out_len: usize) [out_len]u8 { + var isap = IsapA128A{ + .block = Block{ + mem.readIntBig(u64, k[0..8]), + mem.readIntBig(u64, k[8..16]), + mem.readIntBig(u64, iv[0..8]), + 0, + 0, + }, + }; + isap.p12(); + + 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 = @as(u64, (y[y.len - 1] & 1) << 7); + isap.block[0] ^= cur_bit << 56; + isap.p12(); + + 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]); + } + mem.secureZero(u64, &isap.block); + return out; + } + + fn mac(c: []const u8, ad: []const u8, npub: [16]u8, key: [16]u8) [16]u8 { + var isap = IsapA128A{ + .block = Block{ + mem.readIntBig(u64, npub[0..8]), + mem.readIntBig(u64, npub[8..16]), + mem.readIntBig(u64, iv1[0..]), + 0, + 0, + }, + }; + isap.p12(); + + isap.absorb(ad); + isap.block[4] ^= 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]); + 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(); + + var tag: [16]u8 = undefined; + mem.writeIntBig(u64, tag[0..8], isap.block[0]); + mem.writeIntBig(u64, tag[8..16], isap.block[1]); + mem.secureZero(u64, &isap.block); + return tag; + } + + fn xor(out: []u8, in: []const u8, npub: [16]u8, key: [16]u8) void { + debug.assert(in.len == out.len); + + const nb = trickle(key, iv3, npub[0..], 24); + var isap = IsapA128A{ + .block = Block{ + 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(); + + 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])); + if (left == 8) { + break; + } + isap.p6(); + } 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]); + break; + } + } + mem.secureZero(u64, &isap.block); + } + + pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) void { + xor(c, m, npub, key); + tag.* = mac(c, ad, npub, key); + } + + pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) !void { + var computed_tag = mac(c, ad, npub, key); + var acc: u8 = 0; + for (computed_tag) |_, j| { + acc |= (computed_tag[j] ^ tag[j]); + } + mem.secureZero(u8, &computed_tag); + if (acc != 0) { + return error.AuthenticationFailed; + } + xor(m, c, npub, key); + } +}; + +test "ISAP" { + const k = [_]u8{1} ** 16; + const n = [_]u8{2} ** 16; + var tag: [16]u8 = undefined; + const ad = "ad"; + var msg = "test"; + var c: [msg.len]u8 = undefined; + IsapA128A.encrypt(c[0..], &tag, msg[0..], ad, n, k); + testing.expect(mem.eql(u8, &[_]u8{ 0x8f, 0x68, 0x03, 0x8d }, c[0..])); + testing.expect(mem.eql(u8, &[_]u8{ 0x6c, 0x25, 0xe8, 0xe2, 0xe1, 0x1f, 0x38, 0xe9, 0x80, 0x75, 0xde, 0xd5, 0x2d, 0xb2, 0x31, 0x82 }, tag[0..])); + try IsapA128A.decrypt(c[0..], c[0..], tag, ad, n, k); + testing.expect(mem.eql(u8, msg, c[0..])); +}