authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2020-11-11 01:22:21+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-11-16 16:02:19-08:00
logf9d209787bbf55fac828508f18396a4cf9b6b41c
treeedafd4df24070885888839c5770eb0f85fd0125c
parent4df75645ce7d9f82b1b6fb5856c03bf145fabaa1

std/crypto: add ISAPv2 (ISAP-A-128a) AEAD

We currently have ciphers optimized for performance, for compatibility, for size and for specific CPUs. However we lack a class of ciphers that is becoming increasingly important, as Zig is being used for embedded systems, but also as hardware-level side channels keep being found on (Intel) CPUs. Here is ISAPv2, a construction specifically designed for resilience against leakage and fault attacks. ISAPv2 is obviously not optimized for performance, but can be an option for highly sensitive data, when the runtime environment cannot be trusted.

3 files changed, 250 insertions(+), 0 deletions(-)

lib/std/crypto.zig+3
...@@ -23,6 +23,8 @@ pub const aead = struct {...@@ -23,6 +23,8 @@ pub const aead = struct {
23 pub const XChaCha20Poly1305 = @import("crypto/chacha20.zig").XChacha20Poly1305;23 pub const XChaCha20Poly1305 = @import("crypto/chacha20.zig").XChacha20Poly1305;
24 };24 };
2525
26 pub const isap = @import("crypto/isap.zig");
27
26 pub const salsa_poly = struct {28 pub const salsa_poly = struct {
27 pub const XSalsa20Poly1305 = @import("crypto/salsa20.zig").XSalsa20Poly1305;29 pub const XSalsa20Poly1305 = @import("crypto/salsa20.zig").XSalsa20Poly1305;
28 };30 };
...@@ -157,6 +159,7 @@ test "crypto" {...@@ -157,6 +159,7 @@ test "crypto" {
157 _ = @import("crypto/chacha20.zig");159 _ = @import("crypto/chacha20.zig");
158 _ = @import("crypto/gimli.zig");160 _ = @import("crypto/gimli.zig");
159 _ = @import("crypto/hmac.zig");161 _ = @import("crypto/hmac.zig");
162 _ = @import("crypto/isap.zig");
160 _ = @import("crypto/md5.zig");163 _ = @import("crypto/md5.zig");
161 _ = @import("crypto/modes.zig");164 _ = @import("crypto/modes.zig");
162 _ = @import("crypto/pbkdf2.zig");165 _ = @import("crypto/pbkdf2.zig");
lib/std/crypto/benchmark.zig+1
...@@ -208,6 +208,7 @@ const aeads = [_]Crypto{...@@ -208,6 +208,7 @@ const aeads = [_]Crypto{
208 Crypto{ .ty = crypto.aead.aegis.Aegis256, .name = "aegis-256" },208 Crypto{ .ty = crypto.aead.aegis.Aegis256, .name = "aegis-256" },
209 Crypto{ .ty = crypto.aead.aes_gcm.Aes128Gcm, .name = "aes128-gcm" },209 Crypto{ .ty = crypto.aead.aes_gcm.Aes128Gcm, .name = "aes128-gcm" },
210 Crypto{ .ty = crypto.aead.aes_gcm.Aes256Gcm, .name = "aes256-gcm" },210 Crypto{ .ty = crypto.aead.aes_gcm.Aes256Gcm, .name = "aes256-gcm" },
211 Crypto{ .ty = crypto.aead.isap.IsapA128A, .name = "isapa128a" },
211};212};
212213
213pub fn benchmarkAead(comptime Aead: anytype, comptime bytes: comptime_int) !u64 {214pub fn benchmarkAead(comptime Aead: anytype, comptime bytes: comptime_int) !u64 {
lib/std/crypto/isap.zig created+246
...@@ -0,0 +1,246 @@
1const std = @import("std");
2const debug = std.debug;
3const mem = std.mem;
4const math = std.math;
5const 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.
18pub 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
234test "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}