master
1const std = @import("std");
2const builtin = @import("builtin");
3const crypto = std.crypto;
4const debug = std.debug;
5const math = std.math;
6const mem = std.mem;
7
8const Poly1305 = crypto.onetimeauth.Poly1305;
9const Blake2b = crypto.hash.blake2.Blake2b;
10const X25519 = crypto.dh.X25519;
11
12const AuthenticationError = crypto.errors.AuthenticationError;
13const IdentityElementError = crypto.errors.IdentityElementError;
14const WeakPublicKeyError = crypto.errors.WeakPublicKeyError;
15
16/// The Salsa cipher with 20 rounds.
17pub const Salsa20 = Salsa(20);
18
19/// The XSalsa cipher with 20 rounds.
20pub const XSalsa20 = XSalsa(20);
21
22fn SalsaVecImpl(comptime rounds: comptime_int) type {
23 return struct {
24 const Lane = @Vector(4, u32);
25 const Half = @Vector(2, u32);
26 const BlockVec = [4]Lane;
27
28 fn initContext(key: [8]u32, d: [4]u32) BlockVec {
29 const c = "expand 32-byte k";
30 const constant_le = comptime [4]u32{
31 mem.readInt(u32, c[0..4], .little),
32 mem.readInt(u32, c[4..8], .little),
33 mem.readInt(u32, c[8..12], .little),
34 mem.readInt(u32, c[12..16], .little),
35 };
36 return BlockVec{
37 Lane{ key[0], key[1], key[2], key[3] },
38 Lane{ key[4], key[5], key[6], key[7] },
39 Lane{ constant_le[0], constant_le[1], constant_le[2], constant_le[3] },
40 Lane{ d[0], d[1], d[2], d[3] },
41 };
42 }
43
44 fn salsaCore(x: *BlockVec, input: BlockVec, comptime feedback: bool) void {
45 const n1n2n3n0 = Lane{ input[3][1], input[3][2], input[3][3], input[3][0] };
46 const n1n2 = Half{ n1n2n3n0[0], n1n2n3n0[1] };
47 const n3n0 = Half{ n1n2n3n0[2], n1n2n3n0[3] };
48 const k0k1 = Half{ input[0][0], input[0][1] };
49 const k2k3 = Half{ input[0][2], input[0][3] };
50 const k4k5 = Half{ input[1][0], input[1][1] };
51 const k6k7 = Half{ input[1][2], input[1][3] };
52 const n0k0 = Half{ n3n0[1], k0k1[0] };
53 const k0n0 = Half{ n0k0[1], n0k0[0] };
54 const k4k5k0n0 = Lane{ k4k5[0], k4k5[1], k0n0[0], k0n0[1] };
55 const k1k6 = Half{ k0k1[1], k6k7[0] };
56 const k6k1 = Half{ k1k6[1], k1k6[0] };
57 const n1n2k6k1 = Lane{ n1n2[0], n1n2[1], k6k1[0], k6k1[1] };
58 const k7n3 = Half{ k6k7[1], n3n0[0] };
59 const n3k7 = Half{ k7n3[1], k7n3[0] };
60 const k2k3n3k7 = Lane{ k2k3[0], k2k3[1], n3k7[0], n3k7[1] };
61
62 var diag0 = input[2];
63 var diag1 = @shuffle(u32, k4k5k0n0, undefined, [_]i32{ 1, 2, 3, 0 });
64 var diag2 = @shuffle(u32, n1n2k6k1, undefined, [_]i32{ 1, 2, 3, 0 });
65 var diag3 = @shuffle(u32, k2k3n3k7, undefined, [_]i32{ 1, 2, 3, 0 });
66
67 const start0 = diag0;
68 const start1 = diag1;
69 const start2 = diag2;
70 const start3 = diag3;
71
72 var i: usize = 0;
73 while (i < rounds) : (i += 2) {
74 diag3 ^= math.rotl(Lane, diag1 +% diag0, 7);
75 diag2 ^= math.rotl(Lane, diag0 +% diag3, 9);
76 diag1 ^= math.rotl(Lane, diag3 +% diag2, 13);
77 diag0 ^= math.rotl(Lane, diag2 +% diag1, 18);
78
79 diag3 = @shuffle(u32, diag3, undefined, [_]i32{ 3, 0, 1, 2 });
80 diag2 = @shuffle(u32, diag2, undefined, [_]i32{ 2, 3, 0, 1 });
81 diag1 = @shuffle(u32, diag1, undefined, [_]i32{ 1, 2, 3, 0 });
82
83 diag1 ^= math.rotl(Lane, diag3 +% diag0, 7);
84 diag2 ^= math.rotl(Lane, diag0 +% diag1, 9);
85 diag3 ^= math.rotl(Lane, diag1 +% diag2, 13);
86 diag0 ^= math.rotl(Lane, diag2 +% diag3, 18);
87
88 diag1 = @shuffle(u32, diag1, undefined, [_]i32{ 3, 0, 1, 2 });
89 diag2 = @shuffle(u32, diag2, undefined, [_]i32{ 2, 3, 0, 1 });
90 diag3 = @shuffle(u32, diag3, undefined, [_]i32{ 1, 2, 3, 0 });
91 }
92
93 if (feedback) {
94 diag0 +%= start0;
95 diag1 +%= start1;
96 diag2 +%= start2;
97 diag3 +%= start3;
98 }
99
100 const x0x1x10x11 = Lane{ diag0[0], diag1[1], diag0[2], diag1[3] };
101 const x12x13x6x7 = Lane{ diag1[0], diag2[1], diag1[2], diag2[3] };
102 const x8x9x2x3 = Lane{ diag2[0], diag3[1], diag2[2], diag3[3] };
103 const x4x5x14x15 = Lane{ diag3[0], diag0[1], diag3[2], diag0[3] };
104
105 x[0] = Lane{ x0x1x10x11[0], x0x1x10x11[1], x8x9x2x3[2], x8x9x2x3[3] };
106 x[1] = Lane{ x4x5x14x15[0], x4x5x14x15[1], x12x13x6x7[2], x12x13x6x7[3] };
107 x[2] = Lane{ x8x9x2x3[0], x8x9x2x3[1], x0x1x10x11[2], x0x1x10x11[3] };
108 x[3] = Lane{ x12x13x6x7[0], x12x13x6x7[1], x4x5x14x15[2], x4x5x14x15[3] };
109 }
110
111 fn hashToBytes(out: *[64]u8, x: BlockVec) void {
112 var i: usize = 0;
113 while (i < 4) : (i += 1) {
114 mem.writeInt(u32, out[16 * i + 0 ..][0..4], x[i][0], .little);
115 mem.writeInt(u32, out[16 * i + 4 ..][0..4], x[i][1], .little);
116 mem.writeInt(u32, out[16 * i + 8 ..][0..4], x[i][2], .little);
117 mem.writeInt(u32, out[16 * i + 12 ..][0..4], x[i][3], .little);
118 }
119 }
120
121 fn salsaXor(out: []u8, in: []const u8, key: [8]u32, d: [4]u32) void {
122 var ctx = initContext(key, d);
123 var x: BlockVec = undefined;
124 var buf: [64]u8 = undefined;
125 var i: usize = 0;
126 while (i + 64 <= in.len) : (i += 64) {
127 salsaCore(x[0..], ctx, true);
128 hashToBytes(buf[0..], x);
129 var xout = out[i..];
130 const xin = in[i..];
131 var j: usize = 0;
132 while (j < 64) : (j += 1) {
133 xout[j] = xin[j];
134 }
135 j = 0;
136 while (j < 64) : (j += 1) {
137 xout[j] ^= buf[j];
138 }
139 ctx[3][2] +%= 1;
140 if (ctx[3][2] == 0) {
141 ctx[3][3] += 1;
142 }
143 }
144 if (i < in.len) {
145 salsaCore(x[0..], ctx, true);
146 hashToBytes(buf[0..], x);
147
148 var xout = out[i..];
149 const xin = in[i..];
150 var j: usize = 0;
151 while (j < in.len % 64) : (j += 1) {
152 xout[j] = xin[j] ^ buf[j];
153 }
154 }
155 }
156
157 fn hsalsa(input: [16]u8, key: [32]u8) [32]u8 {
158 var c: [4]u32 = undefined;
159 for (c, 0..) |_, i| {
160 c[i] = mem.readInt(u32, input[4 * i ..][0..4], .little);
161 }
162 const ctx = initContext(keyToWords(key), c);
163 var x: BlockVec = undefined;
164 salsaCore(x[0..], ctx, false);
165 var out: [32]u8 = undefined;
166 mem.writeInt(u32, out[0..4], x[0][0], .little);
167 mem.writeInt(u32, out[4..8], x[1][1], .little);
168 mem.writeInt(u32, out[8..12], x[2][2], .little);
169 mem.writeInt(u32, out[12..16], x[3][3], .little);
170 mem.writeInt(u32, out[16..20], x[1][2], .little);
171 mem.writeInt(u32, out[20..24], x[1][3], .little);
172 mem.writeInt(u32, out[24..28], x[2][0], .little);
173 mem.writeInt(u32, out[28..32], x[2][1], .little);
174 return out;
175 }
176 };
177}
178
179fn SalsaNonVecImpl(comptime rounds: comptime_int) type {
180 return struct {
181 const BlockVec = [16]u32;
182
183 fn initContext(key: [8]u32, d: [4]u32) BlockVec {
184 const c = "expand 32-byte k";
185 const constant_le = comptime [4]u32{
186 mem.readInt(u32, c[0..4], .little),
187 mem.readInt(u32, c[4..8], .little),
188 mem.readInt(u32, c[8..12], .little),
189 mem.readInt(u32, c[12..16], .little),
190 };
191 return BlockVec{
192 constant_le[0], key[0], key[1], key[2],
193 key[3], constant_le[1], d[0], d[1],
194 d[2], d[3], constant_le[2], key[4],
195 key[5], key[6], key[7], constant_le[3],
196 };
197 }
198
199 const QuarterRound = struct {
200 a: usize,
201 b: usize,
202 c: usize,
203 d: u6,
204 };
205
206 fn Rp(a: usize, b: usize, c: usize, d: u6) QuarterRound {
207 return QuarterRound{
208 .a = a,
209 .b = b,
210 .c = c,
211 .d = d,
212 };
213 }
214
215 fn salsaCore(x: *BlockVec, input: BlockVec, comptime feedback: bool) void {
216 const arx_steps = comptime [_]QuarterRound{
217 Rp(4, 0, 12, 7), Rp(8, 4, 0, 9), Rp(12, 8, 4, 13), Rp(0, 12, 8, 18),
218 Rp(9, 5, 1, 7), Rp(13, 9, 5, 9), Rp(1, 13, 9, 13), Rp(5, 1, 13, 18),
219 Rp(14, 10, 6, 7), Rp(2, 14, 10, 9), Rp(6, 2, 14, 13), Rp(10, 6, 2, 18),
220 Rp(3, 15, 11, 7), Rp(7, 3, 15, 9), Rp(11, 7, 3, 13), Rp(15, 11, 7, 18),
221 Rp(1, 0, 3, 7), Rp(2, 1, 0, 9), Rp(3, 2, 1, 13), Rp(0, 3, 2, 18),
222 Rp(6, 5, 4, 7), Rp(7, 6, 5, 9), Rp(4, 7, 6, 13), Rp(5, 4, 7, 18),
223 Rp(11, 10, 9, 7), Rp(8, 11, 10, 9), Rp(9, 8, 11, 13), Rp(10, 9, 8, 18),
224 Rp(12, 15, 14, 7), Rp(13, 12, 15, 9), Rp(14, 13, 12, 13), Rp(15, 14, 13, 18),
225 };
226 x.* = input;
227 var j: usize = 0;
228 while (j < rounds) : (j += 2) {
229 inline for (arx_steps) |r| {
230 x[r.a] ^= math.rotl(u32, x[r.b] +% x[r.c], r.d);
231 }
232 }
233 if (feedback) {
234 j = 0;
235 while (j < 16) : (j += 1) {
236 x[j] +%= input[j];
237 }
238 }
239 }
240
241 fn hashToBytes(out: *[64]u8, x: BlockVec) void {
242 for (x, 0..) |w, i| {
243 mem.writeInt(u32, out[i * 4 ..][0..4], w, .little);
244 }
245 }
246
247 fn salsaXor(out: []u8, in: []const u8, key: [8]u32, d: [4]u32) void {
248 var ctx = initContext(key, d);
249 var x: BlockVec = undefined;
250 var buf: [64]u8 = undefined;
251 var i: usize = 0;
252 while (i + 64 <= in.len) : (i += 64) {
253 salsaCore(x[0..], ctx, true);
254 hashToBytes(buf[0..], x);
255 var xout = out[i..];
256 const xin = in[i..];
257 var j: usize = 0;
258 while (j < 64) : (j += 1) {
259 xout[j] = xin[j];
260 }
261 j = 0;
262 while (j < 64) : (j += 1) {
263 xout[j] ^= buf[j];
264 }
265 const ov = @addWithOverflow(ctx[8], 1);
266 ctx[8] = ov[0];
267 ctx[9] += ov[1];
268 }
269 if (i < in.len) {
270 salsaCore(x[0..], ctx, true);
271 hashToBytes(buf[0..], x);
272
273 var xout = out[i..];
274 const xin = in[i..];
275 var j: usize = 0;
276 while (j < in.len % 64) : (j += 1) {
277 xout[j] = xin[j] ^ buf[j];
278 }
279 }
280 }
281
282 fn hsalsa(input: [16]u8, key: [32]u8) [32]u8 {
283 var c: [4]u32 = undefined;
284 for (c, 0..) |_, i| {
285 c[i] = mem.readInt(u32, input[4 * i ..][0..4], .little);
286 }
287 const ctx = initContext(keyToWords(key), c);
288 var x: BlockVec = undefined;
289 salsaCore(x[0..], ctx, false);
290 var out: [32]u8 = undefined;
291 mem.writeInt(u32, out[0..4], x[0], .little);
292 mem.writeInt(u32, out[4..8], x[5], .little);
293 mem.writeInt(u32, out[8..12], x[10], .little);
294 mem.writeInt(u32, out[12..16], x[15], .little);
295 mem.writeInt(u32, out[16..20], x[6], .little);
296 mem.writeInt(u32, out[20..24], x[7], .little);
297 mem.writeInt(u32, out[24..28], x[8], .little);
298 mem.writeInt(u32, out[28..32], x[9], .little);
299 return out;
300 }
301 };
302}
303
304const SalsaImpl = if (builtin.cpu.arch == .x86_64)
305 SalsaVecImpl
306else
307 SalsaNonVecImpl;
308
309fn keyToWords(key: [32]u8) [8]u32 {
310 var k: [8]u32 = undefined;
311 var i: usize = 0;
312 while (i < 8) : (i += 1) {
313 k[i] = mem.readInt(u32, key[i * 4 ..][0..4], .little);
314 }
315 return k;
316}
317
318fn extend(comptime rounds: comptime_int, key: [32]u8, nonce: [24]u8) struct { key: [32]u8, nonce: [8]u8 } {
319 return .{
320 .key = SalsaImpl(rounds).hsalsa(nonce[0..16].*, key),
321 .nonce = nonce[16..24].*,
322 };
323}
324
325/// The Salsa stream cipher.
326pub fn Salsa(comptime rounds: comptime_int) type {
327 return struct {
328 /// Nonce length in bytes.
329 pub const nonce_length = 8;
330 /// Key length in bytes.
331 pub const key_length = 32;
332
333 /// Add the output of the Salsa stream cipher to `in` and stores the result into `out`.
334 /// WARNING: This function doesn't provide authenticated encryption.
335 /// Using the AEAD or one of the `box` versions is usually preferred.
336 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
337 debug.assert(in.len == out.len);
338
339 var d: [4]u32 = undefined;
340 d[0] = mem.readInt(u32, nonce[0..4], .little);
341 d[1] = mem.readInt(u32, nonce[4..8], .little);
342 d[2] = @as(u32, @truncate(counter));
343 d[3] = @as(u32, @truncate(counter >> 32));
344 SalsaImpl(rounds).salsaXor(out, in, keyToWords(key), d);
345 }
346 };
347}
348
349/// The XSalsa stream cipher.
350pub fn XSalsa(comptime rounds: comptime_int) type {
351 return struct {
352 /// Nonce length in bytes.
353 pub const nonce_length = 24;
354 /// Key length in bytes.
355 pub const key_length = 32;
356
357 /// Add the output of the XSalsa stream cipher to `in` and stores the result into `out`.
358 /// WARNING: This function doesn't provide authenticated encryption.
359 /// Using the AEAD or one of the `box` versions is usually preferred.
360 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
361 const extended = extend(rounds, key, nonce);
362 Salsa(rounds).xor(out, in, counter, extended.key, extended.nonce);
363 }
364 };
365}
366
367/// The XSalsa stream cipher, combined with the Poly1305 MAC
368pub const XSalsa20Poly1305 = struct {
369 /// Authentication tag length in bytes.
370 pub const tag_length = Poly1305.mac_length;
371 /// Nonce length in bytes.
372 pub const nonce_length = XSalsa20.nonce_length;
373 /// Key length in bytes.
374 pub const key_length = XSalsa20.key_length;
375
376 const rounds = 20;
377
378 /// c: ciphertext: output buffer should be of size m.len
379 /// tag: authentication tag: output MAC
380 /// m: message
381 /// ad: Associated Data
382 /// npub: public nonce
383 /// k: private key
384 pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
385 debug.assert(c.len == m.len);
386 const extended = extend(rounds, k, npub);
387 var block0 = [_]u8{0} ** 64;
388 const mlen0 = @min(32, m.len);
389 @memcpy(block0[32..][0..mlen0], m[0..mlen0]);
390 Salsa20.xor(block0[0..], block0[0..], 0, extended.key, extended.nonce);
391 @memcpy(c[0..mlen0], block0[32..][0..mlen0]);
392 Salsa20.xor(c[mlen0..], m[mlen0..], 1, extended.key, extended.nonce);
393 var mac = Poly1305.init(block0[0..32]);
394 mac.update(ad);
395 mac.update(c);
396 mac.final(tag);
397 }
398
399 /// `m`: Message
400 /// `c`: Ciphertext
401 /// `tag`: Authentication tag
402 /// `ad`: Associated data
403 /// `npub`: Public nonce
404 /// `k`: Private key
405 /// Asserts `c.len == m.len`.
406 ///
407 /// Contents of `m` are undefined if an error is returned.
408 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void {
409 debug.assert(c.len == m.len);
410 const extended = extend(rounds, k, npub);
411 var block0 = [_]u8{0} ** 64;
412 const mlen0 = @min(32, c.len);
413 @memcpy(block0[32..][0..mlen0], c[0..mlen0]);
414 Salsa20.xor(block0[0..], block0[0..], 0, extended.key, extended.nonce);
415 var mac = Poly1305.init(block0[0..32]);
416 mac.update(ad);
417 mac.update(c);
418 var computed_tag: [tag_length]u8 = undefined;
419 mac.final(&computed_tag);
420
421 const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
422 if (!verify) {
423 crypto.secureZero(u8, &computed_tag);
424 @memset(m, undefined);
425 return error.AuthenticationFailed;
426 }
427 @memcpy(m[0..mlen0], block0[32..][0..mlen0]);
428 Salsa20.xor(m[mlen0..], c[mlen0..], 1, extended.key, extended.nonce);
429 }
430};
431
432/// NaCl-compatible secretbox API.
433///
434/// A secretbox contains both an encrypted message and an authentication tag to verify that it hasn't been tampered with.
435/// A secret key shared by all the recipients must be already known in order to use this API.
436///
437/// Nonces are 192-bit large and can safely be chosen with a random number generator.
438pub const SecretBox = struct {
439 /// Key length in bytes.
440 pub const key_length = XSalsa20Poly1305.key_length;
441 /// Nonce length in bytes.
442 pub const nonce_length = XSalsa20Poly1305.nonce_length;
443 /// Authentication tag length in bytes.
444 pub const tag_length = XSalsa20Poly1305.tag_length;
445
446 /// Encrypt and authenticate `m` using a nonce `npub` and a key `k`.
447 /// `c` must be exactly `tag_length` longer than `m`, as it will store both the ciphertext and the authentication tag.
448 pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
449 debug.assert(c.len == tag_length + m.len);
450 XSalsa20Poly1305.encrypt(c[tag_length..], c[0..tag_length], m, "", npub, k);
451 }
452
453 /// Verify and decrypt `c` using a nonce `npub` and a key `k`.
454 /// `m` must be exactly `tag_length` smaller than `c`, as `c` includes an authentication tag in addition to the encrypted message.
455 pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void {
456 if (c.len < tag_length) {
457 return error.AuthenticationFailed;
458 }
459 debug.assert(m.len == c.len - tag_length);
460 return XSalsa20Poly1305.decrypt(m, c[tag_length..], c[0..tag_length].*, "", npub, k);
461 }
462};
463
464/// NaCl-compatible box API.
465///
466/// A secretbox contains both an encrypted message and an authentication tag to verify that it hasn't been tampered with.
467/// This construction uses public-key cryptography. A shared secret doesn't have to be known in advance by both parties.
468/// Instead, a message is encrypted using a sender's secret key and a recipient's public key,
469/// and is decrypted using the recipient's secret key and the sender's public key.
470///
471/// Nonces are 192-bit large and can safely be chosen with a random number generator.
472pub const Box = struct {
473 /// Public key length in bytes.
474 pub const public_length = X25519.public_length;
475 /// Secret key length in bytes.
476 pub const secret_length = X25519.secret_length;
477 /// Shared key length in bytes.
478 pub const shared_length = XSalsa20Poly1305.key_length;
479 /// Seed (for key pair creation) length in bytes.
480 pub const seed_length = X25519.seed_length;
481 /// Nonce length in bytes.
482 pub const nonce_length = XSalsa20Poly1305.nonce_length;
483 /// Authentication tag length in bytes.
484 pub const tag_length = XSalsa20Poly1305.tag_length;
485
486 /// A key pair.
487 pub const KeyPair = X25519.KeyPair;
488
489 /// Compute a secret suitable for `secretbox` given a recipient's public key and a sender's secret key.
490 pub fn createSharedSecret(public_key: [public_length]u8, secret_key: [secret_length]u8) (IdentityElementError || WeakPublicKeyError)![shared_length]u8 {
491 const p = try X25519.scalarmult(secret_key, public_key);
492 const zero = [_]u8{0} ** 16;
493 return SalsaImpl(20).hsalsa(zero, p);
494 }
495
496 /// Encrypt and authenticate a message using a recipient's public key `public_key` and a sender's `secret_key`.
497 pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) (IdentityElementError || WeakPublicKeyError)!void {
498 const shared_key = try createSharedSecret(public_key, secret_key);
499 return SecretBox.seal(c, m, npub, shared_key);
500 }
501
502 /// Verify and decrypt a message using a recipient's secret key `public_key` and a sender's `public_key`.
503 pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) (IdentityElementError || WeakPublicKeyError || AuthenticationError)!void {
504 const shared_key = try createSharedSecret(public_key, secret_key);
505 return SecretBox.open(m, c, npub, shared_key);
506 }
507};
508
509/// libsodium-compatible sealed boxes
510///
511/// Sealed boxes are designed to anonymously send messages to a recipient given their public key.
512/// Only the recipient can decrypt these messages, using their private key.
513/// While the recipient can verify the integrity of the message, it cannot verify the identity of the sender.
514///
515/// A message is encrypted using an ephemeral key pair, whose secret part is destroyed right after the encryption process.
516pub const SealedBox = struct {
517 pub const public_length = Box.public_length;
518 pub const secret_length = Box.secret_length;
519 pub const seed_length = Box.seed_length;
520 pub const seal_length = Box.public_length + Box.tag_length;
521
522 /// A key pair.
523 pub const KeyPair = Box.KeyPair;
524
525 fn createNonce(pk1: [public_length]u8, pk2: [public_length]u8) [Box.nonce_length]u8 {
526 var hasher = Blake2b(Box.nonce_length * 8).init(.{});
527 hasher.update(&pk1);
528 hasher.update(&pk2);
529 var nonce: [Box.nonce_length]u8 = undefined;
530 hasher.final(&nonce);
531 return nonce;
532 }
533
534 /// Encrypt a message `m` for a recipient whose public key is `public_key`.
535 /// `c` must be `seal_length` bytes larger than `m`, so that the required metadata can be added.
536 pub fn seal(c: []u8, m: []const u8, public_key: [public_length]u8) (WeakPublicKeyError || IdentityElementError)!void {
537 debug.assert(c.len == m.len + seal_length);
538 var ekp = KeyPair.generate();
539 const nonce = createNonce(ekp.public_key, public_key);
540 c[0..public_length].* = ekp.public_key;
541 try Box.seal(c[Box.public_length..], m, nonce, public_key, ekp.secret_key);
542 crypto.secureZero(u8, ekp.secret_key[0..]);
543 }
544
545 /// Decrypt a message using a key pair.
546 /// `m` must be exactly `seal_length` bytes smaller than `c`, as `c` also includes metadata.
547 pub fn open(m: []u8, c: []const u8, keypair: KeyPair) (IdentityElementError || WeakPublicKeyError || AuthenticationError)!void {
548 if (c.len < seal_length) {
549 return error.AuthenticationFailed;
550 }
551 const epk = c[0..public_length];
552 const nonce = createNonce(epk.*, keypair.public_key);
553 return Box.open(m, c[public_length..], nonce, epk.*, keypair.secret_key);
554 }
555};
556
557const htest = @import("test.zig");
558
559test "(x)salsa20" {
560 if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24299
561
562 const key = [_]u8{0x69} ** 32;
563 const nonce = [_]u8{0x42} ** 8;
564 const msg = [_]u8{0} ** 20;
565 var c: [msg.len]u8 = undefined;
566
567 Salsa20.xor(&c, msg[0..], 0, key, nonce);
568 try htest.assertEqual("30ff9933aa6534ff5207142593cd1fca4b23bdd8", c[0..]);
569
570 const extended_nonce = [_]u8{0x42} ** 24;
571 XSalsa20.xor(&c, msg[0..], 0, key, extended_nonce);
572 try htest.assertEqual("b4ab7d82e750ec07644fa3281bce6cd91d4243f9", c[0..]);
573}
574
575test "xsalsa20poly1305" {
576 var msg: [100]u8 = undefined;
577 var msg2: [msg.len]u8 = undefined;
578 var c: [msg.len]u8 = undefined;
579 var key: [XSalsa20Poly1305.key_length]u8 = undefined;
580 var nonce: [XSalsa20Poly1305.nonce_length]u8 = undefined;
581 var tag: [XSalsa20Poly1305.tag_length]u8 = undefined;
582 crypto.random.bytes(&msg);
583 crypto.random.bytes(&key);
584 crypto.random.bytes(&nonce);
585
586 XSalsa20Poly1305.encrypt(c[0..], &tag, msg[0..], "ad", nonce, key);
587 try XSalsa20Poly1305.decrypt(msg2[0..], c[0..], tag, "ad", nonce, key);
588}
589
590test "xsalsa20poly1305 secretbox" {
591 var msg: [100]u8 = undefined;
592 var msg2: [msg.len]u8 = undefined;
593 var key: [XSalsa20Poly1305.key_length]u8 = undefined;
594 var nonce: [Box.nonce_length]u8 = undefined;
595 var boxed: [msg.len + Box.tag_length]u8 = undefined;
596 crypto.random.bytes(&msg);
597 crypto.random.bytes(&key);
598 crypto.random.bytes(&nonce);
599
600 SecretBox.seal(boxed[0..], msg[0..], nonce, key);
601 try SecretBox.open(msg2[0..], boxed[0..], nonce, key);
602}
603
604test "xsalsa20poly1305 box" {
605 if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24299
606
607 var msg: [100]u8 = undefined;
608 var msg2: [msg.len]u8 = undefined;
609 var nonce: [Box.nonce_length]u8 = undefined;
610 var boxed: [msg.len + Box.tag_length]u8 = undefined;
611 crypto.random.bytes(&msg);
612 crypto.random.bytes(&nonce);
613
614 const kp1 = Box.KeyPair.generate();
615 const kp2 = Box.KeyPair.generate();
616 try Box.seal(boxed[0..], msg[0..], nonce, kp1.public_key, kp2.secret_key);
617 try Box.open(msg2[0..], boxed[0..], nonce, kp2.public_key, kp1.secret_key);
618}
619
620test "xsalsa20poly1305 sealedbox" {
621 if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24299
622
623 var msg: [100]u8 = undefined;
624 var msg2: [msg.len]u8 = undefined;
625 var boxed: [msg.len + SealedBox.seal_length]u8 = undefined;
626 crypto.random.bytes(&msg);
627
628 const kp = Box.KeyPair.generate();
629 try SealedBox.seal(boxed[0..], msg[0..], kp.public_key);
630 try SealedBox.open(msg2[0..], boxed[0..], kp);
631}
632
633test "secretbox twoblocks" {
634 const key = [_]u8{ 0xc9, 0xc9, 0x4d, 0xcf, 0x68, 0xbe, 0x00, 0xe4, 0x7f, 0xe6, 0x13, 0x26, 0xfc, 0xc4, 0x2f, 0xd0, 0xdb, 0x93, 0x91, 0x1c, 0x09, 0x94, 0x89, 0xe1, 0x1b, 0x88, 0x63, 0x18, 0x86, 0x64, 0x8b, 0x7b };
635 const nonce = [_]u8{ 0xa4, 0x33, 0xe9, 0x0a, 0x07, 0x68, 0x6e, 0x9a, 0x2b, 0x6d, 0xd4, 0x59, 0x04, 0x72, 0x3e, 0xd3, 0x8a, 0x67, 0x55, 0xc7, 0x9e, 0x3e, 0x77, 0xdc };
636 const msg = [_]u8{'a'} ** 97;
637 var ciphertext: [msg.len + SecretBox.tag_length]u8 = undefined;
638 SecretBox.seal(&ciphertext, &msg, nonce, key);
639 try htest.assertEqual("b05760e217288ba079caa2fd57fd3701784974ffcfda20fe523b89211ad8af065a6eb37cdb29d51aca5bd75dafdd21d18b044c54bb7c526cf576c94ee8900f911ceab0147e82b667a28c52d58ceb29554ff45471224d37b03256b01c119b89ff6d36855de8138d103386dbc9d971f52261", &ciphertext);
640}