1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
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]);
}
std.crypto.utils.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]);
std.crypto.utils.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;
}
}
std.crypto.utils.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]);
}
std.crypto.utils.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..]));
}
|