authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-17 13:56:13-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-17 13:56:13-07:00
logc00d3d47f06abfc8321b132232586ccc4349cf02
tree67c1964221d60393bfae853b534e1e00c578eab5
parent5cb96681d92e7a410577215ff057e459f10304dc
parent5fabb44aebd599dd22e43ed43c1f8932af06cd27

Merge branch 'jedisct1-xchacha20'

closes #6074

2 files changed, 235 insertions(+), 60 deletions(-)

lib/std/crypto.zig+10-3
......@@ -29,9 +29,10 @@ pub const HmacSha1 = hmac.HmacSha1;
2929pub const HmacSha256 = hmac.HmacSha256;
3030pub const HmacBlake2s256 = hmac.HmacBlake2s256;
3131
32const import_chaCha20 = @import("crypto/chacha20.zig");
33pub const chaCha20IETF = import_chaCha20.chaCha20IETF;
34pub const chaCha20With64BitNonce = import_chaCha20.chaCha20With64BitNonce;
32pub const chacha20 = @import("crypto/chacha20.zig");
33pub const chaCha20IETF = chacha20.chaCha20IETF;
34pub const chaCha20With64BitNonce = chacha20.chaCha20With64BitNonce;
35pub const xChaCha20IETF = chacha20.xChaCha20IETF;
3536
3637pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305;
3738
......@@ -45,6 +46,12 @@ pub const Edwards25519 = @import("crypto/25519/edwards25519.zig").Edwards25519;
4546pub const X25519 = @import("crypto/25519/x25519.zig").X25519;
4647pub const Ristretto255 = @import("crypto/25519/ristretto255.zig").Ristretto255;
4748
49pub const aead = struct {
50 pub const Gimli = gimli.Aead;
51 pub const ChaCha20Poly1305 = chacha20.Chacha20Poly1305;
52 pub const XChaCha20Poly1305 = chacha20.XChacha20Poly1305;
53};
54
4855const std = @import("std.zig");
4956pub const randomBytes = std.os.getrandom;
5057
lib/std/crypto/chacha20.zig+225-57
......@@ -25,12 +25,24 @@ fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {
2525 };
2626}
2727
28// The chacha family of ciphers are based on the salsa family.
29fn salsa20_wordtobyte(out: []u8, input: [16]u32) void {
30 assert(out.len >= 64);
28fn initContext(key: [8]u32, d: [4]u32) [16]u32 {
29 var ctx: [16]u32 = undefined;
30 const c = "expand 32-byte k";
31 const constant_le = comptime [_]u32{
32 mem.readIntLittle(u32, c[0..4]),
33 mem.readIntLittle(u32, c[4..8]),
34 mem.readIntLittle(u32, c[8..12]),
35 mem.readIntLittle(u32, c[12..16]),
36 };
37 mem.copy(u32, ctx[0..], constant_le[0..4]);
38 mem.copy(u32, ctx[4..12], key[0..8]);
39 mem.copy(u32, ctx[12..16], d[0..4]);
3140
32 var x: [16]u32 = undefined;
41 return ctx;
42}
3343
44// The chacha family of ciphers are based on the salsa family.
45fn chacha20Core(x: []u32, input: [16]u32) void {
3446 for (x) |_, i|
3547 x[i] = input[i];
3648
......@@ -59,33 +71,27 @@ fn salsa20_wordtobyte(out: []u8, input: [16]u32) void {
5971 x[r.b] = std.math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7));
6072 }
6173 }
74}
6275
76fn hashToBytes(out: []u8, x: [16]u32) void {
6377 for (x) |_, i| {
64 mem.writeIntLittle(u32, out[4 * i ..][0..4], x[i] +% input[i]);
78 mem.writeIntLittle(u32, out[4 * i ..][0..4], x[i]);
6579 }
6680}
6781
6882fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {
69 var ctx: [16]u32 = undefined;
83 var ctx = initContext(key, counter);
7084 var remaining: usize = if (in.len > out.len) in.len else out.len;
7185 var cursor: usize = 0;
7286
73 const c = "expand 32-byte k";
74 const constant_le = [_]u32{
75 mem.readIntLittle(u32, c[0..4]),
76 mem.readIntLittle(u32, c[4..8]),
77 mem.readIntLittle(u32, c[8..12]),
78 mem.readIntLittle(u32, c[12..16]),
79 };
80
81 mem.copy(u32, ctx[0..], constant_le[0..4]);
82 mem.copy(u32, ctx[4..12], key[0..8]);
83 mem.copy(u32, ctx[12..16], counter[0..4]);
84
8587 while (true) {
88 var x: [16]u32 = undefined;
8689 var buf: [64]u8 = undefined;
87 salsa20_wordtobyte(buf[0..], ctx);
88
90 chacha20Core(x[0..], ctx);
91 for (x) |_, i| {
92 x[i] +%= ctx[i];
93 }
94 hashToBytes(buf[0..], x);
8995 if (remaining < 64) {
9096 var i: usize = 0;
9197 while (i < remaining) : (i += 1)
......@@ -104,6 +110,20 @@ fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) vo
104110 }
105111}
106112
113fn keyToWords(key: [32]u8) [8]u32 {
114 var k: [8]u32 = undefined;
115 k[0] = mem.readIntLittle(u32, key[0..4]);
116 k[1] = mem.readIntLittle(u32, key[4..8]);
117 k[2] = mem.readIntLittle(u32, key[8..12]);
118 k[3] = mem.readIntLittle(u32, key[12..16]);
119 k[4] = mem.readIntLittle(u32, key[16..20]);
120 k[5] = mem.readIntLittle(u32, key[20..24]);
121 k[6] = mem.readIntLittle(u32, key[24..28]);
122 k[7] = mem.readIntLittle(u32, key[28..32]);
123
124 return k;
125}
126
107127/// ChaCha20 avoids the possibility of timing attacks, as there are no branches
108128/// on secret key data.
109129///
......@@ -116,23 +136,12 @@ pub fn chaCha20IETF(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce:
116136 assert(in.len >= out.len);
117137 assert((in.len >> 6) + counter <= maxInt(u32));
118138
119 var k: [8]u32 = undefined;
120139 var c: [4]u32 = undefined;
121
122 k[0] = mem.readIntLittle(u32, key[0..4]);
123 k[1] = mem.readIntLittle(u32, key[4..8]);
124 k[2] = mem.readIntLittle(u32, key[8..12]);
125 k[3] = mem.readIntLittle(u32, key[12..16]);
126 k[4] = mem.readIntLittle(u32, key[16..20]);
127 k[5] = mem.readIntLittle(u32, key[20..24]);
128 k[6] = mem.readIntLittle(u32, key[24..28]);
129 k[7] = mem.readIntLittle(u32, key[28..32]);
130
131140 c[0] = counter;
132141 c[1] = mem.readIntLittle(u32, nonce[0..4]);
133142 c[2] = mem.readIntLittle(u32, nonce[4..8]);
134143 c[3] = mem.readIntLittle(u32, nonce[8..12]);
135 chaCha20_internal(out, in, k, c);
144 chaCha20_internal(out, in, keyToWords(key), c);
136145}
137146
138147/// This is the original ChaCha20 before RFC 7539, which recommends using the
......@@ -143,18 +152,8 @@ pub fn chaCha20With64BitNonce(out: []u8, in: []const u8, counter: u64, key: [32]
143152 assert(counter +% (in.len >> 6) >= counter);
144153
145154 var cursor: usize = 0;
146 var k: [8]u32 = undefined;
155 const k = keyToWords(key);
147156 var c: [4]u32 = undefined;
148
149 k[0] = mem.readIntLittle(u32, key[0..4]);
150 k[1] = mem.readIntLittle(u32, key[4..8]);
151 k[2] = mem.readIntLittle(u32, key[8..12]);
152 k[3] = mem.readIntLittle(u32, key[12..16]);
153 k[4] = mem.readIntLittle(u32, key[16..20]);
154 k[5] = mem.readIntLittle(u32, key[20..24]);
155 k[6] = mem.readIntLittle(u32, key[24..28]);
156 k[7] = mem.readIntLittle(u32, key[28..32]);
157
158157 c[0] = @truncate(u32, counter);
159158 c[1] = @truncate(u32, counter >> 32);
160159 c[2] = mem.readIntLittle(u32, nonce[0..4]);
......@@ -437,15 +436,15 @@ test "crypto.chacha20 test vector 5" {
437436
438437pub const chacha20poly1305_tag_size = 16;
439438
440pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void {
441 assert(dst.len >= plaintext.len + chacha20poly1305_tag_size);
439pub fn chacha20poly1305SealDetached(ciphertext: []u8, tag: *[chacha20poly1305_tag_size]u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void {
440 assert(ciphertext.len >= plaintext.len);
442441
443442 // derive poly1305 key
444443 var polyKey = [_]u8{0} ** 32;
445444 chaCha20IETF(polyKey[0..], polyKey[0..], 0, key, nonce);
446445
447446 // encrypt plaintext
448 chaCha20IETF(dst[0..plaintext.len], plaintext, 1, key, nonce);
447 chaCha20IETF(ciphertext[0..plaintext.len], plaintext, 1, key, nonce);
449448
450449 // construct mac
451450 var mac = Poly1305.init(polyKey[0..]);
......@@ -455,7 +454,7 @@ pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8,
455454 const padding = 16 - (data.len % 16);
456455 mac.update(zeros[0..padding]);
457456 }
458 mac.update(dst[0..plaintext.len]);
457 mac.update(ciphertext[0..plaintext.len]);
459458 if (plaintext.len % 16 != 0) {
460459 const zeros = [_]u8{0} ** 16;
461460 const padding = 16 - (plaintext.len % 16);
......@@ -465,19 +464,17 @@ pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8,
465464 mem.writeIntLittle(u64, lens[0..8], data.len);
466465 mem.writeIntLittle(u64, lens[8..16], plaintext.len);
467466 mac.update(lens[0..]);
468 mac.final(dst[plaintext.len..]);
467 mac.final(tag);
469468}
470469
471/// Verifies and decrypts an authenticated message produced by chacha20poly1305Seal.
472pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) !void {
473 if (msgAndTag.len < chacha20poly1305_tag_size) {
474 return error.InvalidMessage;
475 }
470pub fn chacha20poly1305Seal(ciphertextAndTag: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void {
471 return chacha20poly1305SealDetached(ciphertextAndTag[0..plaintext.len], ciphertextAndTag[plaintext.len..][0..chacha20poly1305_tag_size], plaintext, data, key, nonce);
472}
476473
474/// Verifies and decrypts an authenticated message produced by chacha20poly1305SealDetached.
475pub fn chacha20poly1305OpenDetached(dst: []u8, ciphertext: []const u8, tag: *const [chacha20poly1305_tag_size]u8, data: []const u8, key: [32]u8, nonce: [12]u8) !void {
477476 // split ciphertext and tag
478 assert(dst.len >= msgAndTag.len - chacha20poly1305_tag_size);
479 var ciphertext = msgAndTag[0 .. msgAndTag.len - chacha20poly1305_tag_size];
480 var polyTag = msgAndTag[ciphertext.len..];
477 assert(dst.len >= ciphertext.len);
481478
482479 // derive poly1305 key
483480 var polyKey = [_]u8{0} ** 32;
......@@ -510,7 +507,7 @@ pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []const u8, data: []const u8,
510507 // See https://github.com/ziglang/zig/issues/1776
511508 var acc: u8 = 0;
512509 for (computedTag) |_, i| {
513 acc |= (computedTag[i] ^ polyTag[i]);
510 acc |= (computedTag[i] ^ tag[i]);
514511 }
515512 if (acc != 0) {
516513 return error.AuthenticationFailed;
......@@ -520,6 +517,75 @@ pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []const u8, data: []const u8,
520517 chaCha20IETF(dst[0..ciphertext.len], ciphertext, 1, key, nonce);
521518}
522519
520/// Verifies and decrypts an authenticated message produced by chacha20poly1305Seal.
521pub fn chacha20poly1305Open(dst: []u8, ciphertextAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) !void {
522 if (ciphertextAndTag.len < chacha20poly1305_tag_size) {
523 return error.InvalidMessage;
524 }
525 const ciphertextLen = ciphertextAndTag.len - chacha20poly1305_tag_size;
526 return try chacha20poly1305OpenDetached(dst, ciphertextAndTag[0..ciphertextLen], ciphertextAndTag[ciphertextLen..][0..chacha20poly1305_tag_size], data, key, nonce);
527}
528
529fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
530 var c: [4]u32 = undefined;
531 for (c) |_, i| {
532 c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);
533 }
534 const ctx = initContext(keyToWords(key), c);
535 var x: [16]u32 = undefined;
536 chacha20Core(x[0..], ctx);
537 var out: [32]u8 = undefined;
538 mem.writeIntLittle(u32, out[0..4], x[0]);
539 mem.writeIntLittle(u32, out[4..8], x[1]);
540 mem.writeIntLittle(u32, out[8..12], x[2]);
541 mem.writeIntLittle(u32, out[12..16], x[3]);
542 mem.writeIntLittle(u32, out[16..20], x[12]);
543 mem.writeIntLittle(u32, out[20..24], x[13]);
544 mem.writeIntLittle(u32, out[24..28], x[14]);
545 mem.writeIntLittle(u32, out[28..32], x[15]);
546
547 return out;
548}
549
550fn extend(key: [32]u8, nonce: [24]u8) struct { key: [32]u8, nonce: [12]u8 } {
551 var subnonce: [12]u8 = undefined;
552 mem.set(u8, subnonce[0..4], 0);
553 mem.copy(u8, subnonce[4..], nonce[16..24]);
554 return .{
555 .key = hchacha20(nonce[0..16].*, key),
556 .nonce = subnonce,
557 };
558}
559
560pub fn xChaCha20IETF(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce: [24]u8) void {
561 const extended = extend(key, nonce);
562 chaCha20IETF(out, in, counter, extended.key, extended.nonce);
563}
564
565pub const xchacha20poly1305_tag_size = 16;
566
567pub fn xchacha20poly1305SealDetached(ciphertext: []u8, tag: *[chacha20poly1305_tag_size]u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) void {
568 const extended = extend(key, nonce);
569 return chacha20poly1305SealDetached(ciphertext, tag, plaintext, data, extended.key, extended.nonce);
570}
571
572pub fn xchacha20poly1305Seal(ciphertextAndTag: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) void {
573 const extended = extend(key, nonce);
574 return chacha20poly1305Seal(ciphertextAndTag, plaintext, data, extended.key, extended.nonce);
575}
576
577/// Verifies and decrypts an authenticated message produced by xchacha20poly1305SealDetached.
578pub fn xchacha20poly1305OpenDetached(plaintext: []u8, ciphertext: []const u8, tag: *const [chacha20poly1305_tag_size]u8, data: []const u8, key: [32]u8, nonce: [24]u8) !void {
579 const extended = extend(key, nonce);
580 return try chacha20poly1305OpenDetached(plaintext, ciphertext, tag, data, extended.key, extended.nonce);
581}
582
583/// Verifies and decrypts an authenticated message produced by xchacha20poly1305Seal.
584pub fn xchacha20poly1305Open(ciphertextAndTag: []u8, msgAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) !void {
585 const extended = extend(key, nonce);
586 return try chacha20poly1305Open(ciphertextAndTag, msgAndTag, data, extended.key, extended.nonce);
587}
588
523589test "seal" {
524590 {
525591 const plaintext = "";
......@@ -636,3 +702,105 @@ test "open" {
636702 testing.expectError(error.InvalidMessage, chacha20poly1305Open(out[0..], "", data[0..], key, bad_nonce));
637703 }
638704}
705
706test "crypto.xchacha20" {
707 const key = [_]u8{69} ** 32;
708 const nonce = [_]u8{42} ** 24;
709 const input = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
710 {
711 var ciphertext: [input.len]u8 = undefined;
712 xChaCha20IETF(ciphertext[0..], input[0..], 0, key, nonce);
713 var buf: [2 * ciphertext.len]u8 = undefined;
714 testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{ciphertext}), "E0A1BCF939654AFDBDC1746EC49832647C19D891F0D1A81FC0C1703B4514BDEA584B512F6908C2C5E9DD18D5CBC1805DE5803FE3B9CA5F193FB8359E91FAB0C3BB40309A292EB1CF49685C65C4A3ADF4F11DB0CD2B6B67FBC174BC2E860E8F769FD3565BBFAD1C845E05A0FED9BE167C240D");
715 }
716 {
717 const data = "Additional data";
718 var ciphertext: [input.len + xchacha20poly1305_tag_size]u8 = undefined;
719 xchacha20poly1305Seal(ciphertext[0..], input, data, key, nonce);
720 var out: [input.len]u8 = undefined;
721 try xchacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce);
722 var buf: [2 * ciphertext.len]u8 = undefined;
723 testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{ciphertext}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234");
724 testing.expectEqualSlices(u8, out[0..], input);
725 ciphertext[0] += 1;
726 testing.expectError(error.AuthenticationFailed, xchacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce));
727 }
728}
729
730pub const Chacha20Poly1305 = struct {
731 pub const tag_length = 16;
732 pub const nonce_length = 12;
733 pub const key_length = 32;
734
735 /// c: ciphertext: output buffer should be of size m.len
736 /// at: authentication tag: output MAC
737 /// m: message
738 /// ad: Associated Data
739 /// npub: public nonce
740 /// k: private key
741 pub fn encrypt(c: []u8, at: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
742 assert(c.len == m.len);
743 return chacha20poly1305SealDetached(c, at, m, ad, k, npub);
744 }
745
746 /// m: message: output buffer should be of size c.len
747 /// c: ciphertext
748 /// at: authentication tag
749 /// ad: Associated Data
750 /// npub: public nonce
751 /// k: private key
752 /// NOTE: the check of the authentication tag is currently not done in constant time
753 pub fn decrypt(m: []u8, c: []const u8, at: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void {
754 assert(c.len == m.len);
755 return try chacha20poly1305OpenDetached(m, c, at[0..], ad, k, npub);
756 }
757};
758
759pub const XChacha20Poly1305 = struct {
760 pub const tag_length = 16;
761 pub const nonce_length = 24;
762 pub const key_length = 32;
763
764 /// c: ciphertext: output buffer should be of size m.len
765 /// at: authentication tag: output MAC
766 /// m: message
767 /// ad: Associated Data
768 /// npub: public nonce
769 /// k: private key
770 pub fn encrypt(c: []u8, at: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
771 assert(c.len == m.len);
772 return xchacha20poly1305SealDetached(c, at, m, ad, k, npub);
773 }
774
775 /// m: message: output buffer should be of size c.len
776 /// c: ciphertext
777 /// at: authentication tag
778 /// ad: Associated Data
779 /// npub: public nonce
780 /// k: private key
781 /// NOTE: the check of the authentication tag is currently not done in constant time
782 pub fn decrypt(m: []u8, c: []const u8, at: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void {
783 assert(c.len == m.len);
784 return try xchacha20poly1305OpenDetached(m, c, at[0..], ad, k, npub);
785 }
786};
787
788test "chacha20 AEAD API" {
789 const aeads = [_]type{ Chacha20Poly1305, XChacha20Poly1305 };
790 const input = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
791 const data = "Additional data";
792
793 inline for (aeads) |aead| {
794 const key = [_]u8{69} ** aead.key_length;
795 const nonce = [_]u8{42} ** aead.nonce_length;
796 var ciphertext: [input.len]u8 = undefined;
797 var tag: [aead.tag_length]u8 = undefined;
798 var out: [input.len]u8 = undefined;
799
800 aead.encrypt(ciphertext[0..], tag[0..], input, data, nonce, key);
801 try aead.decrypt(out[0..], ciphertext[0..], tag, data[0..], nonce, key);
802 testing.expectEqualSlices(u8, out[0..], input);
803 ciphertext[0] += 1;
804 testing.expectError(error.AuthenticationFailed, aead.decrypt(out[0..], ciphertext[0..], tag, data[0..], nonce, key));
805 }
806}