authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2021-03-14 22:30:25+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-03-17 11:25:51-07:00
log119fc318a753f57b55809e9256e823accba6b56a
treeaacfb9f97b9bc87b29e630f1445686f616014980
parent587243c7a50b751846f0633d762f4153ef230ce6

std/crypto/chacha20: add round-reduced versions & cleanup internals

See https://eprint.iacr.org/2019/1492.pdf for justification. 8 rounds ChaCha20 provides a 2.5x speedup, and is still believed to be safe. Round-reduced versions are actually deployed (ex: Android filesystem encryption), and thanks to the magic of comptime, it doesn't take much to support them. This also makes the ChaCha20 code more consistent with the Salsa20 code, removing internal functions that were not part of the public API any more. No breaking changes; the public API remains backwards compatible.

3 files changed, 611 insertions(+), 573 deletions(-)

lib/std/crypto.zig+12-2
......@@ -24,8 +24,12 @@ pub const aead = struct {
2424 pub const Gimli = @import("crypto/gimli.zig").Aead;
2525
2626 pub const chacha_poly = struct {
27 pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").Chacha20Poly1305;
28 pub const XChaCha20Poly1305 = @import("crypto/chacha20.zig").XChacha20Poly1305;
27 pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").ChaCha20Poly1305;
28 pub const ChaCha12Poly1305 = @import("crypto/chacha20.zig").ChaCha12Poly1305;
29 pub const ChaCha8Poly1305 = @import("crypto/chacha20.zig").ChaCha8Poly1305;
30 pub const XChaCha20Poly1305 = @import("crypto/chacha20.zig").XChaCha20Poly1305;
31 pub const XChaCha12Poly1305 = @import("crypto/chacha20.zig").XChaCha12Poly1305;
32 pub const XChaCha8Poly1305 = @import("crypto/chacha20.zig").XChaCha8Poly1305;
2933 };
3034
3135 pub const isap = @import("crypto/isap.zig");
......@@ -119,8 +123,14 @@ pub const sign = struct {
119123pub const stream = struct {
120124 pub const chacha = struct {
121125 pub const ChaCha20IETF = @import("crypto/chacha20.zig").ChaCha20IETF;
126 pub const ChaCha12IETF = @import("crypto/chacha20.zig").ChaCha12IETF;
127 pub const ChaCha8IETF = @import("crypto/chacha20.zig").ChaCha8IETF;
122128 pub const ChaCha20With64BitNonce = @import("crypto/chacha20.zig").ChaCha20With64BitNonce;
129 pub const ChaCha12With64BitNonce = @import("crypto/chacha20.zig").ChaCha12With64BitNonce;
130 pub const ChaCha8With64BitNonce = @import("crypto/chacha20.zig").ChaCha8With64BitNonce;
123131 pub const XChaCha20IETF = @import("crypto/chacha20.zig").XChaCha20IETF;
132 pub const XChaCha12IETF = @import("crypto/chacha20.zig").XChaCha12IETF;
133 pub const XChaCha8IETF = @import("crypto/chacha20.zig").XChaCha8IETF;
124134 };
125135
126136 pub const salsa = struct {
lib/std/crypto/benchmark.zig+1
......@@ -202,6 +202,7 @@ pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime
202202const aeads = [_]Crypto{
203203 Crypto{ .ty = crypto.aead.chacha_poly.ChaCha20Poly1305, .name = "chacha20Poly1305" },
204204 Crypto{ .ty = crypto.aead.chacha_poly.XChaCha20Poly1305, .name = "xchacha20Poly1305" },
205 Crypto{ .ty = crypto.aead.chacha_poly.XChaCha8Poly1305, .name = "xchacha8Poly1305" },
205206 Crypto{ .ty = crypto.aead.salsa_poly.XSalsa20Poly1305, .name = "xsalsa20Poly1305" },
206207 Crypto{ .ty = crypto.aead.Gimli, .name = "gimli-aead" },
207208 Crypto{ .ty = crypto.aead.aegis.Aegis128L, .name = "aegis-128l" },
lib/std/crypto/chacha20.zig+598-571
......@@ -15,286 +15,357 @@ const Vector = std.meta.Vector;
1515const Poly1305 = std.crypto.onetimeauth.Poly1305;
1616const Error = std.crypto.Error;
1717
18/// IETF-variant of the ChaCha20 stream cipher, as designed for TLS.
19pub const ChaCha20IETF = ChaChaIETF(20);
20
21/// IETF-variant of the ChaCha20 stream cipher, reduced to 12 rounds.
22/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
23/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
24pub const ChaCha12IETF = ChaChaIETF(12);
25
26/// IETF-variant of the ChaCha20 stream cipher, reduced to 8 rounds.
27/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
28/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
29pub const ChaCha8IETF = ChaChaIETF(8);
30
31/// Original ChaCha20 stream cipher.
32pub const ChaCha20With64BitNonce = ChaChaWith64BitNonce(20);
33
34/// Original ChaCha20 stream cipher, reduced to 12 rounds.
35/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
36/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
37pub const ChaCha12With64BitNonce = ChaChaWith64BitNonce(12);
38
39/// Original ChaCha20 stream cipher, reduced to 8 rounds.
40/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
41/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
42pub const ChaCha8With64BitNonce = ChaChaWith64BitNonce(8);
43
44/// XChaCha20 (nonce-extended version of the IETF ChaCha20 variant) stream cipher
45pub const XChaCha20IETF = XChaChaIETF(20);
46
47/// XChaCha20 (nonce-extended version of the IETF ChaCha20 variant) stream cipher, reduced to 12 rounds
48/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
49/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
50pub const XChaCha12IETF = XChaChaIETF(12);
51
52/// XChaCha20 (nonce-extended version of the IETF ChaCha20 variant) stream cipher, reduced to 8 rounds
53/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
54/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
55pub const XChaCha8IETF = XChaChaIETF(8);
56
57/// ChaCha20-Poly1305 authenticated cipher, as designed for TLS
58pub const ChaCha20Poly1305 = ChaChaPoly1305(20);
59
60/// ChaCha20-Poly1305 authenticated cipher, reduced to 12 rounds
61/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
62/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
63pub const ChaCha12Poly1305 = ChaChaPoly1305(12);
64
65/// ChaCha20-Poly1305 authenticated cipher, reduced to 8 rounds
66/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
67/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
68pub const ChaCha8Poly1305 = ChaChaPoly1305(8);
69
70/// XChaCha20-Poly1305 authenticated cipher
71pub const XChaCha20Poly1305 = XChaChaPoly1305(20);
72
73/// XChaCha20-Poly1305 authenticated cipher
74/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
75/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
76pub const XChaCha12Poly1305 = XChaChaPoly1305(12);
77
78/// XChaCha20-Poly1305 authenticated cipher
79/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
80/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
81pub const XChaCha8Poly1305 = XChaChaPoly1305(8);
82
1883// Vectorized implementation of the core function
19const ChaCha20VecImpl = struct {
20 const Lane = Vector(4, u32);
21 const BlockVec = [4]Lane;
22
23 fn initContext(key: [8]u32, d: [4]u32) BlockVec {
24 const c = "expand 32-byte k";
25 const constant_le = comptime Lane{
26 mem.readIntLittle(u32, c[0..4]),
27 mem.readIntLittle(u32, c[4..8]),
28 mem.readIntLittle(u32, c[8..12]),
29 mem.readIntLittle(u32, c[12..16]),
30 };
31 return BlockVec{
32 constant_le,
33 Lane{ key[0], key[1], key[2], key[3] },
34 Lane{ key[4], key[5], key[6], key[7] },
35 Lane{ d[0], d[1], d[2], d[3] },
36 };
37 }
84fn ChaChaVecImpl(comptime rounds_nb: usize) type {
85 return struct {
86 const Lane = Vector(4, u32);
87 const BlockVec = [4]Lane;
88
89 fn initContext(key: [8]u32, d: [4]u32) BlockVec {
90 const c = "expand 32-byte k";
91 const constant_le = comptime Lane{
92 mem.readIntLittle(u32, c[0..4]),
93 mem.readIntLittle(u32, c[4..8]),
94 mem.readIntLittle(u32, c[8..12]),
95 mem.readIntLittle(u32, c[12..16]),
96 };
97 return BlockVec{
98 constant_le,
99 Lane{ key[0], key[1], key[2], key[3] },
100 Lane{ key[4], key[5], key[6], key[7] },
101 Lane{ d[0], d[1], d[2], d[3] },
102 };
103 }
38104
39 fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void {
40 x.* = input;
41
42 var r: usize = 0;
43 while (r < 20) : (r += 2) {
44 x[0] +%= x[1];
45 x[3] ^= x[0];
46 x[3] = math.rotl(Lane, x[3], 16);
47
48 x[2] +%= x[3];
49 x[1] ^= x[2];
50 x[1] = math.rotl(Lane, x[1], 12);
51
52 x[0] +%= x[1];
53 x[3] ^= x[0];
54 x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 3, 0, 1, 2 });
55 x[3] = math.rotl(Lane, x[3], 8);
56
57 x[2] +%= x[3];
58 x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 });
59 x[1] ^= x[2];
60 x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 1, 2, 3, 0 });
61 x[1] = math.rotl(Lane, x[1], 7);
62
63 x[0] +%= x[1];
64 x[3] ^= x[0];
65 x[3] = math.rotl(Lane, x[3], 16);
66
67 x[2] +%= x[3];
68 x[1] ^= x[2];
69 x[1] = math.rotl(Lane, x[1], 12);
70
71 x[0] +%= x[1];
72 x[3] ^= x[0];
73 x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 1, 2, 3, 0 });
74 x[3] = math.rotl(Lane, x[3], 8);
75
76 x[2] +%= x[3];
77 x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 });
78 x[1] ^= x[2];
79 x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 3, 0, 1, 2 });
80 x[1] = math.rotl(Lane, x[1], 7);
105 fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void {
106 x.* = input;
107
108 var r: usize = 0;
109 while (r < rounds_nb) : (r += 2) {
110 x[0] +%= x[1];
111 x[3] ^= x[0];
112 x[3] = math.rotl(Lane, x[3], 16);
113
114 x[2] +%= x[3];
115 x[1] ^= x[2];
116 x[1] = math.rotl(Lane, x[1], 12);
117
118 x[0] +%= x[1];
119 x[3] ^= x[0];
120 x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 3, 0, 1, 2 });
121 x[3] = math.rotl(Lane, x[3], 8);
122
123 x[2] +%= x[3];
124 x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 });
125 x[1] ^= x[2];
126 x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 1, 2, 3, 0 });
127 x[1] = math.rotl(Lane, x[1], 7);
128
129 x[0] +%= x[1];
130 x[3] ^= x[0];
131 x[3] = math.rotl(Lane, x[3], 16);
132
133 x[2] +%= x[3];
134 x[1] ^= x[2];
135 x[1] = math.rotl(Lane, x[1], 12);
136
137 x[0] +%= x[1];
138 x[3] ^= x[0];
139 x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 1, 2, 3, 0 });
140 x[3] = math.rotl(Lane, x[3], 8);
141
142 x[2] +%= x[3];
143 x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 });
144 x[1] ^= x[2];
145 x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 3, 0, 1, 2 });
146 x[1] = math.rotl(Lane, x[1], 7);
147 }
81148 }
82 }
83149
84 fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void {
85 var i: usize = 0;
86 while (i < 4) : (i += 1) {
87 mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i][0]);
88 mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i][1]);
89 mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i][2]);
90 mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i][3]);
150 fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void {
151 var i: usize = 0;
152 while (i < 4) : (i += 1) {
153 mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i][0]);
154 mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i][1]);
155 mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i][2]);
156 mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i][3]);
157 }
91158 }
92 }
93159
94 fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void {
95 x[0] +%= ctx[0];
96 x[1] +%= ctx[1];
97 x[2] +%= ctx[2];
98 x[3] +%= ctx[3];
99 }
160 fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void {
161 x[0] +%= ctx[0];
162 x[1] +%= ctx[1];
163 x[2] +%= ctx[2];
164 x[3] +%= ctx[3];
165 }
100166
101 fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {
102 var ctx = initContext(key, counter);
103 var x: BlockVec = undefined;
104 var buf: [64]u8 = undefined;
105 var i: usize = 0;
106 while (i + 64 <= in.len) : (i += 64) {
107 chacha20Core(x[0..], ctx);
108 contextFeedback(&x, ctx);
109 hashToBytes(buf[0..], x);
110
111 var xout = out[i..];
112 const xin = in[i..];
113 var j: usize = 0;
114 while (j < 64) : (j += 1) {
115 xout[j] = xin[j];
116 }
117 j = 0;
118 while (j < 64) : (j += 1) {
119 xout[j] ^= buf[j];
167 fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {
168 var ctx = initContext(key, counter);
169 var x: BlockVec = undefined;
170 var buf: [64]u8 = undefined;
171 var i: usize = 0;
172 while (i + 64 <= in.len) : (i += 64) {
173 chacha20Core(x[0..], ctx);
174 contextFeedback(&x, ctx);
175 hashToBytes(buf[0..], x);
176
177 var xout = out[i..];
178 const xin = in[i..];
179 var j: usize = 0;
180 while (j < 64) : (j += 1) {
181 xout[j] = xin[j];
182 }
183 j = 0;
184 while (j < 64) : (j += 1) {
185 xout[j] ^= buf[j];
186 }
187 ctx[3][0] += 1;
120188 }
121 ctx[3][0] += 1;
122 }
123 if (i < in.len) {
124 chacha20Core(x[0..], ctx);
125 contextFeedback(&x, ctx);
126 hashToBytes(buf[0..], x);
127
128 var xout = out[i..];
129 const xin = in[i..];
130 var j: usize = 0;
131 while (j < in.len % 64) : (j += 1) {
132 xout[j] = xin[j] ^ buf[j];
189 if (i < in.len) {
190 chacha20Core(x[0..], ctx);
191 contextFeedback(&x, ctx);
192 hashToBytes(buf[0..], x);
193
194 var xout = out[i..];
195 const xin = in[i..];
196 var j: usize = 0;
197 while (j < in.len % 64) : (j += 1) {
198 xout[j] = xin[j] ^ buf[j];
199 }
133200 }
134201 }
135 }
136202
137 fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
138 var c: [4]u32 = undefined;
139 for (c) |_, i| {
140 c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);
203 fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
204 var c: [4]u32 = undefined;
205 for (c) |_, i| {
206 c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);
207 }
208 const ctx = initContext(keyToWords(key), c);
209 var x: BlockVec = undefined;
210 chacha20Core(x[0..], ctx);
211 var out: [32]u8 = undefined;
212 mem.writeIntLittle(u32, out[0..4], x[0][0]);
213 mem.writeIntLittle(u32, out[4..8], x[0][1]);
214 mem.writeIntLittle(u32, out[8..12], x[0][2]);
215 mem.writeIntLittle(u32, out[12..16], x[0][3]);
216 mem.writeIntLittle(u32, out[16..20], x[3][0]);
217 mem.writeIntLittle(u32, out[20..24], x[3][1]);
218 mem.writeIntLittle(u32, out[24..28], x[3][2]);
219 mem.writeIntLittle(u32, out[28..32], x[3][3]);
220 return out;
141221 }
142 const ctx = initContext(keyToWords(key), c);
143 var x: BlockVec = undefined;
144 chacha20Core(x[0..], ctx);
145 var out: [32]u8 = undefined;
146 mem.writeIntLittle(u32, out[0..4], x[0][0]);
147 mem.writeIntLittle(u32, out[4..8], x[0][1]);
148 mem.writeIntLittle(u32, out[8..12], x[0][2]);
149 mem.writeIntLittle(u32, out[12..16], x[0][3]);
150 mem.writeIntLittle(u32, out[16..20], x[3][0]);
151 mem.writeIntLittle(u32, out[20..24], x[3][1]);
152 mem.writeIntLittle(u32, out[24..28], x[3][2]);
153 mem.writeIntLittle(u32, out[28..32], x[3][3]);
154 return out;
155 }
156};
222 };
223}
157224
158225// Non-vectorized implementation of the core function
159const ChaCha20NonVecImpl = struct {
160 const BlockVec = [16]u32;
161
162 fn initContext(key: [8]u32, d: [4]u32) BlockVec {
163 const c = "expand 32-byte k";
164 const constant_le = comptime [4]u32{
165 mem.readIntLittle(u32, c[0..4]),
166 mem.readIntLittle(u32, c[4..8]),
167 mem.readIntLittle(u32, c[8..12]),
168 mem.readIntLittle(u32, c[12..16]),
169 };
170 return BlockVec{
171 constant_le[0], constant_le[1], constant_le[2], constant_le[3],
172 key[0], key[1], key[2], key[3],
173 key[4], key[5], key[6], key[7],
174 d[0], d[1], d[2], d[3],
175 };
176 }
177
178 const QuarterRound = struct {
179 a: usize,
180 b: usize,
181 c: usize,
182 d: usize,
183 };
226fn ChaChaNonVecImpl(comptime rounds_nb: usize) type {
227 return struct {
228 const BlockVec = [16]u32;
229
230 fn initContext(key: [8]u32, d: [4]u32) BlockVec {
231 const c = "expand 32-byte k";
232 const constant_le = comptime [4]u32{
233 mem.readIntLittle(u32, c[0..4]),
234 mem.readIntLittle(u32, c[4..8]),
235 mem.readIntLittle(u32, c[8..12]),
236 mem.readIntLittle(u32, c[12..16]),
237 };
238 return BlockVec{
239 constant_le[0], constant_le[1], constant_le[2], constant_le[3],
240 key[0], key[1], key[2], key[3],
241 key[4], key[5], key[6], key[7],
242 d[0], d[1], d[2], d[3],
243 };
244 }
184245
185 fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {
186 return QuarterRound{
187 .a = a,
188 .b = b,
189 .c = c,
190 .d = d,
246 const QuarterRound = struct {
247 a: usize,
248 b: usize,
249 c: usize,
250 d: usize,
191251 };
192 }
193252
194 fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void {
195 x.* = input;
196
197 const rounds = comptime [_]QuarterRound{
198 Rp(0, 4, 8, 12),
199 Rp(1, 5, 9, 13),
200 Rp(2, 6, 10, 14),
201 Rp(3, 7, 11, 15),
202 Rp(0, 5, 10, 15),
203 Rp(1, 6, 11, 12),
204 Rp(2, 7, 8, 13),
205 Rp(3, 4, 9, 14),
206 };
253 fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {
254 return QuarterRound{
255 .a = a,
256 .b = b,
257 .c = c,
258 .d = d,
259 };
260 }
207261
208 comptime var j: usize = 0;
209 inline while (j < 20) : (j += 2) {
210 inline for (rounds) |r| {
211 x[r.a] +%= x[r.b];
212 x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 16));
213 x[r.c] +%= x[r.d];
214 x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 12));
215 x[r.a] +%= x[r.b];
216 x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 8));
217 x[r.c] +%= x[r.d];
218 x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7));
262 fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void {
263 x.* = input;
264
265 const rounds = comptime [_]QuarterRound{
266 Rp(0, 4, 8, 12),
267 Rp(1, 5, 9, 13),
268 Rp(2, 6, 10, 14),
269 Rp(3, 7, 11, 15),
270 Rp(0, 5, 10, 15),
271 Rp(1, 6, 11, 12),
272 Rp(2, 7, 8, 13),
273 Rp(3, 4, 9, 14),
274 };
275
276 comptime var j: usize = 0;
277 inline while (j < rounds_nb) : (j += 2) {
278 inline for (rounds) |r| {
279 x[r.a] +%= x[r.b];
280 x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 16));
281 x[r.c] +%= x[r.d];
282 x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 12));
283 x[r.a] +%= x[r.b];
284 x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 8));
285 x[r.c] +%= x[r.d];
286 x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7));
287 }
219288 }
220289 }
221 }
222290
223 fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void {
224 var i: usize = 0;
225 while (i < 4) : (i += 1) {
226 mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0]);
227 mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i * 4 + 1]);
228 mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i * 4 + 2]);
229 mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i * 4 + 3]);
291 fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void {
292 var i: usize = 0;
293 while (i < 4) : (i += 1) {
294 mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0]);
295 mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i * 4 + 1]);
296 mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i * 4 + 2]);
297 mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i * 4 + 3]);
298 }
230299 }
231 }
232300
233 fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void {
234 var i: usize = 0;
235 while (i < 16) : (i += 1) {
236 x[i] +%= ctx[i];
301 fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void {
302 var i: usize = 0;
303 while (i < 16) : (i += 1) {
304 x[i] +%= ctx[i];
305 }
237306 }
238 }
239307
240 fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {
241 var ctx = initContext(key, counter);
242 var x: BlockVec = undefined;
243 var buf: [64]u8 = undefined;
244 var i: usize = 0;
245 while (i + 64 <= in.len) : (i += 64) {
246 chacha20Core(x[0..], ctx);
247 contextFeedback(&x, ctx);
248 hashToBytes(buf[0..], x);
249
250 var xout = out[i..];
251 const xin = in[i..];
252 var j: usize = 0;
253 while (j < 64) : (j += 1) {
254 xout[j] = xin[j];
255 }
256 j = 0;
257 while (j < 64) : (j += 1) {
258 xout[j] ^= buf[j];
308 fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {
309 var ctx = initContext(key, counter);
310 var x: BlockVec = undefined;
311 var buf: [64]u8 = undefined;
312 var i: usize = 0;
313 while (i + 64 <= in.len) : (i += 64) {
314 chacha20Core(x[0..], ctx);
315 contextFeedback(&x, ctx);
316 hashToBytes(buf[0..], x);
317
318 var xout = out[i..];
319 const xin = in[i..];
320 var j: usize = 0;
321 while (j < 64) : (j += 1) {
322 xout[j] = xin[j];
323 }
324 j = 0;
325 while (j < 64) : (j += 1) {
326 xout[j] ^= buf[j];
327 }
328 ctx[12] += 1;
259329 }
260 ctx[12] += 1;
261 }
262 if (i < in.len) {
263 chacha20Core(x[0..], ctx);
264 contextFeedback(&x, ctx);
265 hashToBytes(buf[0..], x);
266
267 var xout = out[i..];
268 const xin = in[i..];
269 var j: usize = 0;
270 while (j < in.len % 64) : (j += 1) {
271 xout[j] = xin[j] ^ buf[j];
330 if (i < in.len) {
331 chacha20Core(x[0..], ctx);
332 contextFeedback(&x, ctx);
333 hashToBytes(buf[0..], x);
334
335 var xout = out[i..];
336 const xin = in[i..];
337 var j: usize = 0;
338 while (j < in.len % 64) : (j += 1) {
339 xout[j] = xin[j] ^ buf[j];
340 }
272341 }
273342 }
274 }
275343
276 fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
277 var c: [4]u32 = undefined;
278 for (c) |_, i| {
279 c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);
344 fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
345 var c: [4]u32 = undefined;
346 for (c) |_, i| {
347 c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);
348 }
349 const ctx = initContext(keyToWords(key), c);
350 var x: BlockVec = undefined;
351 chacha20Core(x[0..], ctx);
352 var out: [32]u8 = undefined;
353 mem.writeIntLittle(u32, out[0..4], x[0]);
354 mem.writeIntLittle(u32, out[4..8], x[1]);
355 mem.writeIntLittle(u32, out[8..12], x[2]);
356 mem.writeIntLittle(u32, out[12..16], x[3]);
357 mem.writeIntLittle(u32, out[16..20], x[12]);
358 mem.writeIntLittle(u32, out[20..24], x[13]);
359 mem.writeIntLittle(u32, out[24..28], x[14]);
360 mem.writeIntLittle(u32, out[28..32], x[15]);
361 return out;
280362 }
281 const ctx = initContext(keyToWords(key), c);
282 var x: BlockVec = undefined;
283 chacha20Core(x[0..], ctx);
284 var out: [32]u8 = undefined;
285 mem.writeIntLittle(u32, out[0..4], x[0]);
286 mem.writeIntLittle(u32, out[4..8], x[1]);
287 mem.writeIntLittle(u32, out[8..12], x[2]);
288 mem.writeIntLittle(u32, out[12..16], x[3]);
289 mem.writeIntLittle(u32, out[16..20], x[12]);
290 mem.writeIntLittle(u32, out[20..24], x[13]);
291 mem.writeIntLittle(u32, out[24..28], x[14]);
292 mem.writeIntLittle(u32, out[28..32], x[15]);
293 return out;
294 }
295};
363 };
364}
296365
297const ChaCha20Impl = if (std.Target.current.cpu.arch == .x86_64) ChaCha20VecImpl else ChaCha20NonVecImpl;
366fn ChaChaImpl(comptime rounds_nb: usize) type {
367 return if (std.Target.current.cpu.arch == .x86_64) ChaChaVecImpl(rounds_nb) else ChaChaNonVecImpl(rounds_nb);
368}
298369
299370fn keyToWords(key: [32]u8) [8]u32 {
300371 var k: [8]u32 = undefined;
......@@ -305,68 +376,239 @@ fn keyToWords(key: [32]u8) [8]u32 {
305376 return k;
306377}
307378
308/// ChaCha20 avoids the possibility of timing attacks, as there are no branches
309/// on secret key data.
310///
311/// in and out should be the same length.
312/// counter should generally be 0 or 1
313///
314/// ChaCha20 is self-reversing. To decrypt just run the cipher with the same
315/// counter, nonce, and key.
316pub const ChaCha20IETF = struct {
317 pub fn xor(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce: [12]u8) void {
318 assert(in.len == out.len);
319 assert((in.len >> 6) + counter <= maxInt(u32));
320
321 var c: [4]u32 = undefined;
322 c[0] = counter;
323 c[1] = mem.readIntLittle(u32, nonce[0..4]);
324 c[2] = mem.readIntLittle(u32, nonce[4..8]);
325 c[3] = mem.readIntLittle(u32, nonce[8..12]);
326 ChaCha20Impl.chacha20Xor(out, in, keyToWords(key), c);
327 }
328};
329
330/// This is the original ChaCha20 before RFC 7539, which recommends using the
331/// orgininal version on applications such as disk or file encryption that might
332/// exceed the 256 GiB limit of the 96-bit nonce version.
333pub const ChaCha20With64BitNonce = struct {
334 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [32]u8, nonce: [8]u8) void {
335 assert(in.len == out.len);
336 assert(counter +% (in.len >> 6) >= counter);
337
338 var cursor: usize = 0;
339 const k = keyToWords(key);
340 var c: [4]u32 = undefined;
341 c[0] = @truncate(u32, counter);
342 c[1] = @truncate(u32, counter >> 32);
343 c[2] = mem.readIntLittle(u32, nonce[0..4]);
344 c[3] = mem.readIntLittle(u32, nonce[4..8]);
345
346 const block_length = (1 << 6);
347 // The full block size is greater than the address space on a 32bit machine
348 const big_block = if (@sizeOf(usize) > 4) (block_length << 32) else maxInt(usize);
349
350 // first partial big block
351 if (((@intCast(u64, maxInt(u32) - @truncate(u32, counter)) + 1) << 6) < in.len) {
352 ChaCha20Impl.chacha20Xor(out[cursor..big_block], in[cursor..big_block], k, c);
353 cursor = big_block - cursor;
354 c[1] += 1;
355 if (comptime @sizeOf(usize) > 4) {
356 // A big block is giant: 256 GiB, but we can avoid this limitation
357 var remaining_blocks: u32 = @intCast(u32, (in.len / big_block));
358 var i: u32 = 0;
359 while (remaining_blocks > 0) : (remaining_blocks -= 1) {
360 ChaCha20Impl.chacha20Xor(out[cursor .. cursor + big_block], in[cursor .. cursor + big_block], k, c);
361 c[1] += 1; // upper 32-bit of counter, generic chacha20Xor() doesn't know about this.
362 cursor += big_block;
379fn extend(key: [32]u8, nonce: [24]u8, comptime rounds_nb: usize) struct { key: [32]u8, nonce: [12]u8 } {
380 var subnonce: [12]u8 = undefined;
381 mem.set(u8, subnonce[0..4], 0);
382 mem.copy(u8, subnonce[4..], nonce[16..24]);
383 return .{
384 .key = ChaChaImpl(rounds_nb).hchacha20(nonce[0..16].*, key),
385 .nonce = subnonce,
386 };
387}
388
389fn ChaChaIETF(comptime rounds_nb: usize) type {
390 return struct {
391 /// Nonce length in bytes.
392 pub const nonce_length = 12;
393 /// Key length in bytes.
394 pub const key_length = 32;
395
396 /// Add the output of the ChaCha20 stream cipher to `in` and stores the result into `out`.
397 /// WARNING: This function doesn't provide authenticated encryption.
398 /// Using the AEAD or one of the `box` versions is usually preferred.
399 pub fn xor(out: []u8, in: []const u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
400 assert(in.len == out.len);
401 assert(in.len / 64 <= (1 << 32 - 1) - counter);
402
403 var d: [4]u32 = undefined;
404 d[0] = counter;
405 d[1] = mem.readIntLittle(u32, nonce[0..4]);
406 d[2] = mem.readIntLittle(u32, nonce[4..8]);
407 d[3] = mem.readIntLittle(u32, nonce[8..12]);
408 ChaChaImpl(rounds_nb).chacha20Xor(out, in, keyToWords(key), d);
409 }
410 };
411}
412
413fn ChaChaWith64BitNonce(comptime rounds_nb: usize) type {
414 return struct {
415 /// Nonce length in bytes.
416 pub const nonce_length = 8;
417 /// Key length in bytes.
418 pub const key_length = 32;
419
420 /// Add the output of the ChaCha20 stream cipher to `in` and stores the result into `out`.
421 /// WARNING: This function doesn't provide authenticated encryption.
422 /// Using the AEAD or one of the `box` versions is usually preferred.
423 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
424 assert(in.len == out.len);
425 assert(in.len / 64 <= (1 << 64 - 1) - counter);
426
427 var cursor: usize = 0;
428 const k = keyToWords(key);
429 var c: [4]u32 = undefined;
430 c[0] = @truncate(u32, counter);
431 c[1] = @truncate(u32, counter >> 32);
432 c[2] = mem.readIntLittle(u32, nonce[0..4]);
433 c[3] = mem.readIntLittle(u32, nonce[4..8]);
434
435 const block_length = (1 << 6);
436 // The full block size is greater than the address space on a 32bit machine
437 const big_block = if (@sizeOf(usize) > 4) (block_length << 32) else maxInt(usize);
438
439 // first partial big block
440 if (((@intCast(u64, maxInt(u32) - @truncate(u32, counter)) + 1) << 6) < in.len) {
441 ChaChaImpl(rounds_nb).chacha20Xor(out[cursor..big_block], in[cursor..big_block], k, c);
442 cursor = big_block - cursor;
443 c[1] += 1;
444 if (comptime @sizeOf(usize) > 4) {
445 // A big block is giant: 256 GiB, but we can avoid this limitation
446 var remaining_blocks: u32 = @intCast(u32, (in.len / big_block));
447 var i: u32 = 0;
448 while (remaining_blocks > 0) : (remaining_blocks -= 1) {
449 ChaChaImpl(rounds_nb).chacha20Xor(out[cursor .. cursor + big_block], in[cursor .. cursor + big_block], k, c);
450 c[1] += 1; // upper 32-bit of counter, generic chacha20Xor() doesn't know about this.
451 cursor += big_block;
452 }
363453 }
364454 }
455 ChaChaImpl(rounds_nb).chacha20Xor(out[cursor..], in[cursor..], k, c);
456 }
457 };
458}
459
460fn XChaChaIETF(comptime rounds_nb: usize) type {
461 return struct {
462 /// Nonce length in bytes.
463 pub const nonce_length = 24;
464 /// Key length in bytes.
465 pub const key_length = 32;
466
467 /// Add the output of the XChaCha20 stream cipher to `in` and stores the result into `out`.
468 /// WARNING: This function doesn't provide authenticated encryption.
469 /// Using the AEAD or one of the `box` versions is usually preferred.
470 pub fn xor(out: []u8, in: []const u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
471 const extended = extend(key, nonce, rounds_nb);
472 ChaChaIETF(rounds_nb).xor(out, in, counter, extended.key, extended.nonce);
473 }
474 };
475}
476
477fn ChaChaPoly1305(comptime rounds_nb: usize) type {
478 return struct {
479 pub const tag_length = 16;
480 pub const nonce_length = 12;
481 pub const key_length = 32;
482
483 /// c: ciphertext: output buffer should be of size m.len
484 /// tag: authentication tag: output MAC
485 /// m: message
486 /// ad: Associated Data
487 /// npub: public nonce
488 /// k: private key
489 pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
490 assert(c.len == m.len);
491
492 var polyKey = [_]u8{0} ** 32;
493 ChaChaIETF(rounds_nb).xor(polyKey[0..], polyKey[0..], 0, k, npub);
494
495 ChaChaIETF(rounds_nb).xor(c[0..m.len], m, 1, k, npub);
496
497 var mac = Poly1305.init(polyKey[0..]);
498 mac.update(ad);
499 if (ad.len % 16 != 0) {
500 const zeros = [_]u8{0} ** 16;
501 const padding = 16 - (ad.len % 16);
502 mac.update(zeros[0..padding]);
503 }
504 mac.update(c[0..m.len]);
505 if (m.len % 16 != 0) {
506 const zeros = [_]u8{0} ** 16;
507 const padding = 16 - (m.len % 16);
508 mac.update(zeros[0..padding]);
509 }
510 var lens: [16]u8 = undefined;
511 mem.writeIntLittle(u64, lens[0..8], ad.len);
512 mem.writeIntLittle(u64, lens[8..16], m.len);
513 mac.update(lens[0..]);
514 mac.final(tag);
515 }
516
517 /// m: message: output buffer should be of size c.len
518 /// c: ciphertext
519 /// tag: authentication tag
520 /// ad: Associated Data
521 /// npub: public nonce
522 /// k: private key
523 /// NOTE: the check of the authentication tag is currently not done in constant time
524 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void {
525 assert(c.len == m.len);
526
527 var polyKey = [_]u8{0} ** 32;
528 ChaChaIETF(rounds_nb).xor(polyKey[0..], polyKey[0..], 0, k, npub);
529
530 var mac = Poly1305.init(polyKey[0..]);
531
532 mac.update(ad);
533 if (ad.len % 16 != 0) {
534 const zeros = [_]u8{0} ** 16;
535 const padding = 16 - (ad.len % 16);
536 mac.update(zeros[0..padding]);
537 }
538 mac.update(c);
539 if (c.len % 16 != 0) {
540 const zeros = [_]u8{0} ** 16;
541 const padding = 16 - (c.len % 16);
542 mac.update(zeros[0..padding]);
543 }
544 var lens: [16]u8 = undefined;
545 mem.writeIntLittle(u64, lens[0..8], ad.len);
546 mem.writeIntLittle(u64, lens[8..16], c.len);
547 mac.update(lens[0..]);
548 var computedTag: [16]u8 = undefined;
549 mac.final(computedTag[0..]);
550
551 var acc: u8 = 0;
552 for (computedTag) |_, i| {
553 acc |= computedTag[i] ^ tag[i];
554 }
555 if (acc != 0) {
556 return error.AuthenticationFailed;
557 }
558 ChaChaIETF(rounds_nb).xor(m[0..c.len], c, 1, k, npub);
559 }
560 };
561}
562
563fn XChaChaPoly1305(comptime rounds_nb: usize) type {
564 return struct {
565 pub const tag_length = 16;
566 pub const nonce_length = 24;
567 pub const key_length = 32;
568
569 /// c: ciphertext: output buffer should be of size m.len
570 /// tag: authentication tag: output MAC
571 /// m: message
572 /// ad: Associated Data
573 /// npub: public nonce
574 /// k: private key
575 pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
576 const extended = extend(k, npub, rounds_nb);
577 return ChaChaPoly1305(rounds_nb).encrypt(c, tag, m, ad, extended.nonce, extended.key);
365578 }
366579
367 ChaCha20Impl.chacha20Xor(out[cursor..], in[cursor..], k, c);
580 /// m: message: output buffer should be of size c.len
581 /// c: ciphertext
582 /// tag: authentication tag
583 /// ad: Associated Data
584 /// npub: public nonce
585 /// k: private key
586 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void {
587 const extended = extend(k, npub, rounds_nb);
588 return ChaChaPoly1305(rounds_nb).decrypt(m, c, tag, ad, extended.nonce, extended.key);
589 }
590 };
591}
592
593test "chacha20 AEAD API" {
594 const aeads = [_]type{ ChaCha20Poly1305, XChaCha20Poly1305 };
595 const m = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
596 const ad = "Additional data";
597
598 inline for (aeads) |aead| {
599 const key = [_]u8{69} ** aead.key_length;
600 const nonce = [_]u8{42} ** aead.nonce_length;
601 var c: [m.len]u8 = undefined;
602 var tag: [aead.tag_length]u8 = undefined;
603 var out: [m.len]u8 = undefined;
604
605 aead.encrypt(c[0..], tag[0..], m, ad, nonce, key);
606 try aead.decrypt(out[0..], c[0..], tag, ad[0..], nonce, key);
607 testing.expectEqualSlices(u8, out[0..], m);
608 c[0] += 1;
609 testing.expectError(error.AuthenticationFailed, aead.decrypt(out[0..], c[0..], tag, ad[0..], nonce, key));
368610 }
369};
611}
370612
371613// https://tools.ietf.org/html/rfc7539#section-2.4.2
372614test "crypto.chacha20 test vector sunscreen" {
......@@ -387,7 +629,7 @@ test "crypto.chacha20 test vector sunscreen" {
387629 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42,
388630 0x87, 0x4d,
389631 };
390 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.";
632 const m = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
391633 var result: [114]u8 = undefined;
392634 const key = [_]u8{
393635 0, 1, 2, 3, 4, 5, 6, 7,
......@@ -401,13 +643,12 @@ test "crypto.chacha20 test vector sunscreen" {
401643 0, 0, 0, 0,
402644 };
403645
404 ChaCha20IETF.xor(result[0..], input[0..], 1, key, nonce);
646 ChaCha20IETF.xor(result[0..], m[0..], 1, key, nonce);
405647 testing.expectEqualSlices(u8, &expected_result, &result);
406648
407 // Chacha20 is self-reversing.
408 var plaintext: [114]u8 = undefined;
409 ChaCha20IETF.xor(plaintext[0..], result[0..], 1, key, nonce);
410 testing.expect(mem.order(u8, input, &plaintext) == .eq);
649 var m2: [114]u8 = undefined;
650 ChaCha20IETF.xor(m2[0..], result[0..], 1, key, nonce);
651 testing.expect(mem.order(u8, m, &m2) == .eq);
411652}
412653
413654// https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7
......@@ -422,7 +663,7 @@ test "crypto.chacha20 test vector 1" {
422663 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c,
423664 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86,
424665 };
425 const input = [_]u8{
666 const m = [_]u8{
426667 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
427668 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
428669 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
......@@ -441,7 +682,7 @@ test "crypto.chacha20 test vector 1" {
441682 };
442683 const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 };
443684
444 ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce);
685 ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce);
445686 testing.expectEqualSlices(u8, &expected_result, &result);
446687}
447688
......@@ -456,7 +697,7 @@ test "crypto.chacha20 test vector 2" {
456697 0x53, 0xd7, 0x92, 0xb1, 0xc4, 0x3f, 0xea, 0x81,
457698 0x7e, 0x9a, 0xd2, 0x75, 0xae, 0x54, 0x69, 0x63,
458699 };
459 const input = [_]u8{
700 const m = [_]u8{
460701 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
461702 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
462703 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
......@@ -475,7 +716,7 @@ test "crypto.chacha20 test vector 2" {
475716 };
476717 const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 };
477718
478 ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce);
719 ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce);
479720 testing.expectEqualSlices(u8, &expected_result, &result);
480721}
481722
......@@ -490,7 +731,7 @@ test "crypto.chacha20 test vector 3" {
490731 0x52, 0x77, 0x06, 0x2e, 0xb7, 0xa0, 0x43, 0x3e,
491732 0x44, 0x5f, 0x41, 0xe3,
492733 };
493 const input = [_]u8{
734 const m = [_]u8{
494735 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
495736 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
496737 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
......@@ -509,7 +750,7 @@ test "crypto.chacha20 test vector 3" {
509750 };
510751 const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 1 };
511752
512 ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce);
753 ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce);
513754 testing.expectEqualSlices(u8, &expected_result, &result);
514755}
515756
......@@ -524,7 +765,7 @@ test "crypto.chacha20 test vector 4" {
524765 0x5d, 0xdc, 0x49, 0x7a, 0x0b, 0x46, 0x6e, 0x7d,
525766 0x6b, 0xbd, 0xb0, 0x04, 0x1b, 0x2f, 0x58, 0x6b,
526767 };
527 const input = [_]u8{
768 const m = [_]u8{
528769 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
529770 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
530771 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
......@@ -543,7 +784,7 @@ test "crypto.chacha20 test vector 4" {
543784 };
544785 const nonce = [_]u8{ 1, 0, 0, 0, 0, 0, 0, 0 };
545786
546 ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce);
787 ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce);
547788 testing.expectEqualSlices(u8, &expected_result, &result);
548789}
549790
......@@ -585,7 +826,7 @@ test "crypto.chacha20 test vector 5" {
585826 0x87, 0x46, 0xd4, 0x52, 0x4d, 0x38, 0x40, 0x7a,
586827 0x6d, 0xeb, 0x3a, 0xb7, 0x8f, 0xab, 0x78, 0xc9,
587828 };
588 const input = [_]u8{
829 const m = [_]u8{
589830 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
590831 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
591832 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
......@@ -615,147 +856,14 @@ test "crypto.chacha20 test vector 5" {
615856 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
616857 };
617858
618 ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce);
859 ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce);
619860 testing.expectEqualSlices(u8, &expected_result, &result);
620861}
621862
622pub const chacha20poly1305_tag_length = 16;
623
624fn chacha20poly1305SealDetached(ciphertext: []u8, tag: *[chacha20poly1305_tag_length]u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void {
625 assert(ciphertext.len == plaintext.len);
626
627 // derive poly1305 key
628 var polyKey = [_]u8{0} ** 32;
629 ChaCha20IETF.xor(polyKey[0..], polyKey[0..], 0, key, nonce);
630
631 // encrypt plaintext
632 ChaCha20IETF.xor(ciphertext[0..plaintext.len], plaintext, 1, key, nonce);
633
634 // construct mac
635 var mac = Poly1305.init(polyKey[0..]);
636 mac.update(data);
637 if (data.len % 16 != 0) {
638 const zeros = [_]u8{0} ** 16;
639 const padding = 16 - (data.len % 16);
640 mac.update(zeros[0..padding]);
641 }
642 mac.update(ciphertext[0..plaintext.len]);
643 if (plaintext.len % 16 != 0) {
644 const zeros = [_]u8{0} ** 16;
645 const padding = 16 - (plaintext.len % 16);
646 mac.update(zeros[0..padding]);
647 }
648 var lens: [16]u8 = undefined;
649 mem.writeIntLittle(u64, lens[0..8], data.len);
650 mem.writeIntLittle(u64, lens[8..16], plaintext.len);
651 mac.update(lens[0..]);
652 mac.final(tag);
653}
654
655fn chacha20poly1305Seal(ciphertextAndTag: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void {
656 return chacha20poly1305SealDetached(ciphertextAndTag[0..plaintext.len], ciphertextAndTag[plaintext.len..][0..chacha20poly1305_tag_length], plaintext, data, key, nonce);
657}
658
659/// Verifies and decrypts an authenticated message produced by chacha20poly1305SealDetached.
660fn chacha20poly1305OpenDetached(dst: []u8, ciphertext: []const u8, tag: *const [chacha20poly1305_tag_length]u8, data: []const u8, key: [32]u8, nonce: [12]u8) Error!void {
661 // split ciphertext and tag
662 assert(dst.len == ciphertext.len);
663
664 // derive poly1305 key
665 var polyKey = [_]u8{0} ** 32;
666 ChaCha20IETF.xor(polyKey[0..], polyKey[0..], 0, key, nonce);
667
668 // construct mac
669 var mac = Poly1305.init(polyKey[0..]);
670
671 mac.update(data);
672 if (data.len % 16 != 0) {
673 const zeros = [_]u8{0} ** 16;
674 const padding = 16 - (data.len % 16);
675 mac.update(zeros[0..padding]);
676 }
677 mac.update(ciphertext);
678 if (ciphertext.len % 16 != 0) {
679 const zeros = [_]u8{0} ** 16;
680 const padding = 16 - (ciphertext.len % 16);
681 mac.update(zeros[0..padding]);
682 }
683 var lens: [16]u8 = undefined;
684 mem.writeIntLittle(u64, lens[0..8], data.len);
685 mem.writeIntLittle(u64, lens[8..16], ciphertext.len);
686 mac.update(lens[0..]);
687 var computedTag: [16]u8 = undefined;
688 mac.final(computedTag[0..]);
689
690 // verify mac in constant time
691 // TODO: we can't currently guarantee that this will run in constant time.
692 // See https://github.com/ziglang/zig/issues/1776
693 var acc: u8 = 0;
694 for (computedTag) |_, i| {
695 acc |= computedTag[i] ^ tag[i];
696 }
697 if (acc != 0) {
698 return error.AuthenticationFailed;
699 }
700
701 // decrypt ciphertext
702 ChaCha20IETF.xor(dst[0..ciphertext.len], ciphertext, 1, key, nonce);
703}
704
705/// Verifies and decrypts an authenticated message produced by chacha20poly1305Seal.
706fn chacha20poly1305Open(dst: []u8, ciphertextAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) Error!void {
707 if (ciphertextAndTag.len < chacha20poly1305_tag_length) {
708 return error.AuthenticationFailed;
709 }
710 const ciphertextLen = ciphertextAndTag.len - chacha20poly1305_tag_length;
711 return try chacha20poly1305OpenDetached(dst, ciphertextAndTag[0..ciphertextLen], ciphertextAndTag[ciphertextLen..][0..chacha20poly1305_tag_length], data, key, nonce);
712}
713
714fn extend(key: [32]u8, nonce: [24]u8) struct { key: [32]u8, nonce: [12]u8 } {
715 var subnonce: [12]u8 = undefined;
716 mem.set(u8, subnonce[0..4], 0);
717 mem.copy(u8, subnonce[4..], nonce[16..24]);
718 return .{
719 .key = ChaCha20Impl.hchacha20(nonce[0..16].*, key),
720 .nonce = subnonce,
721 };
722}
723
724pub const XChaCha20IETF = struct {
725 pub fn xor(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce: [24]u8) void {
726 const extended = extend(key, nonce);
727 ChaCha20IETF.xor(out, in, counter, extended.key, extended.nonce);
728 }
729};
730
731pub const xchacha20poly1305_tag_length = 16;
732
733fn xchacha20poly1305SealDetached(ciphertext: []u8, tag: *[chacha20poly1305_tag_length]u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) void {
734 const extended = extend(key, nonce);
735 return chacha20poly1305SealDetached(ciphertext, tag, plaintext, data, extended.key, extended.nonce);
736}
737
738fn xchacha20poly1305Seal(ciphertextAndTag: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) void {
739 const extended = extend(key, nonce);
740 return chacha20poly1305Seal(ciphertextAndTag, plaintext, data, extended.key, extended.nonce);
741}
742
743/// Verifies and decrypts an authenticated message produced by xchacha20poly1305SealDetached.
744fn xchacha20poly1305OpenDetached(plaintext: []u8, ciphertext: []const u8, tag: *const [chacha20poly1305_tag_length]u8, data: []const u8, key: [32]u8, nonce: [24]u8) Error!void {
745 const extended = extend(key, nonce);
746 return try chacha20poly1305OpenDetached(plaintext, ciphertext, tag, data, extended.key, extended.nonce);
747}
748
749/// Verifies and decrypts an authenticated message produced by xchacha20poly1305Seal.
750fn xchacha20poly1305Open(ciphertextAndTag: []u8, msgAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) Error!void {
751 const extended = extend(key, nonce);
752 return try chacha20poly1305Open(ciphertextAndTag, msgAndTag, data, extended.key, extended.nonce);
753}
754
755863test "seal" {
756864 {
757 const plaintext = "";
758 const data = "";
865 const m = "";
866 const ad = "";
759867 const key = [_]u8{
760868 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
761869 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
......@@ -764,11 +872,11 @@ test "seal" {
764872 const exp_out = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 };
765873
766874 var out: [exp_out.len]u8 = undefined;
767 chacha20poly1305Seal(out[0..], plaintext, data, key, nonce);
875 ChaCha20Poly1305.encrypt(out[0..m.len], out[m.len..], m, ad, nonce, key);
768876 testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
769877 }
770878 {
771 const plaintext = [_]u8{
879 const m = [_]u8{
772880 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c,
773881 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73,
774882 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63,
......@@ -778,7 +886,7 @@ test "seal" {
778886 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69,
779887 0x74, 0x2e,
780888 };
781 const data = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 };
889 const ad = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 };
782890 const key = [_]u8{
783891 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
784892 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
......@@ -797,15 +905,15 @@ test "seal" {
797905 };
798906
799907 var out: [exp_out.len]u8 = undefined;
800 chacha20poly1305Seal(out[0..], plaintext[0..], data[0..], key, nonce);
908 ChaCha20Poly1305.encrypt(out[0..m.len], out[m.len..], m[0..], ad[0..], nonce, key);
801909 testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
802910 }
803911}
804912
805913test "open" {
806914 {
807 const ciphertext = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 };
808 const data = "";
915 const c = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 };
916 const ad = "";
809917 const key = [_]u8{
810918 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
811919 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
......@@ -814,11 +922,11 @@ test "open" {
814922 const exp_out = "";
815923
816924 var out: [exp_out.len]u8 = undefined;
817 try chacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce);
925 try ChaCha20Poly1305.decrypt(out[0..], c[0..exp_out.len], c[exp_out.len..].*, ad[0..], nonce, key);
818926 testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
819927 }
820928 {
821 const ciphertext = [_]u8{
929 const c = [_]u8{
822930 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2,
823931 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x8, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6,
824932 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b,
......@@ -829,7 +937,7 @@ test "open" {
829937 0x61, 0x16, 0x1a, 0xe1, 0xb, 0x59, 0x4f, 0x9, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60,
830938 0x6, 0x91,
831939 };
832 const data = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 };
940 const ad = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 };
833941 const key = [_]u8{
834942 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
835943 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
......@@ -847,126 +955,45 @@ test "open" {
847955 };
848956
849957 var out: [exp_out.len]u8 = undefined;
850 try chacha20poly1305Open(out[0..], ciphertext[0..], data[0..], key, nonce);
958 try ChaCha20Poly1305.decrypt(out[0..], c[0..exp_out.len], c[exp_out.len..].*, ad[0..], nonce, key);
851959 testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
852960
853961 // corrupting the ciphertext, data, key, or nonce should cause a failure
854 var bad_ciphertext = ciphertext;
855 bad_ciphertext[0] ^= 1;
856 testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], bad_ciphertext[0..], data[0..], key, nonce));
857 var bad_data = data;
858 bad_data[0] ^= 1;
859 testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[0..], bad_data[0..], key, nonce));
962 var bad_c = c;
963 bad_c[0] ^= 1;
964 testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], bad_c[0..out.len], bad_c[out.len..].*, ad[0..], nonce, key));
965 var bad_ad = ad;
966 bad_ad[0] ^= 1;
967 testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], c[0..out.len], c[out.len..].*, bad_ad[0..], nonce, key));
860968 var bad_key = key;
861969 bad_key[0] ^= 1;
862 testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[0..], data[0..], bad_key, nonce));
970 testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], c[0..out.len], c[out.len..].*, ad[0..], nonce, bad_key));
863971 var bad_nonce = nonce;
864972 bad_nonce[0] ^= 1;
865 testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[0..], data[0..], key, bad_nonce));
866
867 // a short ciphertext should result in a different error
868 testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], "", data[0..], key, bad_nonce));
973 testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], c[0..out.len], c[out.len..].*, ad[0..], bad_nonce, key));
869974 }
870975}
871976
872977test "crypto.xchacha20" {
873978 const key = [_]u8{69} ** 32;
874979 const nonce = [_]u8{42} ** 24;
875 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.";
980 const m = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
876981 {
877 var ciphertext: [input.len]u8 = undefined;
878 XChaCha20IETF.xor(ciphertext[0..], input[0..], 0, key, nonce);
879 var buf: [2 * ciphertext.len]u8 = undefined;
880 testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&ciphertext)}), "E0A1BCF939654AFDBDC1746EC49832647C19D891F0D1A81FC0C1703B4514BDEA584B512F6908C2C5E9DD18D5CBC1805DE5803FE3B9CA5F193FB8359E91FAB0C3BB40309A292EB1CF49685C65C4A3ADF4F11DB0CD2B6B67FBC174BC2E860E8F769FD3565BBFAD1C845E05A0FED9BE167C240D");
982 var c: [m.len]u8 = undefined;
983 XChaCha20IETF.xor(c[0..], m[0..], 0, key, nonce);
984 var buf: [2 * c.len]u8 = undefined;
985 testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&c)}), "E0A1BCF939654AFDBDC1746EC49832647C19D891F0D1A81FC0C1703B4514BDEA584B512F6908C2C5E9DD18D5CBC1805DE5803FE3B9CA5F193FB8359E91FAB0C3BB40309A292EB1CF49685C65C4A3ADF4F11DB0CD2B6B67FBC174BC2E860E8F769FD3565BBFAD1C845E05A0FED9BE167C240D");
881986 }
882987 {
883 const data = "Additional data";
884 var ciphertext: [input.len + xchacha20poly1305_tag_length]u8 = undefined;
885 xchacha20poly1305Seal(ciphertext[0..], input, data, key, nonce);
886 var out: [input.len]u8 = undefined;
887 try xchacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce);
888 var buf: [2 * ciphertext.len]u8 = undefined;
889 testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&ciphertext)}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234");
890 testing.expectEqualSlices(u8, out[0..], input);
891 ciphertext[0] += 1;
892 testing.expectError(error.AuthenticationFailed, xchacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce));
893 }
894}
895
896pub const Chacha20Poly1305 = struct {
897 pub const tag_length = 16;
898 pub const nonce_length = 12;
899 pub const key_length = 32;
900
901 /// c: ciphertext: output buffer should be of size m.len
902 /// tag: authentication tag: output MAC
903 /// m: message
904 /// ad: Associated Data
905 /// npub: public nonce
906 /// k: private key
907 pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
908 assert(c.len == m.len);
909 return chacha20poly1305SealDetached(c, tag, m, ad, k, npub);
910 }
911
912 /// m: message: output buffer should be of size c.len
913 /// c: ciphertext
914 /// tag: authentication tag
915 /// ad: Associated Data
916 /// npub: public nonce
917 /// k: private key
918 /// NOTE: the check of the authentication tag is currently not done in constant time
919 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void {
920 assert(c.len == m.len);
921 return try chacha20poly1305OpenDetached(m, c, tag[0..], ad, k, npub);
922 }
923};
924
925pub const XChacha20Poly1305 = struct {
926 pub const tag_length = 16;
927 pub const nonce_length = 24;
928 pub const key_length = 32;
929
930 /// c: ciphertext: output buffer should be of size m.len
931 /// tag: authentication tag: output MAC
932 /// m: message
933 /// ad: Associated Data
934 /// npub: public nonce
935 /// k: private key
936 pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
937 assert(c.len == m.len);
938 return xchacha20poly1305SealDetached(c, tag, m, ad, k, npub);
939 }
940
941 /// m: message: output buffer should be of size c.len
942 /// c: ciphertext
943 /// tag: authentication tag
944 /// ad: Associated Data
945 /// npub: public nonce
946 /// k: private key
947 /// NOTE: the check of the authentication tag is currently not done in constant time
948 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void {
949 assert(c.len == m.len);
950 return try xchacha20poly1305OpenDetached(m, c, tag[0..], ad, k, npub);
951 }
952};
953
954test "chacha20 AEAD API" {
955 const aeads = [_]type{ Chacha20Poly1305, XChacha20Poly1305 };
956 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.";
957 const data = "Additional data";
958
959 inline for (aeads) |aead| {
960 const key = [_]u8{69} ** aead.key_length;
961 const nonce = [_]u8{42} ** aead.nonce_length;
962 var ciphertext: [input.len]u8 = undefined;
963 var tag: [aead.tag_length]u8 = undefined;
964 var out: [input.len]u8 = undefined;
965
966 aead.encrypt(ciphertext[0..], tag[0..], input, data, nonce, key);
967 try aead.decrypt(out[0..], ciphertext[0..], tag, data[0..], nonce, key);
968 testing.expectEqualSlices(u8, out[0..], input);
969 ciphertext[0] += 1;
970 testing.expectError(error.AuthenticationFailed, aead.decrypt(out[0..], ciphertext[0..], tag, data[0..], nonce, key));
988 const ad = "Additional data";
989 var c: [m.len + XChaCha20Poly1305.tag_length]u8 = undefined;
990 XChaCha20Poly1305.encrypt(c[0..m.len], c[m.len..], m, ad, nonce, key);
991 var out: [m.len]u8 = undefined;
992 try XChaCha20Poly1305.decrypt(out[0..], c[0..m.len], c[m.len..].*, ad, nonce, key);
993 var buf: [2 * c.len]u8 = undefined;
994 testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&c)}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234");
995 testing.expectEqualSlices(u8, out[0..], m);
996 c[0] += 1;
997 testing.expectError(error.AuthenticationFailed, XChaCha20Poly1305.decrypt(out[0..], c[0..m.len], c[m.len..].*, ad, nonce, key));
971998 }
972999}