authorgravatar for luke.champine@gmail.comlukechampine <luke.champine@gmail.com> 2019-11-05 11:47:04-05:00
committergravatar for luke.champine@gmail.comlukechampine <luke.champine@gmail.com> 2019-12-30 13:34:53-05:00
logfbe7d8c1cbb3fa6a6b080cad97067705cb7da1be
tree9dd2307254ea03f0031f401f8df8d9d58738e2c5
parentc70a673c6eb92383d2a8e026374b4b23647db059
signaturelock-open Commit is signed but in an unrecognized format.

crypto: Add chacha20poly1305


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

lib/std/crypto/chacha20.zig+194
...@@ -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,196 @@ test "crypto.chacha20 test vector 5" {...@@ -434,3 +435,196 @@ 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
472pub fn chacha20poly1305Open(dst: []u8, ciphertext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) bool {
473 assert(ciphertext.len >= chacha20poly1305_tag_size);
474 assert(dst.len >= ciphertext.len - chacha20poly1305_tag_size);
475
476 // split ciphertext and tag
477 var polyTag = ciphertext[ciphertext.len - chacha20poly1305_tag_size ..];
478 ciphertext = ciphertext[0 .. ciphertext.len - chacha20poly1305_tag_size];
479
480 // derive poly1305 key
481 var polyKey = [_]u8{0} ** 32;
482 chaCha20IETF(polyKey[0..], polyKey[0..], 0, key, nonce);
483
484 // construct mac
485 var mac = Poly1305.init(polyKey[0..]);
486
487 mac.update(data);
488 if (data.len % 16 != 0) {
489 const zeros = [_]u8{0} ** 16;
490 const padding = 16 - (data.len % 16);
491 mac.update(zeros[0..padding]);
492 }
493 mac.update(ciphertext);
494 if (ciphertext.len % 16 != 0) {
495 const zeros = [_]u8{0} ** 16;
496 const padding = 16 - (ciphertext.len % 16);
497 mac.update(zeros[0..padding]);
498 }
499 var lens: [16]u8 = undefined;
500 mem.writeIntSliceLittle(u64, lens[0..8], data.len);
501 mem.writeIntSliceLittle(u64, lens[8..16], ciphertext.len);
502 mac.update(lens[0..]);
503 var computedTag: [16]u8 = undefined;
504 mac.final(computedTag[0..]);
505
506 // verify mac
507 if (!mem.eql(u8, polyTag, computedTag[0..])) {
508 return false;
509 }
510
511 // decrypt ciphertext
512 chaCha20IETF(dst[0..ciphertext.len], ciphertext, 1, key, nonce);
513 return true;
514}
515
516test "seal" {
517 {
518 const plaintext = "";
519 const data = "";
520 const key = [_]u8{
521 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
522 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
523 };
524 const nonce = [_]u8{ 0x7, 0x0, 0x0, 0x0, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 };
525 const exp_out = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 };
526
527 var out: [exp_out.len]u8 = undefined;
528 chacha20poly1305Seal(out[0..], plaintext, data, key, nonce);
529 testing.expectEqualSlices(u8, exp_out, out);
530 }
531 {
532 const plaintext = [_]u8{
533 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c,
534 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73,
535 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63,
536 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f,
537 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20,
538 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73,
539 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69,
540 0x74, 0x2e,
541 };
542 const data = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 };
543 const key = [_]u8{
544 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
545 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
546 };
547 const nonce = [_]u8{ 0x7, 0x0, 0x0, 0x0, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 };
548 const exp_out = [_]u8{
549 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2,
550 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x8, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6,
551 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b,
552 0x1a, 0x71, 0xde, 0xa, 0x9e, 0x6, 0xb, 0x29, 0x5, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36,
553 0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c, 0x98, 0x3, 0xae, 0xe3, 0x28, 0x9, 0x1b, 0x58,
554 0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc,
555 0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b,
556 0x61, 0x16, 0x1a, 0xe1, 0xb, 0x59, 0x4f, 0x9, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60,
557 0x6, 0x91,
558 };
559
560 var out: [exp_out.len]u8 = undefined;
561 chacha20poly1305Seal(out[0..], plaintext[0..], data[0..], key, nonce);
562 testing.expectEqualSlices(u8, exp_out, out);
563 }
564}
565
566test "open" {
567 {
568 const ciphertext = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 };
569 const data = "";
570 const key = [_]u8{
571 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
572 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
573 };
574 const nonce = [_]u8{ 0x7, 0x0, 0x0, 0x0, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 };
575 const exp_out = "";
576
577 var out: [exp_out.len]u8 = undefined;
578 var valid: bool = chacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce);
579 testing.expect(valid);
580 testing.expectEqualSlices(u8, exp_out, out);
581 }
582 {
583 const ciphertext = [_]u8{
584 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2,
585 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x8, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6,
586 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b,
587 0x1a, 0x71, 0xde, 0xa, 0x9e, 0x6, 0xb, 0x29, 0x5, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36,
588 0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c, 0x98, 0x3, 0xae, 0xe3, 0x28, 0x9, 0x1b, 0x58,
589 0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc,
590 0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b,
591 0x61, 0x16, 0x1a, 0xe1, 0xb, 0x59, 0x4f, 0x9, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60,
592 0x6, 0x91,
593 };
594 const data = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 };
595 const key = [_]u8{
596 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
597 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
598 };
599 const nonce = [_]u8{ 0x7, 0x0, 0x0, 0x0, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 };
600 const exp_out = [_]u8{
601 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c,
602 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73,
603 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63,
604 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f,
605 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20,
606 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73,
607 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69,
608 0x74, 0x2e,
609 };
610
611 var out: [exp_out.len]u8 = undefined;
612 var valid: bool = chacha20poly1305Open(out[0..], ciphertext[0..], data[0..], key, nonce);
613 testing.expect(valid);
614 testing.expectEqualSlices(u8, exp_out, out);
615
616 // corrupting the ciphertext, data, key, or nonce should cause a failure
617 var bad_ciphertext = ciphertext;
618 bad_ciphertext[0] ^= 1;
619 testing.expect(!chacha20poly1305Open(out[0..], bad_ciphertext[0..], data, key, nonce));
620 var bad_data = data;
621 bad_data[0] ^= 1;
622 testing.expect(!chacha20poly1305Open(out[0..], ciphertext[0..], bad_data, key, nonce));
623 var bad_key = key;
624 bad_key[0] ^= 1;
625 testing.expect(!chacha20poly1305Open(out[0..], ciphertext[0..], data, bad_key, nonce));
626 var bad_nonce = nonce;
627 bad_nonce[0] ^= 1;
628 testing.expect(!chacha20poly1305Open(out[0..], ciphertext[0..], data, key, bad_nonce));
629 }
630}