| 1 | const std = @import("std"); |
| 2 | const crypto = std.crypto; |
| 3 | const debug = std.debug; |
| 4 | const mem = std.mem; |
| 5 | const math = std.math; |
| 6 | const testing = std.testing; |
| 7 | const Ascon = crypto.core.Ascon(.big); |
| 8 | const 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. |
| 21 | pub 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: [8]u8 = @splat(0); |
| 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 | |
| 171 | test "ISAP" { |
| 172 | const k: [16]u8 = @splat(1); |
| 173 | const n: [16]u8 = @splat(2); |
| 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 | } |