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;...@@ -29,9 +29,10 @@ pub const HmacSha1 = hmac.HmacSha1;
29pub const HmacSha256 = hmac.HmacSha256;29pub const HmacSha256 = hmac.HmacSha256;
30pub const HmacBlake2s256 = hmac.HmacBlake2s256;30pub const HmacBlake2s256 = hmac.HmacBlake2s256;
3131
32const import_chaCha20 = @import("crypto/chacha20.zig");32pub const chacha20 = @import("crypto/chacha20.zig");
33pub const chaCha20IETF = import_chaCha20.chaCha20IETF;33pub const chaCha20IETF = chacha20.chaCha20IETF;
34pub const chaCha20With64BitNonce = import_chaCha20.chaCha20With64BitNonce;34pub const chaCha20With64BitNonce = chacha20.chaCha20With64BitNonce;
35pub const xChaCha20IETF = chacha20.xChaCha20IETF;
3536
36pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305;37pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305;
3738
...@@ -45,6 +46,12 @@ pub const Edwards25519 = @import("crypto/25519/edwards25519.zig").Edwards25519;...@@ -45,6 +46,12 @@ pub const Edwards25519 = @import("crypto/25519/edwards25519.zig").Edwards25519;
45pub const X25519 = @import("crypto/25519/x25519.zig").X25519;46pub const X25519 = @import("crypto/25519/x25519.zig").X25519;
46pub const Ristretto255 = @import("crypto/25519/ristretto255.zig").Ristretto255;47pub 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
48const std = @import("std.zig");55const std = @import("std.zig");
49pub const randomBytes = std.os.getrandom;56pub 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 {...@@ -25,12 +25,24 @@ fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {
25 };25 };
26}26}
2727
28// The chacha family of ciphers are based on the salsa family.28fn initContext(key: [8]u32, d: [4]u32) [16]u32 {
29fn salsa20_wordtobyte(out: []u8, input: [16]u32) void {29 var ctx: [16]u32 = undefined;
30 assert(out.len >= 64);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 {
34 for (x) |_, i|46 for (x) |_, i|
35 x[i] = input[i];47 x[i] = input[i];
3648
...@@ -59,33 +71,27 @@ fn salsa20_wordtobyte(out: []u8, input: [16]u32) void {...@@ -59,33 +71,27 @@ fn salsa20_wordtobyte(out: []u8, input: [16]u32) void {
59 x[r.b] = std.math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7));71 x[r.b] = std.math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7));
60 }72 }
61 }73 }
74}
6275
76fn hashToBytes(out: []u8, x: [16]u32) void {
63 for (x) |_, i| {77 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]);
65 }79 }
66}80}
6781
68fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {82fn 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);
70 var remaining: usize = if (in.len > out.len) in.len else out.len;84 var remaining: usize = if (in.len > out.len) in.len else out.len;
71 var cursor: usize = 0;85 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
85 while (true) {87 while (true) {
88 var x: [16]u32 = undefined;
86 var buf: [64]u8 = undefined;89 var buf: [64]u8 = undefined;
87 salsa20_wordtobyte(buf[0..], ctx);90 chacha20Core(x[0..], ctx);
8891 for (x) |_, i| {
92 x[i] +%= ctx[i];
93 }
94 hashToBytes(buf[0..], x);
89 if (remaining < 64) {95 if (remaining < 64) {
90 var i: usize = 0;96 var i: usize = 0;
91 while (i < remaining) : (i += 1)97 while (i < remaining) : (i += 1)
...@@ -104,6 +110,20 @@ fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) vo...@@ -104,6 +110,20 @@ fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) vo
104 }110 }
105}111}
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
107/// ChaCha20 avoids the possibility of timing attacks, as there are no branches127/// ChaCha20 avoids the possibility of timing attacks, as there are no branches
108/// on secret key data.128/// on secret key data.
109///129///
...@@ -116,23 +136,12 @@ pub fn chaCha20IETF(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce:...@@ -116,23 +136,12 @@ pub fn chaCha20IETF(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce:
116 assert(in.len >= out.len);136 assert(in.len >= out.len);
117 assert((in.len >> 6) + counter <= maxInt(u32));137 assert((in.len >> 6) + counter <= maxInt(u32));
118138
119 var k: [8]u32 = undefined;
120 var c: [4]u32 = undefined;139 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
131 c[0] = counter;140 c[0] = counter;
132 c[1] = mem.readIntLittle(u32, nonce[0..4]);141 c[1] = mem.readIntLittle(u32, nonce[0..4]);
133 c[2] = mem.readIntLittle(u32, nonce[4..8]);142 c[2] = mem.readIntLittle(u32, nonce[4..8]);
134 c[3] = mem.readIntLittle(u32, nonce[8..12]);143 c[3] = mem.readIntLittle(u32, nonce[8..12]);
135 chaCha20_internal(out, in, k, c);144 chaCha20_internal(out, in, keyToWords(key), c);
136}145}
137146
138/// This is the original ChaCha20 before RFC 7539, which recommends using the147/// 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]...@@ -143,18 +152,8 @@ pub fn chaCha20With64BitNonce(out: []u8, in: []const u8, counter: u64, key: [32]
143 assert(counter +% (in.len >> 6) >= counter);152 assert(counter +% (in.len >> 6) >= counter);
144153
145 var cursor: usize = 0;154 var cursor: usize = 0;
146 var k: [8]u32 = undefined;155 const k = keyToWords(key);
147 var c: [4]u32 = undefined;156 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
158 c[0] = @truncate(u32, counter);157 c[0] = @truncate(u32, counter);
159 c[1] = @truncate(u32, counter >> 32);158 c[1] = @truncate(u32, counter >> 32);
160 c[2] = mem.readIntLittle(u32, nonce[0..4]);159 c[2] = mem.readIntLittle(u32, nonce[0..4]);
...@@ -437,15 +436,15 @@ test "crypto.chacha20 test vector 5" {...@@ -437,15 +436,15 @@ test "crypto.chacha20 test vector 5" {
437436
438pub const chacha20poly1305_tag_size = 16;437pub const chacha20poly1305_tag_size = 16;
439438
440pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void {439pub fn chacha20poly1305SealDetached(ciphertext: []u8, tag: *[chacha20poly1305_tag_size]u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void {
441 assert(dst.len >= plaintext.len + chacha20poly1305_tag_size);440 assert(ciphertext.len >= plaintext.len);
442441
443 // derive poly1305 key442 // derive poly1305 key
444 var polyKey = [_]u8{0} ** 32;443 var polyKey = [_]u8{0} ** 32;
445 chaCha20IETF(polyKey[0..], polyKey[0..], 0, key, nonce);444 chaCha20IETF(polyKey[0..], polyKey[0..], 0, key, nonce);
446445
447 // encrypt plaintext446 // encrypt plaintext
448 chaCha20IETF(dst[0..plaintext.len], plaintext, 1, key, nonce);447 chaCha20IETF(ciphertext[0..plaintext.len], plaintext, 1, key, nonce);
449448
450 // construct mac449 // construct mac
451 var mac = Poly1305.init(polyKey[0..]);450 var mac = Poly1305.init(polyKey[0..]);
...@@ -455,7 +454,7 @@ pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8,...@@ -455,7 +454,7 @@ pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8,
455 const padding = 16 - (data.len % 16);454 const padding = 16 - (data.len % 16);
456 mac.update(zeros[0..padding]);455 mac.update(zeros[0..padding]);
457 }456 }
458 mac.update(dst[0..plaintext.len]);457 mac.update(ciphertext[0..plaintext.len]);
459 if (plaintext.len % 16 != 0) {458 if (plaintext.len % 16 != 0) {
460 const zeros = [_]u8{0} ** 16;459 const zeros = [_]u8{0} ** 16;
461 const padding = 16 - (plaintext.len % 16);460 const padding = 16 - (plaintext.len % 16);
...@@ -465,19 +464,17 @@ pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8,...@@ -465,19 +464,17 @@ pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8,
465 mem.writeIntLittle(u64, lens[0..8], data.len);464 mem.writeIntLittle(u64, lens[0..8], data.len);
466 mem.writeIntLittle(u64, lens[8..16], plaintext.len);465 mem.writeIntLittle(u64, lens[8..16], plaintext.len);
467 mac.update(lens[0..]);466 mac.update(lens[0..]);
468 mac.final(dst[plaintext.len..]);467 mac.final(tag);
469}468}
470469
471/// Verifies and decrypts an authenticated message produced by chacha20poly1305Seal.470pub fn chacha20poly1305Seal(ciphertextAndTag: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void {
472pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []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);
473 if (msgAndTag.len < chacha20poly1305_tag_size) {472}
474 return error.InvalidMessage;
475 }
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 {
477 // split ciphertext and tag476 // split ciphertext and tag
478 assert(dst.len >= msgAndTag.len - chacha20poly1305_tag_size);477 assert(dst.len >= ciphertext.len);
479 var ciphertext = msgAndTag[0 .. msgAndTag.len - chacha20poly1305_tag_size];
480 var polyTag = msgAndTag[ciphertext.len..];
481478
482 // derive poly1305 key479 // derive poly1305 key
483 var polyKey = [_]u8{0} ** 32;480 var polyKey = [_]u8{0} ** 32;
...@@ -510,7 +507,7 @@ pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []const u8, data: []const u8,...@@ -510,7 +507,7 @@ pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []const u8, data: []const u8,
510 // See https://github.com/ziglang/zig/issues/1776507 // See https://github.com/ziglang/zig/issues/1776
511 var acc: u8 = 0;508 var acc: u8 = 0;
512 for (computedTag) |_, i| {509 for (computedTag) |_, i| {
513 acc |= (computedTag[i] ^ polyTag[i]);510 acc |= (computedTag[i] ^ tag[i]);
514 }511 }
515 if (acc != 0) {512 if (acc != 0) {
516 return error.AuthenticationFailed;513 return error.AuthenticationFailed;
...@@ -520,6 +517,75 @@ pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []const u8, data: []const u8,...@@ -520,6 +517,75 @@ pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []const u8, data: []const u8,
520 chaCha20IETF(dst[0..ciphertext.len], ciphertext, 1, key, nonce);517 chaCha20IETF(dst[0..ciphertext.len], ciphertext, 1, key, nonce);
521}518}
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
523test "seal" {589test "seal" {
524 {590 {
525 const plaintext = "";591 const plaintext = "";
...@@ -636,3 +702,105 @@ test "open" {...@@ -636,3 +702,105 @@ test "open" {
636 testing.expectError(error.InvalidMessage, chacha20poly1305Open(out[0..], "", data[0..], key, bad_nonce));702 testing.expectError(error.InvalidMessage, chacha20poly1305Open(out[0..], "", data[0..], key, bad_nonce));
637 }703 }
638}704}
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}