Skip to content

Commit 1969dfb

Browse files
committed
std: add AEAD modes for gimli
1 parent b495512 commit 1969dfb

File tree

1 file changed

+219
-1
lines changed

1 file changed

+219
-1
lines changed

lib/std/crypto/gimli.zig

+219-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ pub const State = struct {
1919
pub const BLOCKBYTES = 48;
2020
pub const RATE = 16;
2121

22-
// TODO: https://github.com/ziglang/zig/issues/2673#issuecomment-501763017
2322
data: [BLOCKBYTES / 4]u32,
2423

2524
const Self = @This();
@@ -168,3 +167,222 @@ test "hash" {
168167
hash(&md, &msg);
169168
htest.assertEqual("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44", &md);
170169
}
170+
171+
pub const Aead = struct {
172+
/// ad: Associated Data
173+
/// npub: public nonce
174+
/// k: private key
175+
fn init(ad: []const u8, npub: [16]u8, k: [32]u8) State {
176+
var state = State{
177+
.data = undefined,
178+
};
179+
const buf = state.toSlice();
180+
181+
// Gimli-Cipher initializes a 48-byte Gimli state to a 16-byte nonce
182+
// followed by a 32-byte key.
183+
assert(npub.len + k.len == State.BLOCKBYTES);
184+
std.mem.copy(u8, buf[0..npub.len], &npub);
185+
std.mem.copy(u8, buf[npub.len .. npub.len + k.len], &k);
186+
187+
// It then applies the Gimli permutation.
188+
state.permute();
189+
190+
{
191+
// Gimli-Cipher then handles each block of associated data, including
192+
// exactly one final non-full block, in the same way as Gimli-Hash.
193+
var data = ad;
194+
while (data.len >= State.RATE) : (data = data[State.RATE..]) {
195+
for (buf[0..State.RATE]) |*p, i| {
196+
p.* ^= data[i];
197+
}
198+
state.permute();
199+
}
200+
for (buf[0..data.len]) |*p, i| {
201+
p.* ^= data[i];
202+
}
203+
204+
// XOR 1 into the next byte of the state
205+
buf[data.len] ^= 1;
206+
// XOR 1 into the last byte of the state, position 47.
207+
buf[buf.len - 1] ^= 1;
208+
209+
state.permute();
210+
}
211+
212+
return state;
213+
}
214+
215+
/// c: ciphertext: output buffer should be of size m.len
216+
/// at: authentication tag: output MAC
217+
/// m: message
218+
/// ad: Associated Data
219+
/// npub: public nonce
220+
/// k: private key
221+
pub fn encrypt(c: []u8, at: *[State.RATE]u8, m: []const u8, ad: []const u8, npub: [16]u8, k: [32]u8) void {
222+
assert(c.len == m.len);
223+
224+
var state = Aead.init(ad, npub, k);
225+
const buf = state.toSlice();
226+
227+
// Gimli-Cipher then handles each block of plaintext, including
228+
// exactly one final non-full block, in the same way as Gimli-Hash.
229+
// Whenever a plaintext byte is XORed into a state byte, the new state
230+
// byte is output as ciphertext.
231+
var in = m;
232+
var out = c;
233+
while (in.len >= State.RATE) : ({
234+
in = in[State.RATE..];
235+
out = out[State.RATE..];
236+
}) {
237+
for (buf[0..State.RATE]) |*p, i| {
238+
p.* ^= in[i];
239+
out[i] = p.*;
240+
}
241+
state.permute();
242+
}
243+
for (buf[0..in.len]) |*p, i| {
244+
p.* ^= in[i];
245+
out[i] = p.*;
246+
}
247+
248+
// XOR 1 into the next byte of the state
249+
buf[in.len] ^= 1;
250+
// XOR 1 into the last byte of the state, position 47.
251+
buf[buf.len - 1] ^= 1;
252+
253+
state.permute();
254+
255+
// After the final non-full block of plaintext, the first 16 bytes
256+
// of the state are output as an authentication tag.
257+
@memcpy(at, buf.ptr, State.RATE);
258+
}
259+
260+
/// m: message: output buffer should be of size c.len
261+
/// c: ciphertext
262+
/// at: authentication tag
263+
/// ad: Associated Data
264+
/// npub: public nonce
265+
/// k: private key
266+
/// NOTE: the check of the authentication tag is currently not done in constant time
267+
pub fn decrypt(m: []u8, c: []const u8, at: [State.RATE]u8, ad: []u8, npub: [16]u8, k: [32]u8) !void {
268+
assert(c.len == m.len);
269+
270+
var state = Aead.init(ad, npub, k);
271+
const buf = state.toSlice();
272+
273+
var in = c;
274+
var out = m;
275+
while (in.len >= State.RATE) : ({
276+
in = in[State.RATE..];
277+
out = out[State.RATE..];
278+
}) {
279+
for (buf[0..State.RATE]) |*p, i| {
280+
out[i] = p.* ^ in[i];
281+
p.* = in[i];
282+
}
283+
state.permute();
284+
}
285+
for (buf[0..in.len]) |*p, i| {
286+
out[i] = p.* ^ in[i];
287+
p.* = in[i];
288+
}
289+
290+
// XOR 1 into the next byte of the state
291+
buf[in.len] ^= 1;
292+
// XOR 1 into the last byte of the state, position 47.
293+
buf[buf.len - 1] ^= 1;
294+
295+
state.permute();
296+
297+
// After the final non-full block of plaintext, the first 16 bytes
298+
// of the state are the authentication tag.
299+
// TODO: use a constant-time equality check here, see https://github.com/ziglang/zig/issues/1776
300+
if (!mem.eql(u8, buf[0..State.RATE], &at)) {
301+
@memset(m.ptr, undefined, m.len);
302+
return error.InvalidMessage;
303+
}
304+
}
305+
};
306+
307+
test "cipher" {
308+
var key: [32]u8 = undefined;
309+
try std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
310+
var nonce: [16]u8 = undefined;
311+
try std.fmt.hexToBytes(&nonce, "000102030405060708090A0B0C0D0E0F");
312+
{ // test vector (1) from NIST KAT submission.
313+
const ad: [0]u8 = undefined;
314+
const pt: [0]u8 = undefined;
315+
316+
var ct: [pt.len]u8 = undefined;
317+
var at: [16]u8 = undefined;
318+
Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
319+
htest.assertEqual("", &ct);
320+
htest.assertEqual("14DA9BB7120BF58B985A8E00FDEBA15B", &at);
321+
322+
var pt2: [pt.len]u8 = undefined;
323+
try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
324+
testing.expectEqualSlices(u8, &pt, &pt2);
325+
}
326+
{ // test vector (34) from NIST KAT submission.
327+
const ad: [0]u8 = undefined;
328+
var pt: [2 / 2]u8 = undefined;
329+
try std.fmt.hexToBytes(&pt, "00");
330+
331+
var ct: [pt.len]u8 = undefined;
332+
var at: [16]u8 = undefined;
333+
Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
334+
htest.assertEqual("7F", &ct);
335+
htest.assertEqual("80492C317B1CD58A1EDC3A0D3E9876FC", &at);
336+
337+
var pt2: [pt.len]u8 = undefined;
338+
try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
339+
testing.expectEqualSlices(u8, &pt, &pt2);
340+
}
341+
{ // test vector (106) from NIST KAT submission.
342+
var ad: [12 / 2]u8 = undefined;
343+
try std.fmt.hexToBytes(&ad, "000102030405");
344+
var pt: [6 / 2]u8 = undefined;
345+
try std.fmt.hexToBytes(&pt, "000102");
346+
347+
var ct: [pt.len]u8 = undefined;
348+
var at: [16]u8 = undefined;
349+
Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
350+
htest.assertEqual("484D35", &ct);
351+
htest.assertEqual("030BBEA23B61C00CED60A923BDCF9147", &at);
352+
353+
var pt2: [pt.len]u8 = undefined;
354+
try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
355+
testing.expectEqualSlices(u8, &pt, &pt2);
356+
}
357+
{ // test vector (790) from NIST KAT submission.
358+
var ad: [60 / 2]u8 = undefined;
359+
try std.fmt.hexToBytes(&ad, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D");
360+
var pt: [46 / 2]u8 = undefined;
361+
try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F10111213141516");
362+
363+
var ct: [pt.len]u8 = undefined;
364+
var at: [16]u8 = undefined;
365+
Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
366+
htest.assertEqual("6815B4A0ECDAD01596EAD87D9E690697475D234C6A13D1", &ct);
367+
htest.assertEqual("DFE23F1642508290D68245279558B2FB", &at);
368+
369+
var pt2: [pt.len]u8 = undefined;
370+
try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
371+
testing.expectEqualSlices(u8, &pt, &pt2);
372+
}
373+
{ // test vector (1057) from NIST KAT submission.
374+
const ad: [0]u8 = undefined;
375+
var pt: [64 / 2]u8 = undefined;
376+
try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
377+
378+
var ct: [pt.len]u8 = undefined;
379+
var at: [16]u8 = undefined;
380+
Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
381+
htest.assertEqual("7F8A2CF4F52AA4D6B2E74105C30A2777B9D0C8AEFDD555DE35861BD3011F652F", &ct);
382+
htest.assertEqual("7256456FA935AC34BBF55AE135F33257", &at);
383+
384+
var pt2: [pt.len]u8 = undefined;
385+
try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
386+
testing.expectEqualSlices(u8, &pt, &pt2);
387+
}
388+
}

0 commit comments

Comments
 (0)