| ... | ... | @@ -0,0 +1,246 @@ |
| 1 | const std = @import("std"); |
| 2 | const debug = std.debug; |
| 3 | const mem = std.mem; |
| 4 | const math = std.math; |
| 5 | const testing = std.testing; |
| 6 | |
| 7 | /// ISAPv2 is an authenticated encryption system hardened against side channels and fault attacks. |
| 8 | /// https://csrc.nist.gov/CSRC/media/Projects/lightweight-cryptography/documents/round-2/spec-doc-rnd2/isap-spec-round2.pdf |
| 9 | /// |
| 10 | /// Note that ISAP is not suitable for high-performance applications. |
| 11 | /// |
| 12 | /// However: |
| 13 | /// - if allowing physical access to the device is part of your threat model, |
| 14 | /// - or if you need resistance against microcode/hardware-level side channel attacks, |
| 15 | /// - or if software-induced fault attacks such as rowhammer are a concern, |
| 16 | /// |
| 17 | /// then you may consider ISAP for highly sensitive data. |
| 18 | pub const IsapA128A = struct { |
| 19 | pub const key_length = 16; |
| 20 | pub const nonce_length = 16; |
| 21 | pub const tag_length: usize = 16; |
| 22 | |
| 23 | const iv1 = [_]u8{ 0x01, 0x80, 0x40, 0x01, 0x0c, 0x01, 0x06, 0x0c }; |
| 24 | const iv2 = [_]u8{ 0x02, 0x80, 0x40, 0x01, 0x0c, 0x01, 0x06, 0x0c }; |
| 25 | const iv3 = [_]u8{ 0x03, 0x80, 0x40, 0x01, 0x0c, 0x01, 0x06, 0x0c }; |
| 26 | |
| 27 | const Block = [5]u64; |
| 28 | |
| 29 | block: Block, |
| 30 | |
| 31 | fn round(isap: *IsapA128A, rk: u64) void { |
| 32 | var x = &isap.block; |
| 33 | x[2] ^= rk; |
| 34 | x[0] ^= x[4]; |
| 35 | x[4] ^= x[3]; |
| 36 | x[2] ^= x[1]; |
| 37 | var t = x.*; |
| 38 | x[0] = t[0] ^ ((~t[1]) & t[2]); |
| 39 | x[2] = t[2] ^ ((~t[3]) & t[4]); |
| 40 | x[4] = t[4] ^ ((~t[0]) & t[1]); |
| 41 | x[1] = t[1] ^ ((~t[2]) & t[3]); |
| 42 | x[3] = t[3] ^ ((~t[4]) & t[0]); |
| 43 | x[1] ^= x[0]; |
| 44 | t[1] = x[1]; |
| 45 | x[1] = math.rotr(u64, x[1], 39); |
| 46 | x[3] ^= x[2]; |
| 47 | t[2] = x[2]; |
| 48 | x[2] = math.rotr(u64, x[2], 1); |
| 49 | t[4] = x[4]; |
| 50 | t[2] ^= x[2]; |
| 51 | x[2] = math.rotr(u64, x[2], 5); |
| 52 | t[3] = x[3]; |
| 53 | t[1] ^= x[1]; |
| 54 | x[3] = math.rotr(u64, x[3], 10); |
| 55 | x[0] ^= x[4]; |
| 56 | x[4] = math.rotr(u64, x[4], 7); |
| 57 | t[3] ^= x[3]; |
| 58 | x[2] ^= t[2]; |
| 59 | x[1] = math.rotr(u64, x[1], 22); |
| 60 | t[0] = x[0]; |
| 61 | x[2] = ~x[2]; |
| 62 | x[3] = math.rotr(u64, x[3], 7); |
| 63 | t[4] ^= x[4]; |
| 64 | x[4] = math.rotr(u64, x[4], 34); |
| 65 | x[3] ^= t[3]; |
| 66 | x[1] ^= t[1]; |
| 67 | x[0] = math.rotr(u64, x[0], 19); |
| 68 | x[4] ^= t[4]; |
| 69 | t[0] ^= x[0]; |
| 70 | x[0] = math.rotr(u64, x[0], 9); |
| 71 | x[0] ^= t[0]; |
| 72 | } |
| 73 | |
| 74 | fn p12(isap: *IsapA128A) void { |
| 75 | const rks = [12]u64{ 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b }; |
| 76 | inline for (rks) |rk| { |
| 77 | isap.round(rk); |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | fn p6(isap: *IsapA128A) void { |
| 82 | const rks = [6]u64{ 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b }; |
| 83 | inline for (rks) |rk| { |
| 84 | isap.round(rk); |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | fn p1(isap: *IsapA128A) void { |
| 89 | isap.round(0x4b); |
| 90 | } |
| 91 | |
| 92 | fn absorb(isap: *IsapA128A, m: []const u8) void { |
| 93 | var block = &isap.block; |
| 94 | var i: usize = 0; |
| 95 | while (true) : (i += 8) { |
| 96 | const left = m.len - i; |
| 97 | if (left >= 8) { |
| 98 | block[0] ^= mem.readIntBig(u64, m[i..][0..8]); |
| 99 | isap.p12(); |
| 100 | if (left == 8) { |
| 101 | block[0] ^= 0x8000000000000000; |
| 102 | isap.p12(); |
| 103 | break; |
| 104 | } |
| 105 | } else { |
| 106 | var padded = [_]u8{0} ** 8; |
| 107 | mem.copy(u8, padded[0..left], m[i..]); |
| 108 | padded[left] = 0x80; |
| 109 | block[0] ^= mem.readIntBig(u64, padded[0..]); |
| 110 | isap.p12(); |
| 111 | break; |
| 112 | } |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | fn trickle(k: [16]u8, iv: [8]u8, y: []const u8, comptime out_len: usize) [out_len]u8 { |
| 117 | var isap = IsapA128A{ |
| 118 | .block = Block{ |
| 119 | mem.readIntBig(u64, k[0..8]), |
| 120 | mem.readIntBig(u64, k[8..16]), |
| 121 | mem.readIntBig(u64, iv[0..8]), |
| 122 | 0, |
| 123 | 0, |
| 124 | }, |
| 125 | }; |
| 126 | isap.p12(); |
| 127 | |
| 128 | var i: usize = 0; |
| 129 | while (i < y.len * 8 - 1) : (i += 1) { |
| 130 | const cur_byte_pos = i / 8; |
| 131 | const cur_bit_pos = @truncate(u3, 7 - (i % 8)); |
| 132 | const cur_bit = @as(u64, ((y[cur_byte_pos] >> cur_bit_pos) & 1) << 7); |
| 133 | isap.block[0] ^= cur_bit << 56; |
| 134 | isap.p1(); |
| 135 | } |
| 136 | const cur_bit = @as(u64, (y[y.len - 1] & 1) << 7); |
| 137 | isap.block[0] ^= cur_bit << 56; |
| 138 | isap.p12(); |
| 139 | |
| 140 | var out: [out_len]u8 = undefined; |
| 141 | var j: usize = 0; |
| 142 | while (j < out_len) : (j += 8) { |
| 143 | mem.writeIntBig(u64, out[j..][0..8], isap.block[j / 8]); |
| 144 | } |
| 145 | mem.secureZero(u64, &isap.block); |
| 146 | return out; |
| 147 | } |
| 148 | |
| 149 | fn mac(c: []const u8, ad: []const u8, npub: [16]u8, key: [16]u8) [16]u8 { |
| 150 | var isap = IsapA128A{ |
| 151 | .block = Block{ |
| 152 | mem.readIntBig(u64, npub[0..8]), |
| 153 | mem.readIntBig(u64, npub[8..16]), |
| 154 | mem.readIntBig(u64, iv1[0..]), |
| 155 | 0, |
| 156 | 0, |
| 157 | }, |
| 158 | }; |
| 159 | isap.p12(); |
| 160 | |
| 161 | isap.absorb(ad); |
| 162 | isap.block[4] ^= 1; |
| 163 | isap.absorb(c); |
| 164 | |
| 165 | var y: [16]u8 = undefined; |
| 166 | mem.writeIntBig(u64, y[0..8], isap.block[0]); |
| 167 | mem.writeIntBig(u64, y[8..16], isap.block[1]); |
| 168 | const nb = trickle(key, iv2, y[0..], 16); |
| 169 | isap.block[0] = mem.readIntBig(u64, nb[0..8]); |
| 170 | isap.block[1] = mem.readIntBig(u64, nb[8..16]); |
| 171 | isap.p12(); |
| 172 | |
| 173 | var tag: [16]u8 = undefined; |
| 174 | mem.writeIntBig(u64, tag[0..8], isap.block[0]); |
| 175 | mem.writeIntBig(u64, tag[8..16], isap.block[1]); |
| 176 | mem.secureZero(u64, &isap.block); |
| 177 | return tag; |
| 178 | } |
| 179 | |
| 180 | fn xor(out: []u8, in: []const u8, npub: [16]u8, key: [16]u8) void { |
| 181 | debug.assert(in.len == out.len); |
| 182 | |
| 183 | const nb = trickle(key, iv3, npub[0..], 24); |
| 184 | var isap = IsapA128A{ |
| 185 | .block = Block{ |
| 186 | mem.readIntBig(u64, nb[0..8]), |
| 187 | mem.readIntBig(u64, nb[8..16]), |
| 188 | mem.readIntBig(u64, nb[16..24]), |
| 189 | mem.readIntBig(u64, npub[0..8]), |
| 190 | mem.readIntBig(u64, npub[8..16]), |
| 191 | }, |
| 192 | }; |
| 193 | isap.p6(); |
| 194 | |
| 195 | var i: usize = 0; |
| 196 | while (true) : (i += 8) { |
| 197 | const left = in.len - i; |
| 198 | if (left >= 8) { |
| 199 | mem.writeIntNative(u64, out[i..][0..8], mem.bigToNative(u64, isap.block[0]) ^ mem.readIntNative(u64, in[i..][0..8])); |
| 200 | if (left == 8) { |
| 201 | break; |
| 202 | } |
| 203 | isap.p6(); |
| 204 | } else { |
| 205 | var pad = [_]u8{0} ** 8; |
| 206 | mem.copy(u8, pad[0..left], in[i..][0..left]); |
| 207 | mem.writeIntNative(u64, pad[i..][0..8], mem.bigToNative(u64, isap.block[0]) ^ mem.readIntNative(u64, pad[i..][0..8])); |
| 208 | mem.copy(u8, out[i..][0..left], pad[0..left]); |
| 209 | break; |
| 210 | } |
| 211 | } |
| 212 | mem.secureZero(u64, &isap.block); |
| 213 | } |
| 214 | |
| 215 | pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) void { |
| 216 | xor(c, m, npub, key); |
| 217 | tag.* = mac(c, ad, npub, key); |
| 218 | } |
| 219 | |
| 220 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) !void { |
| 221 | var computed_tag = mac(c, ad, npub, key); |
| 222 | var acc: u8 = 0; |
| 223 | for (computed_tag) |_, j| { |
| 224 | acc |= (computed_tag[j] ^ tag[j]); |
| 225 | } |
| 226 | mem.secureZero(u8, &computed_tag); |
| 227 | if (acc != 0) { |
| 228 | return error.AuthenticationFailed; |
| 229 | } |
| 230 | xor(m, c, npub, key); |
| 231 | } |
| 232 | }; |
| 233 | |
| 234 | test "ISAP" { |
| 235 | const k = [_]u8{1} ** 16; |
| 236 | const n = [_]u8{2} ** 16; |
| 237 | var tag: [16]u8 = undefined; |
| 238 | const ad = "ad"; |
| 239 | var msg = "test"; |
| 240 | var c: [msg.len]u8 = undefined; |
| 241 | IsapA128A.encrypt(c[0..], &tag, msg[0..], ad, n, k); |
| 242 | testing.expect(mem.eql(u8, &[_]u8{ 0x8f, 0x68, 0x03, 0x8d }, c[0..])); |
| 243 | testing.expect(mem.eql(u8, &[_]u8{ 0x6c, 0x25, 0xe8, 0xe2, 0xe1, 0x1f, 0x38, 0xe9, 0x80, 0x75, 0xde, 0xd5, 0x2d, 0xb2, 0x31, 0x82 }, tag[0..])); |
| 244 | try IsapA128A.decrypt(c[0..], c[0..], tag, ad, n, k); |
| 245 | testing.expect(mem.eql(u8, msg, c[0..])); |
| 246 | } |