authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-02-05 18:13:28-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-02-05 18:13:28-05:00
log5cf30b6791243f1df55c7730399d1a772c85305f
tree6b51cfe12e8c9e1828f91a5f142273e169a8e4bd
parente1f4f44dff238918d0e4f1fa35d56de50d1c22bb
parentef4ecaa46212c34a124eca2da5dc41859a7de40a
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #4369 from daurnimator/gimli

std.crypto.gimli enhancements

3 files changed, 248 insertions(+), 1 deletions(-)

lib/std/crypto/benchmark.zig+1
...@@ -23,6 +23,7 @@ const hashes = [_]Crypto{...@@ -23,6 +23,7 @@ const hashes = [_]Crypto{
23 Crypto{ .ty = crypto.Sha512, .name = "sha512" },23 Crypto{ .ty = crypto.Sha512, .name = "sha512" },
24 Crypto{ .ty = crypto.Sha3_256, .name = "sha3-256" },24 Crypto{ .ty = crypto.Sha3_256, .name = "sha3-256" },
25 Crypto{ .ty = crypto.Sha3_512, .name = "sha3-512" },25 Crypto{ .ty = crypto.Sha3_512, .name = "sha3-512" },
26 Crypto{ .ty = crypto.gimli.Hash, .name = "gimli-hash" },
26 Crypto{ .ty = crypto.Blake2s256, .name = "blake2s" },27 Crypto{ .ty = crypto.Blake2s256, .name = "blake2s" },
27 Crypto{ .ty = crypto.Blake2b512, .name = "blake2b" },28 Crypto{ .ty = crypto.Blake2b512, .name = "blake2b" },
28 Crypto{ .ty = crypto.Blake3, .name = "blake3" },29 Crypto{ .ty = crypto.Blake3, .name = "blake3" },
lib/std/crypto/gimli.zig+221-1
...@@ -19,7 +19,6 @@ pub const State = struct {...@@ -19,7 +19,6 @@ pub const State = struct {
19 pub const BLOCKBYTES = 48;19 pub const BLOCKBYTES = 48;
20 pub const RATE = 16;20 pub const RATE = 16;
2121
22 // TODO: https://github.com/ziglang/zig/issues/2673#issuecomment-501763017
23 data: [BLOCKBYTES / 4]u32,22 data: [BLOCKBYTES / 4]u32,
2423
25 const Self = @This();24 const Self = @This();
...@@ -134,6 +133,8 @@ pub const Hash = struct {...@@ -134,6 +133,8 @@ pub const Hash = struct {
134 }133 }
135 }134 }
136135
136 pub const digest_length = 32;
137
137 /// Finish the current hashing operation, writing the hash to `out`138 /// Finish the current hashing operation, writing the hash to `out`
138 ///139 ///
139 /// From 4.9 "Application to hashing"140 /// From 4.9 "Application to hashing"
...@@ -166,3 +167,222 @@ test "hash" {...@@ -166,3 +167,222 @@ test "hash" {
166 hash(&md, &msg);167 hash(&md, &msg);
167 htest.assertEqual("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44", &md);168 htest.assertEqual("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44", &md);
168}169}
170
171pub 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 std.mem.copy(u8, at, buf[0..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
307test "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}
lib/std/rand.zig+26
...@@ -733,6 +733,32 @@ test "xoroshiro sequence" {...@@ -733,6 +733,32 @@ test "xoroshiro sequence" {
733 }733 }
734}734}
735735
736// Gimli
737//
738// CSPRNG
739pub const Gimli = struct {
740 random: Random,
741 state: std.crypto.gimli.State,
742
743 pub fn init(init_s: u64) Gimli {
744 var self = Gimli{
745 .random = Random{ .fillFn = fill },
746 .state = std.crypto.gimli.State{
747 .data = [_]u32{0} ** (std.crypto.gimli.State.BLOCKBYTES / 4),
748 },
749 };
750 self.state.data[0] = @truncate(u32, init_s >> 32);
751 self.state.data[1] = @truncate(u32, init_s);
752 return self;
753 }
754
755 fn fill(r: *Random, buf: []u8) void {
756 const self = @fieldParentPtr(Gimli, "random", r);
757
758 self.state.squeeze(buf);
759 }
760};
761
736// ISAAC64 - http://www.burtleburtle.net/bob/rand/isaacafa.html762// ISAAC64 - http://www.burtleburtle.net/bob/rand/isaacafa.html
737//763//
738// CSPRNG764// CSPRNG