authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-12-30 17:39:46-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2019-12-30 17:39:46-05:00
log34b4538d7b15b4f1731228fc9e40b63001771956
tree8210c1bdd624b14a60762684c920790424d33069
parentc70a673c6eb92383d2a8e026374b4b23647db059
parentab093228f931f71ca55d526bbce18835c9dea852
signature Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #4011 from lukechampine/chacha20poly1305

crypto: Implement ChaCha20-Poly1305 AEAD (fixed)

1 files changed, 203 insertions(+), 0 deletions(-)

lib/std/crypto/chacha20.zig+203
...@@ -7,6 +7,7 @@ const assert = std.debug.assert;...@@ -7,6 +7,7 @@ const assert = std.debug.assert;
7const testing = std.testing;7const testing = std.testing;
8const builtin = @import("builtin");8const builtin = @import("builtin");
9const maxInt = std.math.maxInt;9const maxInt = std.math.maxInt;
10const Poly1305 = std.crypto.Poly1305;
1011
11const QuarterRound = struct {12const QuarterRound = struct {
12 a: usize,13 a: usize,
...@@ -434,3 +435,205 @@ test "crypto.chacha20 test vector 5" {...@@ -434,3 +435,205 @@ test "crypto.chacha20 test vector 5" {
434 chaCha20With64BitNonce(result[0..], input[0..], 0, key, nonce);435 chaCha20With64BitNonce(result[0..], input[0..], 0, key, nonce);
435 testing.expectEqualSlices(u8, &expected_result, &result);436 testing.expectEqualSlices(u8, &expected_result, &result);
436}437}
438
439pub const chacha20poly1305_tag_size = 16;
440
441pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void {
442 assert(dst.len >= plaintext.len + chacha20poly1305_tag_size);
443
444 // derive poly1305 key
445 var polyKey = [_]u8{0} ** 32;
446 chaCha20IETF(polyKey[0..], polyKey[0..], 0, key, nonce);
447
448 // encrypt plaintext
449 chaCha20IETF(dst[0..plaintext.len], plaintext, 1, key, nonce);
450
451 // construct mac
452 var mac = Poly1305.init(polyKey[0..]);
453 mac.update(data);
454 if (data.len % 16 != 0) {
455 const zeros = [_]u8{0} ** 16;
456 const padding = 16 - (data.len % 16);
457 mac.update(zeros[0..padding]);
458 }
459 mac.update(dst[0..plaintext.len]);
460 if (plaintext.len % 16 != 0) {
461 const zeros = [_]u8{0} ** 16;
462 const padding = 16 - (plaintext.len % 16);
463 mac.update(zeros[0..padding]);
464 }
465 var lens: [16]u8 = undefined;
466 mem.writeIntSliceLittle(u64, lens[0..8], data.len);
467 mem.writeIntSliceLittle(u64, lens[8..16], plaintext.len);
468 mac.update(lens[0..]);
469 mac.final(dst[plaintext.len..]);
470}
471
472/// Verifies and decrypts an authenticated message produced by chacha20poly1305Seal.
473pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) !void {
474 if (msgAndTag.len < chacha20poly1305_tag_size) {
475 return error.InvalidMessage;
476 }
477
478 // split ciphertext and tag
479 assert(dst.len >= msgAndTag.len - chacha20poly1305_tag_size);
480 var ciphertext = msgAndTag[0 .. msgAndTag.len - chacha20poly1305_tag_size];
481 var polyTag = msgAndTag[ciphertext.len..];
482
483 // derive poly1305 key
484 var polyKey = [_]u8{0} ** 32;
485 chaCha20IETF(polyKey[0..], polyKey[0..], 0, key, nonce);
486
487 // construct mac
488 var mac = Poly1305.init(polyKey[0..]);
489
490 mac.update(data);
491 if (data.len % 16 != 0) {
492 const zeros = [_]u8{0} ** 16;
493 const padding = 16 - (data.len % 16);
494 mac.update(zeros[0..padding]);
495 }
496 mac.update(ciphertext);
497 if (ciphertext.len % 16 != 0) {
498 const zeros = [_]u8{0} ** 16;
499 const padding = 16 - (ciphertext.len % 16);
500 mac.update(zeros[0..padding]);
501 }
502 var lens: [16]u8 = undefined;
503 mem.writeIntSliceLittle(u64, lens[0..8], data.len);
504 mem.writeIntSliceLittle(u64, lens[8..16], ciphertext.len);
505 mac.update(lens[0..]);
506 var computedTag: [16]u8 = undefined;
507 mac.final(computedTag[0..]);
508
509 // verify mac in constant time
510 // TODO: we can't currently guarantee that this will run in constant time.
511 // See https://github.com/ziglang/zig/issues/1776
512 var acc: u8 = 0;
513 for (computedTag) |_, i| {
514 acc |= (computedTag[i] ^ polyTag[i]);
515 }
516 if (acc != 0) {
517 return error.AuthenticationFailed;
518 }
519
520 // decrypt ciphertext
521 chaCha20IETF(dst[0..ciphertext.len], ciphertext, 1, key, nonce);
522}
523
524test "seal" {
525 {
526 const plaintext = "";
527 const data = "";
528 const key = [_]u8{
529 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
530 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
531 };
532 const nonce = [_]u8{ 0x7, 0x0, 0x0, 0x0, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 };
533 const exp_out = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 };
534
535 var out: [exp_out.len]u8 = undefined;
536 chacha20poly1305Seal(out[0..], plaintext, data, key, nonce);
537 testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
538 }
539 {
540 const plaintext = [_]u8{
541 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c,
542 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73,
543 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63,
544 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f,
545 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20,
546 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73,
547 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69,
548 0x74, 0x2e,
549 };
550 const data = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 };
551 const key = [_]u8{
552 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
553 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
554 };
555 const nonce = [_]u8{ 0x7, 0x0, 0x0, 0x0, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 };
556 const exp_out = [_]u8{
557 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2,
558 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x8, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6,
559 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b,
560 0x1a, 0x71, 0xde, 0xa, 0x9e, 0x6, 0xb, 0x29, 0x5, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36,
561 0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c, 0x98, 0x3, 0xae, 0xe3, 0x28, 0x9, 0x1b, 0x58,
562 0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc,
563 0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b,
564 0x61, 0x16, 0x1a, 0xe1, 0xb, 0x59, 0x4f, 0x9, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60,
565 0x6, 0x91,
566 };
567
568 var out: [exp_out.len]u8 = undefined;
569 chacha20poly1305Seal(out[0..], plaintext[0..], data[0..], key, nonce);
570 testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
571 }
572}
573
574test "open" {
575 {
576 const ciphertext = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 };
577 const data = "";
578 const key = [_]u8{
579 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
580 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
581 };
582 const nonce = [_]u8{ 0x7, 0x0, 0x0, 0x0, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 };
583 const exp_out = "";
584
585 var out: [exp_out.len]u8 = undefined;
586 try chacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce);
587 testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
588 }
589 {
590 const ciphertext = [_]u8{
591 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2,
592 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x8, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6,
593 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b,
594 0x1a, 0x71, 0xde, 0xa, 0x9e, 0x6, 0xb, 0x29, 0x5, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36,
595 0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c, 0x98, 0x3, 0xae, 0xe3, 0x28, 0x9, 0x1b, 0x58,
596 0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc,
597 0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b,
598 0x61, 0x16, 0x1a, 0xe1, 0xb, 0x59, 0x4f, 0x9, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60,
599 0x6, 0x91,
600 };
601 const data = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 };
602 const key = [_]u8{
603 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
604 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
605 };
606 const nonce = [_]u8{ 0x7, 0x0, 0x0, 0x0, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 };
607 const exp_out = [_]u8{
608 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c,
609 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73,
610 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63,
611 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f,
612 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20,
613 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73,
614 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69,
615 0x74, 0x2e,
616 };
617
618 var out: [exp_out.len]u8 = undefined;
619 try chacha20poly1305Open(out[0..], ciphertext[0..], data[0..], key, nonce);
620 testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
621
622 // corrupting the ciphertext, data, key, or nonce should cause a failure
623 var bad_ciphertext = ciphertext;
624 bad_ciphertext[0] ^= 1;
625 testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], bad_ciphertext[0..], data[0..], key, nonce));
626 var bad_data = data;
627 bad_data[0] ^= 1;
628 testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[0..], bad_data[0..], key, nonce));
629 var bad_key = key;
630 bad_key[0] ^= 1;
631 testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[0..], data[0..], bad_key, nonce));
632 var bad_nonce = nonce;
633 bad_nonce[0] ^= 1;
634 testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[0..], data[0..], key, bad_nonce));
635
636 // a short ciphertext should result in a different error
637 testing.expectError(error.InvalidMessage, chacha20poly1305Open(out[0..], "", data[0..], key, bad_nonce));
638 }
639}