master
  1const std = @import("std");
  2const crypto = std.crypto;
  3const debug = std.debug;
  4const mem = std.mem;
  5const math = std.math;
  6const testing = std.testing;
  7const Ascon = crypto.core.Ascon(.big);
  8const AuthenticationError = crypto.errors.AuthenticationError;
  9
 10/// ISAPv2 is an authenticated encryption system hardened against side channels and fault attacks.
 11/// https://csrc.nist.gov/CSRC/media/Projects/lightweight-cryptography/documents/round-2/spec-doc-rnd2/isap-spec-round2.pdf
 12///
 13/// Note that ISAP is not suitable for high-performance applications.
 14///
 15/// However:
 16/// - if allowing physical access to the device is part of your threat model,
 17/// - or if you need resistance against microcode/hardware-level side channel attacks,
 18/// - or if software-induced fault attacks such as rowhammer are a concern,
 19///
 20/// then you may consider ISAP for highly sensitive data.
 21pub const IsapA128A = struct {
 22    pub const key_length = 16;
 23    pub const nonce_length = 16;
 24    pub const tag_length: usize = 16;
 25
 26    const iv1 = [_]u8{ 0x01, 0x80, 0x40, 0x01, 0x0c, 0x01, 0x06, 0x0c };
 27    const iv2 = [_]u8{ 0x02, 0x80, 0x40, 0x01, 0x0c, 0x01, 0x06, 0x0c };
 28    const iv3 = [_]u8{ 0x03, 0x80, 0x40, 0x01, 0x0c, 0x01, 0x06, 0x0c };
 29
 30    st: Ascon,
 31
 32    fn absorb(isap: *IsapA128A, m: []const u8) void {
 33        var i: usize = 0;
 34        while (true) : (i += 8) {
 35            const left = m.len - i;
 36            if (left >= 8) {
 37                isap.st.addBytes(m[i..][0..8]);
 38                isap.st.permute();
 39                if (left == 8) {
 40                    isap.st.addByte(0x80, 0);
 41                    isap.st.permute();
 42                    break;
 43                }
 44            } else {
 45                var padded = [_]u8{0} ** 8;
 46                @memcpy(padded[0..left], m[i..]);
 47                padded[left] = 0x80;
 48                isap.st.addBytes(&padded);
 49                isap.st.permute();
 50                break;
 51            }
 52        }
 53    }
 54
 55    fn trickle(k: [16]u8, iv: [8]u8, y: []const u8, comptime out_len: usize) [out_len]u8 {
 56        var isap = IsapA128A{
 57            .st = Ascon.initFromWords(.{
 58                mem.readInt(u64, k[0..8], .big),
 59                mem.readInt(u64, k[8..16], .big),
 60                mem.readInt(u64, iv[0..8], .big),
 61                0,
 62                0,
 63            }),
 64        };
 65        isap.st.permute();
 66
 67        var i: usize = 0;
 68        while (i < y.len * 8 - 1) : (i += 1) {
 69            const cur_byte_pos = i / 8;
 70            const cur_bit_pos: u3 = @truncate(7 - (i % 8));
 71            const cur_bit = ((y[cur_byte_pos] >> cur_bit_pos) & 1) << 7;
 72            isap.st.addByte(cur_bit, 0);
 73            isap.st.permuteR(1);
 74        }
 75        const cur_bit = (y[y.len - 1] & 1) << 7;
 76        isap.st.addByte(cur_bit, 0);
 77        isap.st.permute();
 78
 79        var out: [out_len]u8 = undefined;
 80        isap.st.extractBytes(&out);
 81        isap.st.secureZero();
 82        return out;
 83    }
 84
 85    fn mac(c: []const u8, ad: []const u8, npub: [16]u8, key: [16]u8) [16]u8 {
 86        var isap = IsapA128A{
 87            .st = Ascon.initFromWords(.{
 88                mem.readInt(u64, npub[0..8], .big),
 89                mem.readInt(u64, npub[8..16], .big),
 90                mem.readInt(u64, iv1[0..], .big),
 91                0,
 92                0,
 93            }),
 94        };
 95        isap.st.permute();
 96
 97        isap.absorb(ad);
 98        isap.st.addByte(1, Ascon.block_bytes - 1);
 99        isap.absorb(c);
100
101        var y: [16]u8 = undefined;
102        isap.st.extractBytes(&y);
103        const nb = trickle(key, iv2, y[0..], 16);
104        isap.st.setBytes(&nb);
105        isap.st.permute();
106
107        var tag: [16]u8 = undefined;
108        isap.st.extractBytes(&tag);
109        isap.st.secureZero();
110        return tag;
111    }
112
113    fn xor(out: []u8, in: []const u8, npub: [16]u8, key: [16]u8) void {
114        debug.assert(in.len == out.len);
115
116        const nb = trickle(key, iv3, npub[0..], 24);
117        var isap = IsapA128A{
118            .st = Ascon.initFromWords(.{
119                mem.readInt(u64, nb[0..8], .big),
120                mem.readInt(u64, nb[8..16], .big),
121                mem.readInt(u64, nb[16..24], .big),
122                mem.readInt(u64, npub[0..8], .big),
123                mem.readInt(u64, npub[8..16], .big),
124            }),
125        };
126        isap.st.permuteR(6);
127
128        var i: usize = 0;
129        while (true) : (i += 8) {
130            const left = in.len - i;
131            if (left >= 8) {
132                isap.st.xorBytes(out[i..][0..8], in[i..][0..8]);
133                if (left == 8) {
134                    break;
135                }
136                isap.st.permuteR(6);
137            } else {
138                isap.st.xorBytes(out[i..], in[i..]);
139                break;
140            }
141        }
142        isap.st.secureZero();
143    }
144
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 {
146        xor(c, m, npub, key);
147        tag.* = mac(c, ad, npub, key);
148    }
149
150    /// `m`: Message
151    /// `c`: Ciphertext
152    /// `tag`: Authentication tag
153    /// `ad`: Associated data
154    /// `npub`: Public nonce
155    /// `k`: Private key
156    /// Asserts `c.len == m.len`.
157    ///
158    /// Contents of `m` are undefined if an error is returned.
159    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 {
160        var computed_tag = mac(c, ad, npub, key);
161        const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
162        if (!verify) {
163            crypto.secureZero(u8, &computed_tag);
164            @memset(m, undefined);
165            return error.AuthenticationFailed;
166        }
167        xor(m, c, npub, key);
168    }
169};
170
171test "ISAP" {
172    const k = [_]u8{1} ** 16;
173    const n = [_]u8{2} ** 16;
174    var tag: [16]u8 = undefined;
175    const ad = "ad";
176    var msg = "test";
177    var c: [msg.len]u8 = undefined;
178    IsapA128A.encrypt(c[0..], &tag, msg[0..], ad, n, k);
179    try testing.expect(mem.eql(u8, &[_]u8{ 0x8f, 0x68, 0x03, 0x8d }, c[0..]));
180    try testing.expect(mem.eql(u8, &[_]u8{ 0x6c, 0x25, 0xe8, 0xe2, 0xe1, 0x1f, 0x38, 0xe9, 0x80, 0x75, 0xde, 0xd5, 0x2d, 0xb2, 0x31, 0x82 }, tag[0..]));
181    try IsapA128A.decrypt(c[0..], c[0..], tag, ad, n, k);
182    try testing.expect(mem.eql(u8, msg, c[0..]));
183}