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}