std/crypto: add ISAPv2 (ISAP-A-128a) AEAD

We currently have ciphers optimized for performance, for
compatibility, for size and for specific CPUs.

However we lack a class of ciphers that is becoming increasingly
important, as Zig is being used for embedded systems, but also as
hardware-level side channels keep being found on (Intel) CPUs.

Here is ISAPv2, a construction specifically designed for resilience
against leakage and fault attacks.

ISAPv2 is obviously not optimized for performance, but can be an
option for highly sensitive data, when the runtime environment cannot
be trusted.
master
Frank Denis 2020-11-11 01:22:21 +01:00 committed by Andrew Kelley
parent 4df75645ce
commit f9d209787b
3 changed files with 250 additions and 0 deletions

View File

@ -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");

View File

@ -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 {

246
lib/std/crypto/isap.zig Normal file
View File

@ -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..]));
}