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 {...@@ -24,8 +24,12 @@ pub const aead = struct {
24 pub const Gimli = @import("crypto/gimli.zig").Aead;24 pub const Gimli = @import("crypto/gimli.zig").Aead;
2525
26 pub const chacha_poly = struct {26 pub const chacha_poly = struct {
27 pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").Chacha20Poly1305;27 pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").ChaCha20Poly1305;
28 pub const XChaCha20Poly1305 = @import("crypto/chacha20.zig").XChacha20Poly1305;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;
29 };33 };
3034
31 pub const isap = @import("crypto/isap.zig");35 pub const isap = @import("crypto/isap.zig");
...@@ -119,8 +123,14 @@ pub const sign = struct {...@@ -119,8 +123,14 @@ pub const sign = struct {
119pub const stream = struct {123pub const stream = struct {
120 pub const chacha = struct {124 pub const chacha = struct {
121 pub const ChaCha20IETF = @import("crypto/chacha20.zig").ChaCha20IETF;125 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;
122 pub const ChaCha20With64BitNonce = @import("crypto/chacha20.zig").ChaCha20With64BitNonce;128 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;
123 pub const XChaCha20IETF = @import("crypto/chacha20.zig").XChaCha20IETF;131 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;
124 };134 };
125135
126 pub const salsa = struct {136 pub const salsa = struct {
lib/std/crypto/benchmark.zig+1
...@@ -202,6 +202,7 @@ pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime...@@ -202,6 +202,7 @@ pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime
202const aeads = [_]Crypto{202const aeads = [_]Crypto{
203 Crypto{ .ty = crypto.aead.chacha_poly.ChaCha20Poly1305, .name = "chacha20Poly1305" },203 Crypto{ .ty = crypto.aead.chacha_poly.ChaCha20Poly1305, .name = "chacha20Poly1305" },
204 Crypto{ .ty = crypto.aead.chacha_poly.XChaCha20Poly1305, .name = "xchacha20Poly1305" },204 Crypto{ .ty = crypto.aead.chacha_poly.XChaCha20Poly1305, .name = "xchacha20Poly1305" },
205 Crypto{ .ty = crypto.aead.chacha_poly.XChaCha8Poly1305, .name = "xchacha8Poly1305" },
205 Crypto{ .ty = crypto.aead.salsa_poly.XSalsa20Poly1305, .name = "xsalsa20Poly1305" },206 Crypto{ .ty = crypto.aead.salsa_poly.XSalsa20Poly1305, .name = "xsalsa20Poly1305" },
206 Crypto{ .ty = crypto.aead.Gimli, .name = "gimli-aead" },207 Crypto{ .ty = crypto.aead.Gimli, .name = "gimli-aead" },
207 Crypto{ .ty = crypto.aead.aegis.Aegis128L, .name = "aegis-128l" },208 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;...@@ -15,286 +15,357 @@ const Vector = std.meta.Vector;
15const Poly1305 = std.crypto.onetimeauth.Poly1305;15const Poly1305 = std.crypto.onetimeauth.Poly1305;
16const Error = std.crypto.Error;16const 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
18// Vectorized implementation of the core function83// Vectorized implementation of the core function
19const ChaCha20VecImpl = struct {84fn ChaChaVecImpl(comptime rounds_nb: usize) type {
20 const Lane = Vector(4, u32);85 return struct {
21 const BlockVec = [4]Lane;86 const Lane = Vector(4, u32);
2287 const BlockVec = [4]Lane;
23 fn initContext(key: [8]u32, d: [4]u32) BlockVec {88
24 const c = "expand 32-byte k";89 fn initContext(key: [8]u32, d: [4]u32) BlockVec {
25 const constant_le = comptime Lane{90 const c = "expand 32-byte k";
26 mem.readIntLittle(u32, c[0..4]),91 const constant_le = comptime Lane{
27 mem.readIntLittle(u32, c[4..8]),92 mem.readIntLittle(u32, c[0..4]),
28 mem.readIntLittle(u32, c[8..12]),93 mem.readIntLittle(u32, c[4..8]),
29 mem.readIntLittle(u32, c[12..16]),94 mem.readIntLittle(u32, c[8..12]),
30 };95 mem.readIntLittle(u32, c[12..16]),
31 return BlockVec{96 };
32 constant_le,97 return BlockVec{
33 Lane{ key[0], key[1], key[2], key[3] },98 constant_le,
34 Lane{ key[4], key[5], key[6], key[7] },99 Lane{ key[0], key[1], key[2], key[3] },
35 Lane{ d[0], d[1], d[2], d[3] },100 Lane{ key[4], key[5], key[6], key[7] },
36 };101 Lane{ d[0], d[1], d[2], d[3] },
37 }102 };
103 }
38104
39 fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void {105 fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void {
40 x.* = input;106 x.* = input;
41107
42 var r: usize = 0;108 var r: usize = 0;
43 while (r < 20) : (r += 2) {109 while (r < rounds_nb) : (r += 2) {
44 x[0] +%= x[1];110 x[0] +%= x[1];
45 x[3] ^= x[0];111 x[3] ^= x[0];
46 x[3] = math.rotl(Lane, x[3], 16);112 x[3] = math.rotl(Lane, x[3], 16);
47113
48 x[2] +%= x[3];114 x[2] +%= x[3];
49 x[1] ^= x[2];115 x[1] ^= x[2];
50 x[1] = math.rotl(Lane, x[1], 12);116 x[1] = math.rotl(Lane, x[1], 12);
51117
52 x[0] +%= x[1];118 x[0] +%= x[1];
53 x[3] ^= x[0];119 x[3] ^= x[0];
54 x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 3, 0, 1, 2 });120 x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 3, 0, 1, 2 });
55 x[3] = math.rotl(Lane, x[3], 8);121 x[3] = math.rotl(Lane, x[3], 8);
56122
57 x[2] +%= x[3];123 x[2] +%= x[3];
58 x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 });124 x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 });
59 x[1] ^= x[2];125 x[1] ^= x[2];
60 x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 1, 2, 3, 0 });126 x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 1, 2, 3, 0 });
61 x[1] = math.rotl(Lane, x[1], 7);127 x[1] = math.rotl(Lane, x[1], 7);
62128
63 x[0] +%= x[1];129 x[0] +%= x[1];
64 x[3] ^= x[0];130 x[3] ^= x[0];
65 x[3] = math.rotl(Lane, x[3], 16);131 x[3] = math.rotl(Lane, x[3], 16);
66132
67 x[2] +%= x[3];133 x[2] +%= x[3];
68 x[1] ^= x[2];134 x[1] ^= x[2];
69 x[1] = math.rotl(Lane, x[1], 12);135 x[1] = math.rotl(Lane, x[1], 12);
70136
71 x[0] +%= x[1];137 x[0] +%= x[1];
72 x[3] ^= x[0];138 x[3] ^= x[0];
73 x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 1, 2, 3, 0 });139 x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 1, 2, 3, 0 });
74 x[3] = math.rotl(Lane, x[3], 8);140 x[3] = math.rotl(Lane, x[3], 8);
75141
76 x[2] +%= x[3];142 x[2] +%= x[3];
77 x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 });143 x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 });
78 x[1] ^= x[2];144 x[1] ^= x[2];
79 x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 3, 0, 1, 2 });145 x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 3, 0, 1, 2 });
80 x[1] = math.rotl(Lane, x[1], 7);146 x[1] = math.rotl(Lane, x[1], 7);
147 }
81 }148 }
82 }
83149
84 fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void {150 fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void {
85 var i: usize = 0;151 var i: usize = 0;
86 while (i < 4) : (i += 1) {152 while (i < 4) : (i += 1) {
87 mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i][0]);153 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]);154 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]);155 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]);156 mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i][3]);
157 }
91 }158 }
92 }
93159
94 fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void {160 fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void {
95 x[0] +%= ctx[0];161 x[0] +%= ctx[0];
96 x[1] +%= ctx[1];162 x[1] +%= ctx[1];
97 x[2] +%= ctx[2];163 x[2] +%= ctx[2];
98 x[3] +%= ctx[3];164 x[3] +%= ctx[3];
99 }165 }
100166
101 fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {167 fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {
102 var ctx = initContext(key, counter);168 var ctx = initContext(key, counter);
103 var x: BlockVec = undefined;169 var x: BlockVec = undefined;
104 var buf: [64]u8 = undefined;170 var buf: [64]u8 = undefined;
105 var i: usize = 0;171 var i: usize = 0;
106 while (i + 64 <= in.len) : (i += 64) {172 while (i + 64 <= in.len) : (i += 64) {
107 chacha20Core(x[0..], ctx);173 chacha20Core(x[0..], ctx);
108 contextFeedback(&x, ctx);174 contextFeedback(&x, ctx);
109 hashToBytes(buf[0..], x);175 hashToBytes(buf[0..], x);
110176
111 var xout = out[i..];177 var xout = out[i..];
112 const xin = in[i..];178 const xin = in[i..];
113 var j: usize = 0;179 var j: usize = 0;
114 while (j < 64) : (j += 1) {180 while (j < 64) : (j += 1) {
115 xout[j] = xin[j];181 xout[j] = xin[j];
116 }182 }
117 j = 0;183 j = 0;
118 while (j < 64) : (j += 1) {184 while (j < 64) : (j += 1) {
119 xout[j] ^= buf[j];185 xout[j] ^= buf[j];
186 }
187 ctx[3][0] += 1;
120 }188 }
121 ctx[3][0] += 1;189 if (i < in.len) {
122 }190 chacha20Core(x[0..], ctx);
123 if (i < in.len) {191 contextFeedback(&x, ctx);
124 chacha20Core(x[0..], ctx);192 hashToBytes(buf[0..], x);
125 contextFeedback(&x, ctx);193
126 hashToBytes(buf[0..], x);194 var xout = out[i..];
127195 const xin = in[i..];
128 var xout = out[i..];196 var j: usize = 0;
129 const xin = in[i..];197 while (j < in.len % 64) : (j += 1) {
130 var j: usize = 0;198 xout[j] = xin[j] ^ buf[j];
131 while (j < in.len % 64) : (j += 1) {199 }
132 xout[j] = xin[j] ^ buf[j];
133 }200 }
134 }201 }
135 }
136202
137 fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {203 fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
138 var c: [4]u32 = undefined;204 var c: [4]u32 = undefined;
139 for (c) |_, i| {205 for (c) |_, i| {
140 c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);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;
141 }221 }
142 const ctx = initContext(keyToWords(key), c);222 };
143 var x: BlockVec = undefined;223}
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};
157224
158// Non-vectorized implementation of the core function225// Non-vectorized implementation of the core function
159const ChaCha20NonVecImpl = struct {226fn ChaChaNonVecImpl(comptime rounds_nb: usize) type {
160 const BlockVec = [16]u32;227 return struct {
161228 const BlockVec = [16]u32;
162 fn initContext(key: [8]u32, d: [4]u32) BlockVec {229
163 const c = "expand 32-byte k";230 fn initContext(key: [8]u32, d: [4]u32) BlockVec {
164 const constant_le = comptime [4]u32{231 const c = "expand 32-byte k";
165 mem.readIntLittle(u32, c[0..4]),232 const constant_le = comptime [4]u32{
166 mem.readIntLittle(u32, c[4..8]),233 mem.readIntLittle(u32, c[0..4]),
167 mem.readIntLittle(u32, c[8..12]),234 mem.readIntLittle(u32, c[4..8]),
168 mem.readIntLittle(u32, c[12..16]),235 mem.readIntLittle(u32, c[8..12]),
169 };236 mem.readIntLittle(u32, c[12..16]),
170 return BlockVec{237 };
171 constant_le[0], constant_le[1], constant_le[2], constant_le[3],238 return BlockVec{
172 key[0], key[1], key[2], key[3],239 constant_le[0], constant_le[1], constant_le[2], constant_le[3],
173 key[4], key[5], key[6], key[7],240 key[0], key[1], key[2], key[3],
174 d[0], d[1], d[2], d[3],241 key[4], key[5], key[6], key[7],
175 };242 d[0], d[1], d[2], d[3],
176 }243 };
177244 }
178 const QuarterRound = struct {
179 a: usize,
180 b: usize,
181 c: usize,
182 d: usize,
183 };
184245
185 fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {246 const QuarterRound = struct {
186 return QuarterRound{247 a: usize,
187 .a = a,248 b: usize,
188 .b = b,249 c: usize,
189 .c = c,250 d: usize,
190 .d = d,
191 };251 };
192 }
193252
194 fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void {253 fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {
195 x.* = input;254 return QuarterRound{
196255 .a = a,
197 const rounds = comptime [_]QuarterRound{256 .b = b,
198 Rp(0, 4, 8, 12),257 .c = c,
199 Rp(1, 5, 9, 13),258 .d = d,
200 Rp(2, 6, 10, 14),259 };
201 Rp(3, 7, 11, 15),260 }
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 };
207261
208 comptime var j: usize = 0;262 fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void {
209 inline while (j < 20) : (j += 2) {263 x.* = input;
210 inline for (rounds) |r| {264
211 x[r.a] +%= x[r.b];265 const rounds = comptime [_]QuarterRound{
212 x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 16));266 Rp(0, 4, 8, 12),
213 x[r.c] +%= x[r.d];267 Rp(1, 5, 9, 13),
214 x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 12));268 Rp(2, 6, 10, 14),
215 x[r.a] +%= x[r.b];269 Rp(3, 7, 11, 15),
216 x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 8));270 Rp(0, 5, 10, 15),
217 x[r.c] +%= x[r.d];271 Rp(1, 6, 11, 12),
218 x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7));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 }
219 }288 }
220 }289 }
221 }
222290
223 fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void {291 fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void {
224 var i: usize = 0;292 var i: usize = 0;
225 while (i < 4) : (i += 1) {293 while (i < 4) : (i += 1) {
226 mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0]);294 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]);295 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]);296 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]);297 mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i * 4 + 3]);
298 }
230 }299 }
231 }
232300
233 fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void {301 fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void {
234 var i: usize = 0;302 var i: usize = 0;
235 while (i < 16) : (i += 1) {303 while (i < 16) : (i += 1) {
236 x[i] +%= ctx[i];304 x[i] +%= ctx[i];
305 }
237 }306 }
238 }
239307
240 fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {308 fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {
241 var ctx = initContext(key, counter);309 var ctx = initContext(key, counter);
242 var x: BlockVec = undefined;310 var x: BlockVec = undefined;
243 var buf: [64]u8 = undefined;311 var buf: [64]u8 = undefined;
244 var i: usize = 0;312 var i: usize = 0;
245 while (i + 64 <= in.len) : (i += 64) {313 while (i + 64 <= in.len) : (i += 64) {
246 chacha20Core(x[0..], ctx);314 chacha20Core(x[0..], ctx);
247 contextFeedback(&x, ctx);315 contextFeedback(&x, ctx);
248 hashToBytes(buf[0..], x);316 hashToBytes(buf[0..], x);
249317
250 var xout = out[i..];318 var xout = out[i..];
251 const xin = in[i..];319 const xin = in[i..];
252 var j: usize = 0;320 var j: usize = 0;
253 while (j < 64) : (j += 1) {321 while (j < 64) : (j += 1) {
254 xout[j] = xin[j];322 xout[j] = xin[j];
255 }323 }
256 j = 0;324 j = 0;
257 while (j < 64) : (j += 1) {325 while (j < 64) : (j += 1) {
258 xout[j] ^= buf[j];326 xout[j] ^= buf[j];
327 }
328 ctx[12] += 1;
259 }329 }
260 ctx[12] += 1;330 if (i < in.len) {
261 }331 chacha20Core(x[0..], ctx);
262 if (i < in.len) {332 contextFeedback(&x, ctx);
263 chacha20Core(x[0..], ctx);333 hashToBytes(buf[0..], x);
264 contextFeedback(&x, ctx);334
265 hashToBytes(buf[0..], x);335 var xout = out[i..];
266336 const xin = in[i..];
267 var xout = out[i..];337 var j: usize = 0;
268 const xin = in[i..];338 while (j < in.len % 64) : (j += 1) {
269 var j: usize = 0;339 xout[j] = xin[j] ^ buf[j];
270 while (j < in.len % 64) : (j += 1) {340 }
271 xout[j] = xin[j] ^ buf[j];
272 }341 }
273 }342 }
274 }
275343
276 fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {344 fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
277 var c: [4]u32 = undefined;345 var c: [4]u32 = undefined;
278 for (c) |_, i| {346 for (c) |_, i| {
279 c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);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;
280 }362 }
281 const ctx = initContext(keyToWords(key), c);363 };
282 var x: BlockVec = undefined;364}
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};
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
299fn keyToWords(key: [32]u8) [8]u32 {370fn keyToWords(key: [32]u8) [8]u32 {
300 var k: [8]u32 = undefined;371 var k: [8]u32 = undefined;
...@@ -305,68 +376,239 @@ fn keyToWords(key: [32]u8) [8]u32 {...@@ -305,68 +376,239 @@ fn keyToWords(key: [32]u8) [8]u32 {
305 return k;376 return k;
306}377}
307378
308/// ChaCha20 avoids the possibility of timing attacks, as there are no branches379fn extend(key: [32]u8, nonce: [24]u8, comptime rounds_nb: usize) struct { key: [32]u8, nonce: [12]u8 } {
309/// on secret key data.380 var subnonce: [12]u8 = undefined;
310///381 mem.set(u8, subnonce[0..4], 0);
311/// in and out should be the same length.382 mem.copy(u8, subnonce[4..], nonce[16..24]);
312/// counter should generally be 0 or 1383 return .{
313///384 .key = ChaChaImpl(rounds_nb).hchacha20(nonce[0..16].*, key),
314/// ChaCha20 is self-reversing. To decrypt just run the cipher with the same385 .nonce = subnonce,
315/// counter, nonce, and key.386 };
316pub const ChaCha20IETF = struct {387}
317 pub fn xor(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce: [12]u8) void {388
318 assert(in.len == out.len);389fn ChaChaIETF(comptime rounds_nb: usize) type {
319 assert((in.len >> 6) + counter <= maxInt(u32));390 return struct {
320391 /// Nonce length in bytes.
321 var c: [4]u32 = undefined;392 pub const nonce_length = 12;
322 c[0] = counter;393 /// Key length in bytes.
323 c[1] = mem.readIntLittle(u32, nonce[0..4]);394 pub const key_length = 32;
324 c[2] = mem.readIntLittle(u32, nonce[4..8]);395
325 c[3] = mem.readIntLittle(u32, nonce[8..12]);396 /// Add the output of the ChaCha20 stream cipher to `in` and stores the result into `out`.
326 ChaCha20Impl.chacha20Xor(out, in, keyToWords(key), c);397 /// WARNING: This function doesn't provide authenticated encryption.
327 }398 /// Using the AEAD or one of the `box` versions is usually preferred.
328};399 pub fn xor(out: []u8, in: []const u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
329400 assert(in.len == out.len);
330/// This is the original ChaCha20 before RFC 7539, which recommends using the401 assert(in.len / 64 <= (1 << 32 - 1) - counter);
331/// orgininal version on applications such as disk or file encryption that might402
332/// exceed the 256 GiB limit of the 96-bit nonce version.403 var d: [4]u32 = undefined;
333pub const ChaCha20With64BitNonce = struct {404 d[0] = counter;
334 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [32]u8, nonce: [8]u8) void {405 d[1] = mem.readIntLittle(u32, nonce[0..4]);
335 assert(in.len == out.len);406 d[2] = mem.readIntLittle(u32, nonce[4..8]);
336 assert(counter +% (in.len >> 6) >= counter);407 d[3] = mem.readIntLittle(u32, nonce[8..12]);
337408 ChaChaImpl(rounds_nb).chacha20Xor(out, in, keyToWords(key), d);
338 var cursor: usize = 0;409 }
339 const k = keyToWords(key);410 };
340 var c: [4]u32 = undefined;411}
341 c[0] = @truncate(u32, counter);412
342 c[1] = @truncate(u32, counter >> 32);413fn ChaChaWith64BitNonce(comptime rounds_nb: usize) type {
343 c[2] = mem.readIntLittle(u32, nonce[0..4]);414 return struct {
344 c[3] = mem.readIntLittle(u32, nonce[4..8]);415 /// Nonce length in bytes.
345416 pub const nonce_length = 8;
346 const block_length = (1 << 6);417 /// Key length in bytes.
347 // The full block size is greater than the address space on a 32bit machine418 pub const key_length = 32;
348 const big_block = if (@sizeOf(usize) > 4) (block_length << 32) else maxInt(usize);419
349420 /// Add the output of the ChaCha20 stream cipher to `in` and stores the result into `out`.
350 // first partial big block421 /// WARNING: This function doesn't provide authenticated encryption.
351 if (((@intCast(u64, maxInt(u32) - @truncate(u32, counter)) + 1) << 6) < in.len) {422 /// Using the AEAD or one of the `box` versions is usually preferred.
352 ChaCha20Impl.chacha20Xor(out[cursor..big_block], in[cursor..big_block], k, c);423 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
353 cursor = big_block - cursor;424 assert(in.len == out.len);
354 c[1] += 1;425 assert(in.len / 64 <= (1 << 64 - 1) - counter);
355 if (comptime @sizeOf(usize) > 4) {426
356 // A big block is giant: 256 GiB, but we can avoid this limitation427 var cursor: usize = 0;
357 var remaining_blocks: u32 = @intCast(u32, (in.len / big_block));428 const k = keyToWords(key);
358 var i: u32 = 0;429 var c: [4]u32 = undefined;
359 while (remaining_blocks > 0) : (remaining_blocks -= 1) {430 c[0] = @truncate(u32, counter);
360 ChaCha20Impl.chacha20Xor(out[cursor .. cursor + big_block], in[cursor .. cursor + big_block], k, c);431 c[1] = @truncate(u32, counter >> 32);
361 c[1] += 1; // upper 32-bit of counter, generic chacha20Xor() doesn't know about this.432 c[2] = mem.readIntLittle(u32, nonce[0..4]);
362 cursor += big_block;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 }
363 }453 }
364 }454 }
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);
365 }578 }
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));
368 }610 }
369};611}
370612
371// https://tools.ietf.org/html/rfc7539#section-2.4.2613// https://tools.ietf.org/html/rfc7539#section-2.4.2
372test "crypto.chacha20 test vector sunscreen" {614test "crypto.chacha20 test vector sunscreen" {
...@@ -387,7 +629,7 @@ test "crypto.chacha20 test vector sunscreen" {...@@ -387,7 +629,7 @@ test "crypto.chacha20 test vector sunscreen" {
387 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42,629 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42,
388 0x87, 0x4d,630 0x87, 0x4d,
389 };631 };
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.";
391 var result: [114]u8 = undefined;633 var result: [114]u8 = undefined;
392 const key = [_]u8{634 const key = [_]u8{
393 0, 1, 2, 3, 4, 5, 6, 7,635 0, 1, 2, 3, 4, 5, 6, 7,
...@@ -401,13 +643,12 @@ test "crypto.chacha20 test vector sunscreen" {...@@ -401,13 +643,12 @@ test "crypto.chacha20 test vector sunscreen" {
401 0, 0, 0, 0,643 0, 0, 0, 0,
402 };644 };
403645
404 ChaCha20IETF.xor(result[0..], input[0..], 1, key, nonce);646 ChaCha20IETF.xor(result[0..], m[0..], 1, key, nonce);
405 testing.expectEqualSlices(u8, &expected_result, &result);647 testing.expectEqualSlices(u8, &expected_result, &result);
406648
407 // Chacha20 is self-reversing.649 var m2: [114]u8 = undefined;
408 var plaintext: [114]u8 = undefined;650 ChaCha20IETF.xor(m2[0..], result[0..], 1, key, nonce);
409 ChaCha20IETF.xor(plaintext[0..], result[0..], 1, key, nonce);651 testing.expect(mem.order(u8, m, &m2) == .eq);
410 testing.expect(mem.order(u8, input, &plaintext) == .eq);
411}652}
412653
413// https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7654// https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7
...@@ -422,7 +663,7 @@ test "crypto.chacha20 test vector 1" {...@@ -422,7 +663,7 @@ test "crypto.chacha20 test vector 1" {
422 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c,663 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c,
423 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86,664 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86,
424 };665 };
425 const input = [_]u8{666 const m = [_]u8{
426 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,667 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
427 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,668 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
428 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,669 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
...@@ -441,7 +682,7 @@ test "crypto.chacha20 test vector 1" {...@@ -441,7 +682,7 @@ test "crypto.chacha20 test vector 1" {
441 };682 };
442 const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 };683 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);
445 testing.expectEqualSlices(u8, &expected_result, &result);686 testing.expectEqualSlices(u8, &expected_result, &result);
446}687}
447688
...@@ -456,7 +697,7 @@ test "crypto.chacha20 test vector 2" {...@@ -456,7 +697,7 @@ test "crypto.chacha20 test vector 2" {
456 0x53, 0xd7, 0x92, 0xb1, 0xc4, 0x3f, 0xea, 0x81,697 0x53, 0xd7, 0x92, 0xb1, 0xc4, 0x3f, 0xea, 0x81,
457 0x7e, 0x9a, 0xd2, 0x75, 0xae, 0x54, 0x69, 0x63,698 0x7e, 0x9a, 0xd2, 0x75, 0xae, 0x54, 0x69, 0x63,
458 };699 };
459 const input = [_]u8{700 const m = [_]u8{
460 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,701 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
461 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,702 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
462 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,703 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
...@@ -475,7 +716,7 @@ test "crypto.chacha20 test vector 2" {...@@ -475,7 +716,7 @@ test "crypto.chacha20 test vector 2" {
475 };716 };
476 const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 };717 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);
479 testing.expectEqualSlices(u8, &expected_result, &result);720 testing.expectEqualSlices(u8, &expected_result, &result);
480}721}
481722
...@@ -490,7 +731,7 @@ test "crypto.chacha20 test vector 3" {...@@ -490,7 +731,7 @@ test "crypto.chacha20 test vector 3" {
490 0x52, 0x77, 0x06, 0x2e, 0xb7, 0xa0, 0x43, 0x3e,731 0x52, 0x77, 0x06, 0x2e, 0xb7, 0xa0, 0x43, 0x3e,
491 0x44, 0x5f, 0x41, 0xe3,732 0x44, 0x5f, 0x41, 0xe3,
492 };733 };
493 const input = [_]u8{734 const m = [_]u8{
494 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,735 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
495 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,736 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
496 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,737 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
...@@ -509,7 +750,7 @@ test "crypto.chacha20 test vector 3" {...@@ -509,7 +750,7 @@ test "crypto.chacha20 test vector 3" {
509 };750 };
510 const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 1 };751 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);
513 testing.expectEqualSlices(u8, &expected_result, &result);754 testing.expectEqualSlices(u8, &expected_result, &result);
514}755}
515756
...@@ -524,7 +765,7 @@ test "crypto.chacha20 test vector 4" {...@@ -524,7 +765,7 @@ test "crypto.chacha20 test vector 4" {
524 0x5d, 0xdc, 0x49, 0x7a, 0x0b, 0x46, 0x6e, 0x7d,765 0x5d, 0xdc, 0x49, 0x7a, 0x0b, 0x46, 0x6e, 0x7d,
525 0x6b, 0xbd, 0xb0, 0x04, 0x1b, 0x2f, 0x58, 0x6b,766 0x6b, 0xbd, 0xb0, 0x04, 0x1b, 0x2f, 0x58, 0x6b,
526 };767 };
527 const input = [_]u8{768 const m = [_]u8{
528 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,769 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
529 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,770 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
530 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,771 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
...@@ -543,7 +784,7 @@ test "crypto.chacha20 test vector 4" {...@@ -543,7 +784,7 @@ test "crypto.chacha20 test vector 4" {
543 };784 };
544 const nonce = [_]u8{ 1, 0, 0, 0, 0, 0, 0, 0 };785 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);
547 testing.expectEqualSlices(u8, &expected_result, &result);788 testing.expectEqualSlices(u8, &expected_result, &result);
548}789}
549790
...@@ -585,7 +826,7 @@ test "crypto.chacha20 test vector 5" {...@@ -585,7 +826,7 @@ test "crypto.chacha20 test vector 5" {
585 0x87, 0x46, 0xd4, 0x52, 0x4d, 0x38, 0x40, 0x7a,826 0x87, 0x46, 0xd4, 0x52, 0x4d, 0x38, 0x40, 0x7a,
586 0x6d, 0xeb, 0x3a, 0xb7, 0x8f, 0xab, 0x78, 0xc9,827 0x6d, 0xeb, 0x3a, 0xb7, 0x8f, 0xab, 0x78, 0xc9,
587 };828 };
588 const input = [_]u8{829 const m = [_]u8{
589 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,830 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
590 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,831 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
591 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,832 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" {...@@ -615,147 +856,14 @@ test "crypto.chacha20 test vector 5" {
615 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,856 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
616 };857 };
617858
618 ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce);859 ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce);
619 testing.expectEqualSlices(u8, &expected_result, &result);860 testing.expectEqualSlices(u8, &expected_result, &result);
620}861}
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
755test "seal" {863test "seal" {
756 {864 {
757 const plaintext = "";865 const m = "";
758 const data = "";866 const ad = "";
759 const key = [_]u8{867 const key = [_]u8{
760 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,868 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
761 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,869 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
...@@ -764,11 +872,11 @@ test "seal" {...@@ -764,11 +872,11 @@ test "seal" {
764 const exp_out = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 };872 const exp_out = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 };
765873
766 var out: [exp_out.len]u8 = undefined;874 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);
768 testing.expectEqualSlices(u8, exp_out[0..], out[0..]);876 testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
769 }877 }
770 {878 {
771 const plaintext = [_]u8{879 const m = [_]u8{
772 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c,880 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c,
773 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73,881 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73,
774 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63,882 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63,
...@@ -778,7 +886,7 @@ test "seal" {...@@ -778,7 +886,7 @@ test "seal" {
778 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69,886 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69,
779 0x74, 0x2e,887 0x74, 0x2e,
780 };888 };
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 };
782 const key = [_]u8{890 const key = [_]u8{
783 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,891 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
784 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,892 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
...@@ -797,15 +905,15 @@ test "seal" {...@@ -797,15 +905,15 @@ test "seal" {
797 };905 };
798906
799 var out: [exp_out.len]u8 = undefined;907 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);
801 testing.expectEqualSlices(u8, exp_out[0..], out[0..]);909 testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
802 }910 }
803}911}
804912
805test "open" {913test "open" {
806 {914 {
807 const ciphertext = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 };915 const c = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 };
808 const data = "";916 const ad = "";
809 const key = [_]u8{917 const key = [_]u8{
810 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,918 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
811 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,919 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
...@@ -814,11 +922,11 @@ test "open" {...@@ -814,11 +922,11 @@ test "open" {
814 const exp_out = "";922 const exp_out = "";
815923
816 var out: [exp_out.len]u8 = undefined;924 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);
818 testing.expectEqualSlices(u8, exp_out[0..], out[0..]);926 testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
819 }927 }
820 {928 {
821 const ciphertext = [_]u8{929 const c = [_]u8{
822 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2,930 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2,
823 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x8, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6,931 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x8, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6,
824 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b,932 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b,
...@@ -829,7 +937,7 @@ test "open" {...@@ -829,7 +937,7 @@ test "open" {
829 0x61, 0x16, 0x1a, 0xe1, 0xb, 0x59, 0x4f, 0x9, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60,937 0x61, 0x16, 0x1a, 0xe1, 0xb, 0x59, 0x4f, 0x9, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60,
830 0x6, 0x91,938 0x6, 0x91,
831 };939 };
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 };
833 const key = [_]u8{941 const key = [_]u8{
834 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,942 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
835 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,943 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
...@@ -847,126 +955,45 @@ test "open" {...@@ -847,126 +955,45 @@ test "open" {
847 };955 };
848956
849 var out: [exp_out.len]u8 = undefined;957 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);
851 testing.expectEqualSlices(u8, exp_out[0..], out[0..]);959 testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
852960
853 // corrupting the ciphertext, data, key, or nonce should cause a failure961 // corrupting the ciphertext, data, key, or nonce should cause a failure
854 var bad_ciphertext = ciphertext;962 var bad_c = c;
855 bad_ciphertext[0] ^= 1;963 bad_c[0] ^= 1;
856 testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], bad_ciphertext[0..], data[0..], key, nonce));964 testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], bad_c[0..out.len], bad_c[out.len..].*, ad[0..], nonce, key));
857 var bad_data = data;965 var bad_ad = ad;
858 bad_data[0] ^= 1;966 bad_ad[0] ^= 1;
859 testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[0..], bad_data[0..], key, nonce));967 testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], c[0..out.len], c[out.len..].*, bad_ad[0..], nonce, key));
860 var bad_key = key;968 var bad_key = key;
861 bad_key[0] ^= 1;969 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));
863 var bad_nonce = nonce;971 var bad_nonce = nonce;
864 bad_nonce[0] ^= 1;972 bad_nonce[0] ^= 1;
865 testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[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));
866
867 // a short ciphertext should result in a different error
868 testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], "", data[0..], key, bad_nonce));
869 }974 }
870}975}
871976
872test "crypto.xchacha20" {977test "crypto.xchacha20" {
873 const key = [_]u8{69} ** 32;978 const key = [_]u8{69} ** 32;
874 const nonce = [_]u8{42} ** 24;979 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.";
876 {981 {
877 var ciphertext: [input.len]u8 = undefined;982 var c: [m.len]u8 = undefined;
878 XChaCha20IETF.xor(ciphertext[0..], input[0..], 0, key, nonce);983 XChaCha20IETF.xor(c[0..], m[0..], 0, key, nonce);
879 var buf: [2 * ciphertext.len]u8 = undefined;984 var buf: [2 * c.len]u8 = undefined;
880 testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&ciphertext)}), "E0A1BCF939654AFDBDC1746EC49832647C19D891F0D1A81FC0C1703B4514BDEA584B512F6908C2C5E9DD18D5CBC1805DE5803FE3B9CA5F193FB8359E91FAB0C3BB40309A292EB1CF49685C65C4A3ADF4F11DB0CD2B6B67FBC174BC2E860E8F769FD3565BBFAD1C845E05A0FED9BE167C240D");985 testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&c)}), "E0A1BCF939654AFDBDC1746EC49832647C19D891F0D1A81FC0C1703B4514BDEA584B512F6908C2C5E9DD18D5CBC1805DE5803FE3B9CA5F193FB8359E91FAB0C3BB40309A292EB1CF49685C65C4A3ADF4F11DB0CD2B6B67FBC174BC2E860E8F769FD3565BBFAD1C845E05A0FED9BE167C240D");
881 }986 }
882 {987 {
883 const data = "Additional data";988 const ad = "Additional data";
884 var ciphertext: [input.len + xchacha20poly1305_tag_length]u8 = undefined;989 var c: [m.len + XChaCha20Poly1305.tag_length]u8 = undefined;
885 xchacha20poly1305Seal(ciphertext[0..], input, data, key, nonce);990 XChaCha20Poly1305.encrypt(c[0..m.len], c[m.len..], m, ad, nonce, key);
886 var out: [input.len]u8 = undefined;991 var out: [m.len]u8 = undefined;
887 try xchacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce);992 try XChaCha20Poly1305.decrypt(out[0..], c[0..m.len], c[m.len..].*, ad, nonce, key);
888 var buf: [2 * ciphertext.len]u8 = undefined;993 var buf: [2 * c.len]u8 = undefined;
889 testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&ciphertext)}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234");994 testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&c)}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234");
890 testing.expectEqualSlices(u8, out[0..], input);995 testing.expectEqualSlices(u8, out[0..], m);
891 ciphertext[0] += 1;996 c[0] += 1;
892 testing.expectError(error.AuthenticationFailed, xchacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce));997 testing.expectError(error.AuthenticationFailed, XChaCha20Poly1305.decrypt(out[0..], c[0..m.len], c[m.len..].*, ad, nonce, key));
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));
971 }998 }
972}999}