authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2020-10-25 17:02:26+01:00
committergravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2020-10-25 18:04:12+01:00
log28fb97f1885ea54dd2a1ee2d06b2a73b009b1850
tree1c97f6fb613d0276e6418d8b173073918ad39fb7
parent59af275680ad12f7da4dcc5b41a2cab707ec2ffa

Add (X)Salsa20 and NaCl boxes

The NaCl constructions are available in pretty much all programming languages, making them a solid choice for applications that require interoperability. Go includes them in the standard library, JavaScript has the popular tweetnacl.js module, and reimplementations and ports of TweetNaCl have been made everywhere. Zig has almost everything that NaCl has at this point, the main missing component being the Salsa20 cipher, on top on which NaCl's secretboxes, boxes, and sealedboxes can be implemented. So, here they are! And clean the X25519 API up a little bit by the way.

5 files changed, 496 insertions(+), 43 deletions(-)

lib/std/crypto.zig+13-4
......@@ -6,15 +6,14 @@
66
77/// Authenticated Encryption with Associated Data
88pub const aead = struct {
9 const chacha20 = @import("crypto/chacha20.zig");
10
119 pub const Gimli = @import("crypto/gimli.zig").Aead;
12 pub const ChaCha20Poly1305 = chacha20.Chacha20Poly1305;
13 pub const XChaCha20Poly1305 = chacha20.XChacha20Poly1305;
10 pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").Chacha20Poly1305;
11 pub const XChaCha20Poly1305 = @import("crypto/chacha20.zig").XChacha20Poly1305;
1412 pub const Aegis128L = @import("crypto/aegis.zig").Aegis128L;
1513 pub const Aegis256 = @import("crypto/aegis.zig").Aegis256;
1614 pub const Aes128Gcm = @import("crypto/aes_gcm.zig").Aes128Gcm;
1715 pub const Aes256Gcm = @import("crypto/aes_gcm.zig").Aes256Gcm;
16 pub const XSalsa20Poly1305 = @import("crypto/salsa20.zig").XSalsa20Poly1305;
1817};
1918
2019/// Authentication (MAC) functions.
......@@ -101,6 +100,15 @@ pub const stream = struct {
101100 pub const ChaCha20IETF = @import("crypto/chacha20.zig").ChaCha20IETF;
102101 pub const XChaCha20IETF = @import("crypto/chacha20.zig").XChaCha20IETF;
103102 pub const ChaCha20With64BitNonce = @import("crypto/chacha20.zig").ChaCha20With64BitNonce;
103 pub const Salsa20 = @import("crypto/salsa20.zig").Salsa20;
104 pub const XSalsa20 = @import("crypto/salsa20.zig").XSalsa20;
105};
106
107pub const nacl = struct {
108 const salsa20 = @import("crypto/salsa20.zig");
109 pub const box = salsa20.box;
110 pub const secretBox = salsa20.secretBox;
111 pub const sealedBox = salsa20.sealedBox;
104112};
105113
106114const std = @import("std.zig");
......@@ -134,6 +142,7 @@ test "crypto" {
134142 _ = @import("crypto/sha1.zig");
135143 _ = @import("crypto/sha2.zig");
136144 _ = @import("crypto/sha3.zig");
145 _ = @import("crypto/salsa20.zig");
137146 _ = @import("crypto/siphash.zig");
138147 _ = @import("crypto/25519/curve25519.zig");
139148 _ = @import("crypto/25519/ed25519.zig");
lib/std/crypto/25519/ed25519.zig+9-3
......@@ -4,6 +4,7 @@
44// The MIT license requires this copyright notice to be included in all copies
55// and substantial portions of the software.
66const std = @import("std");
7const crypto = std.crypto;
78const fmt = std.fmt;
89const mem = std.mem;
910const Sha512 = std.crypto.hash.sha2.Sha512;
......@@ -31,14 +32,19 @@ pub const Ed25519 = struct {
3132 ///
3233 /// For this reason, an EdDSA secret key is commonly called a seed,
3334 /// from which the actual secret is derived.
34 pub fn createKeyPair(seed: [seed_length]u8) ![keypair_length]u8 {
35 pub fn createKeyPair(seed: ?[seed_length]u8) ![keypair_length]u8 {
36 const sk = seed orelse sk: {
37 var random_seed: [seed_length]u8 = undefined;
38 try crypto.randomBytes(&random_seed);
39 break :sk random_seed;
40 };
3541 var az: [Sha512.digest_length]u8 = undefined;
3642 var h = Sha512.init(.{});
37 h.update(&seed);
43 h.update(&sk);
3844 h.final(&az);
3945 const p = try Curve.basePoint.clampedMul(az[0..32].*);
4046 var keypair: [keypair_length]u8 = undefined;
41 mem.copy(u8, &keypair, &seed);
47 mem.copy(u8, &keypair, &sk);
4248 mem.copy(u8, keypair[seed_length..], &p.toBytes());
4349 return keypair;
4450 }
lib/std/crypto/25519/x25519.zig+39-32
......@@ -4,6 +4,7 @@
44// The MIT license requires this copyright notice to be included in all copies
55// and substantial portions of the software.
66const std = @import("std");
7const crypto = std.crypto;
78const mem = std.mem;
89const fmt = std.fmt;
910
......@@ -13,40 +14,46 @@ pub const X25519 = struct {
1314 pub const Curve = @import("curve25519.zig").Curve25519;
1415 /// Length (in bytes) of a secret key.
1516 pub const secret_length = 32;
17 /// Length (in bytes) of a public key.
18 pub const public_length = 32;
1619 /// Length (in bytes) of the output of the DH function.
17 pub const key_length = 32;
20 pub const shared_length = 32;
21 /// Seed (for key pair creation) length in bytes.
22 pub const seed_length = 32;
23
24 /// An X25519 key pair.
25 pub const KeyPair = struct {
26 /// Public part.
27 public_key: [public_length]u8,
28 /// Secret part.
29 secret_key: [secret_length]u8,
30
31 /// Create a new key pair using an optional seed.
32 pub fn create(seed: ?[seed_length]u8) !KeyPair {
33 const sk = seed orelse sk: {
34 var random_seed: [seed_length]u8 = undefined;
35 try crypto.randomBytes(&random_seed);
36 break :sk random_seed;
37 };
38 var kp: KeyPair = undefined;
39 mem.copy(u8, &kp.secret_key, sk[0..]);
40 try X25519.recoverPublicKey(&kp.public_key, sk);
41 return kp;
42 }
43 };
1844
1945 /// Compute the public key for a given private key.
20 pub fn createPublicKey(public_key: []u8, private_key: []const u8) bool {
21 std.debug.assert(private_key.len >= key_length);
22 std.debug.assert(public_key.len >= key_length);
23 var s: [32]u8 = undefined;
24 mem.copy(u8, &s, private_key[0..32]);
25 if (Curve.basePoint.clampedMul(s)) |q| {
26 mem.copy(u8, public_key, q.toBytes()[0..]);
27 return true;
28 } else |_| {
29 return false;
30 }
46 pub fn recoverPublicKey(public_key: *[public_length]u8, secret_key: [secret_length]u8) !void {
47 const q = try Curve.basePoint.clampedMul(secret_key);
48 mem.copy(u8, public_key, q.toBytes()[0..]);
3149 }
3250
3351 /// Compute the scalar product of a public key and a secret scalar.
3452 /// Note that the output should not be used as a shared secret without
3553 /// hashing it first.
36 pub fn create(out: []u8, private_key: []const u8, public_key: []const u8) bool {
37 std.debug.assert(out.len >= secret_length);
38 std.debug.assert(private_key.len >= key_length);
39 std.debug.assert(public_key.len >= key_length);
40 var s: [32]u8 = undefined;
41 var b: [32]u8 = undefined;
42 mem.copy(u8, &s, private_key[0..32]);
43 mem.copy(u8, &b, public_key[0..32]);
44 if (Curve.fromBytes(b).clampedMul(s)) |q| {
45 mem.copy(u8, out, q.toBytes()[0..]);
46 return true;
47 } else |_| {
48 return false;
49 }
54 pub fn scalarmult(out: *[shared_length]u8, secret_key: [secret_length]u8, public_key: [public_length]u8) !void {
55 const q = try Curve.fromBytes(public_key).clampedMul(secret_key);
56 mem.copy(u8, out, q.toBytes()[0..]);
5057 }
5158};
5259
......@@ -56,7 +63,7 @@ test "x25519 public key calculation from secret key" {
5663 var pk_calculated: [32]u8 = undefined;
5764 try fmt.hexToBytes(sk[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");
5865 try fmt.hexToBytes(pk_expected[0..], "f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50");
59 std.testing.expect(X25519.createPublicKey(pk_calculated[0..], &sk));
66 try X25519.recoverPublicKey(&pk_calculated, sk);
6067 std.testing.expectEqual(pk_calculated, pk_expected);
6168}
6269
......@@ -68,7 +75,7 @@ test "x25519 rfc7748 vector1" {
6875
6976 var output: [32]u8 = undefined;
7077
71 std.testing.expect(X25519.create(output[0..], secret_key[0..], public_key[0..]));
78 try X25519.scalarmult(&output, secret_key, public_key);
7279 std.testing.expectEqual(output, expected_output);
7380}
7481
......@@ -80,7 +87,7 @@ test "x25519 rfc7748 vector2" {
8087
8188 var output: [32]u8 = undefined;
8289
83 std.testing.expect(X25519.create(output[0..], secret_key[0..], public_key[0..]));
90 try X25519.scalarmult(&output, secret_key, public_key);
8491 std.testing.expectEqual(output, expected_output);
8592}
8693
......@@ -94,7 +101,7 @@ test "x25519 rfc7748 one iteration" {
94101 var i: usize = 0;
95102 while (i < 1) : (i += 1) {
96103 var output: [32]u8 = undefined;
97 std.testing.expect(X25519.create(output[0..], &k, &u));
104 try X25519.scalarmult(output[0..], k, u);
98105
99106 mem.copy(u8, u[0..], k[0..]);
100107 mem.copy(u8, k[0..], output[0..]);
......@@ -118,7 +125,7 @@ test "x25519 rfc7748 1,000 iterations" {
118125 var i: usize = 0;
119126 while (i < 1000) : (i += 1) {
120127 var output: [32]u8 = undefined;
121 std.testing.expect(X25519.create(output[0..], &k, &u));
128 std.testing.expect(X25519.scalarmult(output[0..], &k, &u));
122129
123130 mem.copy(u8, u[0..], k[0..]);
124131 mem.copy(u8, k[0..], output[0..]);
......@@ -141,7 +148,7 @@ test "x25519 rfc7748 1,000,000 iterations" {
141148 var i: usize = 0;
142149 while (i < 1000000) : (i += 1) {
143150 var output: [32]u8 = undefined;
144 std.testing.expect(X25519.create(output[0..], &k, &u));
151 std.testing.expect(X25519.scalarmult(output[0..], &k, &u));
145152
146153 mem.copy(u8, u[0..], k[0..]);
147154 mem.copy(u8, k[0..], output[0..]);
lib/std/crypto/benchmark.zig+5-4
......@@ -96,12 +96,12 @@ pub fn benchmarkMac(comptime Mac: anytype, comptime bytes: comptime_int) !u64 {
9696const exchanges = [_]Crypto{Crypto{ .ty = crypto.dh.X25519, .name = "x25519" }};
9797
9898pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_count: comptime_int) !u64 {
99 std.debug.assert(DhKeyExchange.key_length >= DhKeyExchange.secret_length);
99 std.debug.assert(DhKeyExchange.shared_length >= DhKeyExchange.secret_length);
100100
101 var in: [DhKeyExchange.key_length]u8 = undefined;
101 var in: [DhKeyExchange.shared_length]u8 = undefined;
102102 prng.random.bytes(in[0..]);
103103
104 var out: [DhKeyExchange.key_length]u8 = undefined;
104 var out: [DhKeyExchange.shared_length]u8 = undefined;
105105 prng.random.bytes(out[0..]);
106106
107107 var timer = try Timer.start();
......@@ -109,7 +109,7 @@ pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_c
109109 {
110110 var i: usize = 0;
111111 while (i < exchange_count) : (i += 1) {
112 _ = DhKeyExchange.create(out[0..], out[0..], in[0..]);
112 try DhKeyExchange.scalarmult(&out, out, in);
113113 mem.doNotOptimizeAway(&out);
114114 }
115115 }
......@@ -208,6 +208,7 @@ pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime
208208const aeads = [_]Crypto{
209209 Crypto{ .ty = crypto.aead.ChaCha20Poly1305, .name = "chacha20Poly1305" },
210210 Crypto{ .ty = crypto.aead.XChaCha20Poly1305, .name = "xchacha20Poly1305" },
211 Crypto{ .ty = crypto.aead.XSalsa20Poly1305, .name = "xsalsa20Poly1305" },
211212 Crypto{ .ty = crypto.aead.Gimli, .name = "gimli-aead" },
212213 Crypto{ .ty = crypto.aead.Aegis128L, .name = "aegis-128l" },
213214 Crypto{ .ty = crypto.aead.Aegis256, .name = "aegis-256" },
lib/std/crypto/salsa20.zig created+430
......@@ -0,0 +1,430 @@
1const std = @import("std");
2const crypto = std.crypto;
3const debug = std.debug;
4const math = std.math;
5const mem = std.mem;
6
7const Poly1305 = crypto.onetimeauth.Poly1305;
8const Blake2b = crypto.hash.blake2.Blake2b;
9const X25519 = crypto.dh.X25519;
10
11const Salsa20NonVecImpl = struct {
12 const BlockVec = [16]u32;
13
14 fn initContext(key: [8]u32, d: [4]u32) BlockVec {
15 const c = "expand 32-byte k";
16 const constant_le = comptime [4]u32{
17 mem.readIntLittle(u32, c[0..4]),
18 mem.readIntLittle(u32, c[4..8]),
19 mem.readIntLittle(u32, c[8..12]),
20 mem.readIntLittle(u32, c[12..16]),
21 };
22 return BlockVec{
23 constant_le[0], key[0], key[1], key[2],
24 key[3], constant_le[1], d[0], d[1],
25 d[2], d[3], constant_le[2], key[4],
26 key[5], key[6], key[7], constant_le[3],
27 };
28 }
29
30 const QuarterRound = struct {
31 a: usize,
32 b: usize,
33 c: usize,
34 d: u6,
35 };
36
37 inline fn Rp(comptime a: usize, comptime b: usize, comptime c: usize, comptime d: u6) QuarterRound {
38 return QuarterRound{
39 .a = a,
40 .b = b,
41 .c = c,
42 .d = d,
43 };
44 }
45
46 inline fn salsa20Core(x: *BlockVec, input: BlockVec) void {
47 const arx_steps = comptime [_]QuarterRound{
48 Rp(4, 0, 12, 7), Rp(8, 4, 0, 9), Rp(12, 8, 4, 13), Rp(0, 12, 8, 18),
49 Rp(9, 5, 1, 7), Rp(13, 9, 5, 9), Rp(1, 13, 9, 13), Rp(5, 1, 13, 18),
50 Rp(14, 10, 6, 7), Rp(2, 14, 10, 9), Rp(6, 2, 14, 13), Rp(10, 6, 2, 18),
51 Rp(3, 15, 11, 7), Rp(7, 3, 15, 9), Rp(11, 7, 3, 13), Rp(15, 11, 7, 18),
52 Rp(1, 0, 3, 7), Rp(2, 1, 0, 9), Rp(3, 2, 1, 13), Rp(0, 3, 2, 18),
53 Rp(6, 5, 4, 7), Rp(7, 6, 5, 9), Rp(4, 7, 6, 13), Rp(5, 4, 7, 18),
54 Rp(11, 10, 9, 7), Rp(8, 11, 10, 9), Rp(9, 8, 11, 13), Rp(10, 9, 8, 18),
55 Rp(12, 15, 14, 7), Rp(13, 12, 15, 9), Rp(14, 13, 12, 13), Rp(15, 14, 13, 18),
56 };
57 x.* = input;
58 var j: usize = 0;
59 while (j < 20) : (j += 2) {
60 inline for (arx_steps) |r| {
61 x[r.a] ^= math.rotl(u32, x[r.b] +% x[r.c], r.d);
62 }
63 }
64 }
65
66 fn hashToBytes(out: *[64]u8, x: BlockVec) void {
67 for (x) |w, i| {
68 mem.writeIntLittle(u32, out[i * 4 ..][0..4], w);
69 }
70 }
71
72 fn contextFeedback(x: *BlockVec, ctx: BlockVec) void {
73 var i: usize = 0;
74 while (i < 16) : (i += 1) {
75 x[i] +%= ctx[i];
76 }
77 }
78
79 fn salsa20Internal(out: []u8, in: []const u8, key: [8]u32, d: [4]u32) void {
80 var ctx = initContext(key, d);
81 var x: BlockVec = undefined;
82 var buf: [64]u8 = undefined;
83 var i: usize = 0;
84 while (i + 64 <= in.len) : (i += 64) {
85 salsa20Core(x[0..], ctx);
86 contextFeedback(&x, ctx);
87 hashToBytes(buf[0..], x);
88 var xout = out[i..];
89 const xin = in[i..];
90 var j: usize = 0;
91 while (j < 64) : (j += 1) {
92 xout[j] = xin[j];
93 }
94 j = 0;
95 while (j < 64) : (j += 1) {
96 xout[j] ^= buf[j];
97 }
98 ctx[9] += @boolToInt(@addWithOverflow(u32, ctx[8], 1, &ctx[8]));
99 }
100 if (i < in.len) {
101 salsa20Core(x[0..], ctx);
102 contextFeedback(&x, ctx);
103 hashToBytes(buf[0..], x);
104
105 var xout = out[i..];
106 const xin = in[i..];
107 var j: usize = 0;
108 while (j < in.len % 64) : (j += 1) {
109 xout[j] = xin[j] ^ buf[j];
110 }
111 }
112 }
113
114 fn hsalsa20(input: [16]u8, key: [32]u8) [32]u8 {
115 var c: [4]u32 = undefined;
116 for (c) |_, i| {
117 c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);
118 }
119 const ctx = initContext(keyToWords(key), c);
120 var x: BlockVec = undefined;
121 salsa20Core(x[0..], ctx);
122 var out: [32]u8 = undefined;
123 mem.writeIntLittle(u32, out[0..4], x[0]);
124 mem.writeIntLittle(u32, out[4..8], x[5]);
125 mem.writeIntLittle(u32, out[8..12], x[10]);
126 mem.writeIntLittle(u32, out[12..16], x[15]);
127 mem.writeIntLittle(u32, out[16..20], x[6]);
128 mem.writeIntLittle(u32, out[20..24], x[7]);
129 mem.writeIntLittle(u32, out[24..28], x[8]);
130 mem.writeIntLittle(u32, out[28..32], x[9]);
131 return out;
132 }
133};
134
135const Salsa20Impl = Salsa20NonVecImpl;
136
137fn keyToWords(key: [32]u8) [8]u32 {
138 var k: [8]u32 = undefined;
139 var i: usize = 0;
140 while (i < 8) : (i += 1) {
141 k[i] = mem.readIntLittle(u32, key[i * 4 ..][0..4]);
142 }
143 return k;
144}
145
146fn extend(key: [32]u8, nonce: [24]u8) struct { key: [32]u8, nonce: [8]u8 } {
147 return .{
148 .key = Salsa20Impl.hsalsa20(nonce[0..16].*, key),
149 .nonce = nonce[16..24].*,
150 };
151}
152
153/// The Salsa20 stream cipher.
154pub const Salsa20 = struct {
155 /// Nonce length in bytes.
156 pub const nonce_length = 8;
157 /// Key length in bytes.
158 pub const key_length = 32;
159
160 /// Add the output of the Salsa20 stream cipher to `in` and stores the result into `out`.
161 /// WARNING: This function doesn't provide authenticated encryption.
162 /// Using the AEAD or one of the `box` versions is usually preferred.
163 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
164 debug.assert(in.len == out.len);
165
166 var d: [4]u32 = undefined;
167 d[0] = mem.readIntLittle(u32, nonce[0..4]);
168 d[1] = mem.readIntLittle(u32, nonce[4..8]);
169 d[2] = @truncate(u32, counter);
170 d[3] = @truncate(u32, counter >> 32);
171 Salsa20Impl.salsa20Internal(out, in, keyToWords(key), d);
172 }
173};
174
175/// The XSalsa20 stream cipher.
176pub const XSalsa20 = struct {
177 /// Nonce length in bytes.
178 pub const nonce_length = 24;
179 /// Key length in bytes.
180 pub const key_length = 32;
181
182 /// Add the output of the XSalsa20 stream cipher to `in` and stores the result into `out`.
183 /// WARNING: This function doesn't provide authenticated encryption.
184 /// Using the AEAD or one of the `box` versions is usually preferred.
185 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
186 const extended = extend(key, nonce);
187 Salsa20.xor(out, in, counter, extended.key, extended.nonce);
188 }
189};
190
191/// The XSalsa20 stream cipher, combined with the Poly1305 MAC
192pub const XSalsa20Poly1305 = struct {
193 /// Authentication tag length in bytes.
194 pub const tag_length = Poly1305.mac_length;
195 /// Nonce length in bytes.
196 pub const nonce_length = XSalsa20.nonce_length;
197 /// Key length in bytes.
198 pub const key_length = XSalsa20.key_length;
199
200 /// c: ciphertext: output buffer should be of size m.len
201 /// tag: authentication tag: output MAC
202 /// m: message
203 /// ad: Associated Data
204 /// npub: public nonce
205 /// k: private key
206 pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
207 debug.assert(c.len == m.len);
208 const extended = extend(k, npub);
209 var block0 = [_]u8{0} ** 64;
210 const mlen0 = math.min(32, m.len);
211 mem.copy(u8, block0[32..][0..mlen0], m[0..mlen0]);
212 Salsa20.xor(block0[0..], block0[0..], 0, extended.key, extended.nonce);
213 mem.copy(u8, c[0..mlen0], block0[32..][0..mlen0]);
214 Salsa20.xor(c[mlen0..], m[mlen0..], 1, extended.key, extended.nonce);
215 var mac = Poly1305.init(block0[0..32]);
216 mac.update(ad);
217 mac.update(c);
218 mac.final(tag);
219 }
220
221 /// m: message: output buffer should be of size c.len
222 /// c: ciphertext
223 /// tag: authentication tag
224 /// ad: Associated Data
225 /// npub: public nonce
226 /// k: private key
227 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void {
228 debug.assert(c.len == m.len);
229 const extended = extend(k, npub);
230 var block0 = [_]u8{0} ** 64;
231 const mlen0 = math.min(32, c.len);
232 mem.copy(u8, block0[32..][0..mlen0], c[0..mlen0]);
233 Salsa20.xor(block0[0..], block0[0..], 0, extended.key, extended.nonce);
234 var mac = Poly1305.init(block0[0..32]);
235 mac.update(ad);
236 mac.update(c);
237 var computedTag: [tag_length]u8 = undefined;
238 mac.final(&computedTag);
239 var acc: u8 = 0;
240 for (computedTag) |_, i| {
241 acc |= (computedTag[i] ^ tag[i]);
242 }
243 if (acc != 0) {
244 mem.secureZero(u8, &computedTag);
245 return error.AuthenticationFailed;
246 }
247 mem.copy(u8, m[0..mlen0], block0[32..][0..mlen0]);
248 Salsa20.xor(m[mlen0..], c[mlen0..], 1, extended.key, extended.nonce);
249 }
250};
251
252/// NaCl-compatible secretbox API.
253///
254/// A secretbox contains both an encrypted message and an authentication tag to verify that it hasn't been tampered with.
255/// A secret key shared by all the recipients must be already known in order to use this API.
256///
257/// Nonces are 192-bit large and can safely be chosen with a random number generator.
258pub const secretBox = struct {
259 /// Key length in bytes.
260 pub const key_length = XSalsa20Poly1305.key_length;
261 /// Nonce length in bytes.
262 pub const nonce_length = XSalsa20Poly1305.nonce_length;
263 /// Authentication tag length in bytes.
264 pub const tag_length = XSalsa20Poly1305.tag_length;
265
266 /// Encrypt and authenticate `m` using a nonce `npub` and a key `k`.
267 /// `c` must be exactly `tag_length` longer than `m`, as it will store both the ciphertext and the authentication tag.
268 pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
269 debug.assert(c.len == tag_length + m.len);
270 XSalsa20Poly1305.encrypt(c[tag_length..], c[0..tag_length], m, "", npub, k);
271 }
272
273 /// Verify and decrypt `c` using a nonce `npub` and a key `k`.
274 /// `m` must be exactly `tag_length` smaller than `c`, as `c` includes an authentication tag in addition to the encrypted message.
275 pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void {
276 if (c.len < tag_length) {
277 return error.AuthenticationFailed;
278 }
279 debug.assert(m.len == c.len - tag_length);
280 return XSalsa20Poly1305.decrypt(m, c[tag_length..], c[0..tag_length].*, "", npub, k);
281 }
282};
283
284/// NaCl-compatible box API.
285///
286/// A secretbox contains both an encrypted message and an authentication tag to verify that it hasn't been tampered with.
287/// This construction uses public-key cryptography. A shared secret doesn't have to be known in advance by both parties.
288/// Instead, a message is encrypted using a sender's secret key and a recipient's public key,
289/// and is decrypted using the recipient's secret key and the sender's public key.
290///
291/// Nonces are 192-bit large and can safely be chosen with a random number generator.
292pub const box = struct {
293 /// Public key length in bytes.
294 pub const public_length = X25519.public_length;
295 /// Secret key length in bytes.
296 pub const secret_length = X25519.secret_length;
297 /// Shared key length in bytes.
298 pub const shared_length = XSalsa20Poly1305.key_length;
299 /// Seed (for key pair creation) length in bytes.
300 pub const seed_length = X25519.seed_length;
301 /// Nonce length in bytes.
302 pub const nonce_length = XSalsa20Poly1305.nonce_length;
303 /// Authentication tag length in bytes.
304 pub const tag_length = XSalsa20Poly1305.tag_length;
305
306 /// A key pair.
307 pub const KeyPair = X25519.KeyPair;
308
309 /// Compute a secret suitable for `secretbox` given a recipent's public key and a sender's secret key.
310 pub fn createSharedSecret(public_key: [public_length]u8, secret_key: [secret_length]u8) ![shared_length]u8 {
311 var p: [32]u8 = undefined;
312 try X25519.scalarmult(&p, secret_key, public_key);
313 const zero = [_]u8{0} ** 16;
314 return Salsa20Impl.hsalsa20(zero, p);
315 }
316
317 /// Encrypt and authenticate a message using a recipient's public key `public_key` and a sender's `secret_key`.
318 pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) !void {
319 const shared_key = try createSharedSecret(public_key, secret_key);
320 return secretBox.seal(c, m, npub, shared_key);
321 }
322
323 /// Verify and decrypt a message using a recipient's secret key `public_key` and a sender's `public_key`.
324 pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) !void {
325 const shared_key = try createSharedSecret(public_key, secret_key);
326 return secretBox.open(m, c, npub, shared_key);
327 }
328};
329
330/// libsodium-compatible sealed boxes
331///
332/// Sealed boxes are designed to anonymously send messages to a recipient given their public key.
333/// Only the recipient can decrypt these messages, using their private key.
334/// While the recipient can verify the integrity of the message, it cannot verify the identity of the sender.
335///
336/// A message is encrypted using an ephemeral key pair, whose secret part is destroyed right after the encryption process.
337pub const sealedBox = struct {
338 pub const public_length = box.public_length;
339 pub const secret_length = box.secret_length;
340 pub const seed_length = box.seed_length;
341 pub const seal_length = box.public_length + box.tag_length;
342
343 /// A key pair.
344 pub const KeyPair = box.KeyPair;
345
346 fn createNonce(pk1: [public_length]u8, pk2: [public_length]u8) [box.nonce_length]u8 {
347 var hasher = Blake2b(box.nonce_length * 8).init(.{});
348 hasher.update(&pk1);
349 hasher.update(&pk2);
350 var nonce: [box.nonce_length]u8 = undefined;
351 hasher.final(&nonce);
352 return nonce;
353 }
354
355 /// Encrypt a message `m` for a recipient whose public key is `public_key`.
356 /// `c` must be `seal_length` bytes larger than `m`, so that the required metadata can be added.
357 pub fn seal(c: []u8, m: []const u8, public_key: [public_length]u8) !void {
358 debug.assert(c.len == m.len + seal_length);
359 var ekp = try KeyPair.create(null);
360 const nonce = createNonce(ekp.public_key, public_key);
361 mem.copy(u8, c[0..public_length], ekp.public_key[0..]);
362 try box.seal(c[box.public_length..], m, nonce, public_key, ekp.secret_key);
363 mem.secureZero(u8, ekp.secret_key[0..]);
364 }
365
366 /// Decrypt a message using a key pair.
367 /// `m` must be exactly `seal_length` bytes smaller than `c`, as `c` also includes metadata.
368 pub fn open(m: []u8, c: []const u8, keypair: KeyPair) !void {
369 if (c.len < seal_length) {
370 return error.AuthenticationFailed;
371 }
372 const epk = c[0..public_length];
373 const nonce = createNonce(epk.*, keypair.public_key);
374 return box.open(m, c[public_length..], nonce, epk.*, keypair.secret_key);
375 }
376};
377
378test "xsalsa20poly1305" {
379 var msg: [100]u8 = undefined;
380 var msg2: [msg.len]u8 = undefined;
381 var c: [msg.len]u8 = undefined;
382 var key: [XSalsa20Poly1305.key_length]u8 = undefined;
383 var nonce: [XSalsa20Poly1305.nonce_length]u8 = undefined;
384 var tag: [XSalsa20Poly1305.tag_length]u8 = undefined;
385 try crypto.randomBytes(&msg);
386 try crypto.randomBytes(&key);
387 try crypto.randomBytes(&nonce);
388
389 XSalsa20Poly1305.encrypt(c[0..], &tag, msg[0..], "ad", nonce, key);
390 try XSalsa20Poly1305.decrypt(msg2[0..], c[0..], tag, "ad", nonce, key);
391}
392
393test "xsalsa20poly1305 secretbox" {
394 var msg: [100]u8 = undefined;
395 var msg2: [msg.len]u8 = undefined;
396 var key: [XSalsa20Poly1305.key_length]u8 = undefined;
397 var nonce: [box.nonce_length]u8 = undefined;
398 var boxed: [msg.len + box.tag_length]u8 = undefined;
399 try crypto.randomBytes(&msg);
400 try crypto.randomBytes(&key);
401 try crypto.randomBytes(&nonce);
402
403 secretBox.seal(boxed[0..], msg[0..], nonce, key);
404 try secretBox.open(msg2[0..], boxed[0..], nonce, key);
405}
406
407test "xsalsa20poly1305 box" {
408 var msg: [100]u8 = undefined;
409 var msg2: [msg.len]u8 = undefined;
410 var nonce: [box.nonce_length]u8 = undefined;
411 var boxed: [msg.len + box.tag_length]u8 = undefined;
412 try crypto.randomBytes(&msg);
413 try crypto.randomBytes(&nonce);
414
415 var kp1 = try box.KeyPair.create(null);
416 var kp2 = try box.KeyPair.create(null);
417 try box.seal(boxed[0..], msg[0..], nonce, kp1.public_key, kp2.secret_key);
418 try box.open(msg2[0..], boxed[0..], nonce, kp2.public_key, kp1.secret_key);
419}
420
421test "xsalsa20poly1305 sealedbox" {
422 var msg: [100]u8 = undefined;
423 var msg2: [msg.len]u8 = undefined;
424 var boxed: [msg.len + sealedBox.seal_length]u8 = undefined;
425 try crypto.randomBytes(&msg);
426
427 var kp = try box.KeyPair.create(null);
428 try sealedBox.seal(boxed[0..], msg[0..], kp.public_key);
429 try sealedBox.open(msg2[0..], boxed[0..], kp);
430}