Commit 28fb97f188
Changed files (5)
lib
std
lib/std/crypto/25519/ed25519.zig
@@ -4,6 +4,7 @@
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
const std = @import("std");
+const crypto = std.crypto;
const fmt = std.fmt;
const mem = std.mem;
const Sha512 = std.crypto.hash.sha2.Sha512;
@@ -31,14 +32,19 @@ pub const Ed25519 = struct {
///
/// For this reason, an EdDSA secret key is commonly called a seed,
/// from which the actual secret is derived.
- pub fn createKeyPair(seed: [seed_length]u8) ![keypair_length]u8 {
+ pub fn createKeyPair(seed: ?[seed_length]u8) ![keypair_length]u8 {
+ const sk = seed orelse sk: {
+ var random_seed: [seed_length]u8 = undefined;
+ try crypto.randomBytes(&random_seed);
+ break :sk random_seed;
+ };
var az: [Sha512.digest_length]u8 = undefined;
var h = Sha512.init(.{});
- h.update(&seed);
+ h.update(&sk);
h.final(&az);
const p = try Curve.basePoint.clampedMul(az[0..32].*);
var keypair: [keypair_length]u8 = undefined;
- mem.copy(u8, &keypair, &seed);
+ mem.copy(u8, &keypair, &sk);
mem.copy(u8, keypair[seed_length..], &p.toBytes());
return keypair;
}
lib/std/crypto/25519/x25519.zig
@@ -4,6 +4,7 @@
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
const std = @import("std");
+const crypto = std.crypto;
const mem = std.mem;
const fmt = std.fmt;
@@ -13,40 +14,46 @@ pub const X25519 = struct {
pub const Curve = @import("curve25519.zig").Curve25519;
/// Length (in bytes) of a secret key.
pub const secret_length = 32;
+ /// Length (in bytes) of a public key.
+ pub const public_length = 32;
/// Length (in bytes) of the output of the DH function.
- pub const key_length = 32;
+ pub const shared_length = 32;
+ /// Seed (for key pair creation) length in bytes.
+ pub const seed_length = 32;
+
+ /// An X25519 key pair.
+ pub const KeyPair = struct {
+ /// Public part.
+ public_key: [public_length]u8,
+ /// Secret part.
+ secret_key: [secret_length]u8,
+
+ /// Create a new key pair using an optional seed.
+ pub fn create(seed: ?[seed_length]u8) !KeyPair {
+ const sk = seed orelse sk: {
+ var random_seed: [seed_length]u8 = undefined;
+ try crypto.randomBytes(&random_seed);
+ break :sk random_seed;
+ };
+ var kp: KeyPair = undefined;
+ mem.copy(u8, &kp.secret_key, sk[0..]);
+ try X25519.recoverPublicKey(&kp.public_key, sk);
+ return kp;
+ }
+ };
/// Compute the public key for a given private key.
- pub fn createPublicKey(public_key: []u8, private_key: []const u8) bool {
- std.debug.assert(private_key.len >= key_length);
- std.debug.assert(public_key.len >= key_length);
- var s: [32]u8 = undefined;
- mem.copy(u8, &s, private_key[0..32]);
- if (Curve.basePoint.clampedMul(s)) |q| {
- mem.copy(u8, public_key, q.toBytes()[0..]);
- return true;
- } else |_| {
- return false;
- }
+ pub fn recoverPublicKey(public_key: *[public_length]u8, secret_key: [secret_length]u8) !void {
+ const q = try Curve.basePoint.clampedMul(secret_key);
+ mem.copy(u8, public_key, q.toBytes()[0..]);
}
/// Compute the scalar product of a public key and a secret scalar.
/// Note that the output should not be used as a shared secret without
/// hashing it first.
- pub fn create(out: []u8, private_key: []const u8, public_key: []const u8) bool {
- std.debug.assert(out.len >= secret_length);
- std.debug.assert(private_key.len >= key_length);
- std.debug.assert(public_key.len >= key_length);
- var s: [32]u8 = undefined;
- var b: [32]u8 = undefined;
- mem.copy(u8, &s, private_key[0..32]);
- mem.copy(u8, &b, public_key[0..32]);
- if (Curve.fromBytes(b).clampedMul(s)) |q| {
- mem.copy(u8, out, q.toBytes()[0..]);
- return true;
- } else |_| {
- return false;
- }
+ pub fn scalarmult(out: *[shared_length]u8, secret_key: [secret_length]u8, public_key: [public_length]u8) !void {
+ const q = try Curve.fromBytes(public_key).clampedMul(secret_key);
+ mem.copy(u8, out, q.toBytes()[0..]);
}
};
@@ -56,7 +63,7 @@ test "x25519 public key calculation from secret key" {
var pk_calculated: [32]u8 = undefined;
try fmt.hexToBytes(sk[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");
try fmt.hexToBytes(pk_expected[0..], "f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50");
- std.testing.expect(X25519.createPublicKey(pk_calculated[0..], &sk));
+ try X25519.recoverPublicKey(&pk_calculated, sk);
std.testing.expectEqual(pk_calculated, pk_expected);
}
@@ -68,7 +75,7 @@ test "x25519 rfc7748 vector1" {
var output: [32]u8 = undefined;
- std.testing.expect(X25519.create(output[0..], secret_key[0..], public_key[0..]));
+ try X25519.scalarmult(&output, secret_key, public_key);
std.testing.expectEqual(output, expected_output);
}
@@ -80,7 +87,7 @@ test "x25519 rfc7748 vector2" {
var output: [32]u8 = undefined;
- std.testing.expect(X25519.create(output[0..], secret_key[0..], public_key[0..]));
+ try X25519.scalarmult(&output, secret_key, public_key);
std.testing.expectEqual(output, expected_output);
}
@@ -94,7 +101,7 @@ test "x25519 rfc7748 one iteration" {
var i: usize = 0;
while (i < 1) : (i += 1) {
var output: [32]u8 = undefined;
- std.testing.expect(X25519.create(output[0..], &k, &u));
+ try X25519.scalarmult(output[0..], k, u);
mem.copy(u8, u[0..], k[0..]);
mem.copy(u8, k[0..], output[0..]);
@@ -118,7 +125,7 @@ test "x25519 rfc7748 1,000 iterations" {
var i: usize = 0;
while (i < 1000) : (i += 1) {
var output: [32]u8 = undefined;
- std.testing.expect(X25519.create(output[0..], &k, &u));
+ std.testing.expect(X25519.scalarmult(output[0..], &k, &u));
mem.copy(u8, u[0..], k[0..]);
mem.copy(u8, k[0..], output[0..]);
@@ -141,7 +148,7 @@ test "x25519 rfc7748 1,000,000 iterations" {
var i: usize = 0;
while (i < 1000000) : (i += 1) {
var output: [32]u8 = undefined;
- std.testing.expect(X25519.create(output[0..], &k, &u));
+ std.testing.expect(X25519.scalarmult(output[0..], &k, &u));
mem.copy(u8, u[0..], k[0..]);
mem.copy(u8, k[0..], output[0..]);
lib/std/crypto/benchmark.zig
@@ -96,12 +96,12 @@ pub fn benchmarkMac(comptime Mac: anytype, comptime bytes: comptime_int) !u64 {
const exchanges = [_]Crypto{Crypto{ .ty = crypto.dh.X25519, .name = "x25519" }};
pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_count: comptime_int) !u64 {
- std.debug.assert(DhKeyExchange.key_length >= DhKeyExchange.secret_length);
+ std.debug.assert(DhKeyExchange.shared_length >= DhKeyExchange.secret_length);
- var in: [DhKeyExchange.key_length]u8 = undefined;
+ var in: [DhKeyExchange.shared_length]u8 = undefined;
prng.random.bytes(in[0..]);
- var out: [DhKeyExchange.key_length]u8 = undefined;
+ var out: [DhKeyExchange.shared_length]u8 = undefined;
prng.random.bytes(out[0..]);
var timer = try Timer.start();
@@ -109,7 +109,7 @@ pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_c
{
var i: usize = 0;
while (i < exchange_count) : (i += 1) {
- _ = DhKeyExchange.create(out[0..], out[0..], in[0..]);
+ try DhKeyExchange.scalarmult(&out, out, in);
mem.doNotOptimizeAway(&out);
}
}
@@ -208,6 +208,7 @@ pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime
const aeads = [_]Crypto{
Crypto{ .ty = crypto.aead.ChaCha20Poly1305, .name = "chacha20Poly1305" },
Crypto{ .ty = crypto.aead.XChaCha20Poly1305, .name = "xchacha20Poly1305" },
+ Crypto{ .ty = crypto.aead.XSalsa20Poly1305, .name = "xsalsa20Poly1305" },
Crypto{ .ty = crypto.aead.Gimli, .name = "gimli-aead" },
Crypto{ .ty = crypto.aead.Aegis128L, .name = "aegis-128l" },
Crypto{ .ty = crypto.aead.Aegis256, .name = "aegis-256" },
lib/std/crypto/salsa20.zig
@@ -0,0 +1,430 @@
+const std = @import("std");
+const crypto = std.crypto;
+const debug = std.debug;
+const math = std.math;
+const mem = std.mem;
+
+const Poly1305 = crypto.onetimeauth.Poly1305;
+const Blake2b = crypto.hash.blake2.Blake2b;
+const X25519 = crypto.dh.X25519;
+
+const Salsa20NonVecImpl = struct {
+ const BlockVec = [16]u32;
+
+ fn initContext(key: [8]u32, d: [4]u32) BlockVec {
+ const c = "expand 32-byte k";
+ const constant_le = comptime [4]u32{
+ mem.readIntLittle(u32, c[0..4]),
+ mem.readIntLittle(u32, c[4..8]),
+ mem.readIntLittle(u32, c[8..12]),
+ mem.readIntLittle(u32, c[12..16]),
+ };
+ return BlockVec{
+ constant_le[0], key[0], key[1], key[2],
+ key[3], constant_le[1], d[0], d[1],
+ d[2], d[3], constant_le[2], key[4],
+ key[5], key[6], key[7], constant_le[3],
+ };
+ }
+
+ const QuarterRound = struct {
+ a: usize,
+ b: usize,
+ c: usize,
+ d: u6,
+ };
+
+ inline fn Rp(comptime a: usize, comptime b: usize, comptime c: usize, comptime d: u6) QuarterRound {
+ return QuarterRound{
+ .a = a,
+ .b = b,
+ .c = c,
+ .d = d,
+ };
+ }
+
+ inline fn salsa20Core(x: *BlockVec, input: BlockVec) void {
+ const arx_steps = comptime [_]QuarterRound{
+ Rp(4, 0, 12, 7), Rp(8, 4, 0, 9), Rp(12, 8, 4, 13), Rp(0, 12, 8, 18),
+ Rp(9, 5, 1, 7), Rp(13, 9, 5, 9), Rp(1, 13, 9, 13), Rp(5, 1, 13, 18),
+ Rp(14, 10, 6, 7), Rp(2, 14, 10, 9), Rp(6, 2, 14, 13), Rp(10, 6, 2, 18),
+ Rp(3, 15, 11, 7), Rp(7, 3, 15, 9), Rp(11, 7, 3, 13), Rp(15, 11, 7, 18),
+ Rp(1, 0, 3, 7), Rp(2, 1, 0, 9), Rp(3, 2, 1, 13), Rp(0, 3, 2, 18),
+ Rp(6, 5, 4, 7), Rp(7, 6, 5, 9), Rp(4, 7, 6, 13), Rp(5, 4, 7, 18),
+ Rp(11, 10, 9, 7), Rp(8, 11, 10, 9), Rp(9, 8, 11, 13), Rp(10, 9, 8, 18),
+ Rp(12, 15, 14, 7), Rp(13, 12, 15, 9), Rp(14, 13, 12, 13), Rp(15, 14, 13, 18),
+ };
+ x.* = input;
+ var j: usize = 0;
+ while (j < 20) : (j += 2) {
+ inline for (arx_steps) |r| {
+ x[r.a] ^= math.rotl(u32, x[r.b] +% x[r.c], r.d);
+ }
+ }
+ }
+
+ fn hashToBytes(out: *[64]u8, x: BlockVec) void {
+ for (x) |w, i| {
+ mem.writeIntLittle(u32, out[i * 4 ..][0..4], w);
+ }
+ }
+
+ fn contextFeedback(x: *BlockVec, ctx: BlockVec) void {
+ var i: usize = 0;
+ while (i < 16) : (i += 1) {
+ x[i] +%= ctx[i];
+ }
+ }
+
+ fn salsa20Internal(out: []u8, in: []const u8, key: [8]u32, d: [4]u32) void {
+ var ctx = initContext(key, d);
+ var x: BlockVec = undefined;
+ var buf: [64]u8 = undefined;
+ var i: usize = 0;
+ while (i + 64 <= in.len) : (i += 64) {
+ salsa20Core(x[0..], ctx);
+ contextFeedback(&x, ctx);
+ hashToBytes(buf[0..], x);
+ var xout = out[i..];
+ const xin = in[i..];
+ var j: usize = 0;
+ while (j < 64) : (j += 1) {
+ xout[j] = xin[j];
+ }
+ j = 0;
+ while (j < 64) : (j += 1) {
+ xout[j] ^= buf[j];
+ }
+ ctx[9] += @boolToInt(@addWithOverflow(u32, ctx[8], 1, &ctx[8]));
+ }
+ if (i < in.len) {
+ salsa20Core(x[0..], ctx);
+ contextFeedback(&x, ctx);
+ hashToBytes(buf[0..], x);
+
+ var xout = out[i..];
+ const xin = in[i..];
+ var j: usize = 0;
+ while (j < in.len % 64) : (j += 1) {
+ xout[j] = xin[j] ^ buf[j];
+ }
+ }
+ }
+
+ fn hsalsa20(input: [16]u8, key: [32]u8) [32]u8 {
+ var c: [4]u32 = undefined;
+ for (c) |_, i| {
+ c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);
+ }
+ const ctx = initContext(keyToWords(key), c);
+ var x: BlockVec = undefined;
+ salsa20Core(x[0..], ctx);
+ var out: [32]u8 = undefined;
+ mem.writeIntLittle(u32, out[0..4], x[0]);
+ mem.writeIntLittle(u32, out[4..8], x[5]);
+ mem.writeIntLittle(u32, out[8..12], x[10]);
+ mem.writeIntLittle(u32, out[12..16], x[15]);
+ mem.writeIntLittle(u32, out[16..20], x[6]);
+ mem.writeIntLittle(u32, out[20..24], x[7]);
+ mem.writeIntLittle(u32, out[24..28], x[8]);
+ mem.writeIntLittle(u32, out[28..32], x[9]);
+ return out;
+ }
+};
+
+const Salsa20Impl = Salsa20NonVecImpl;
+
+fn keyToWords(key: [32]u8) [8]u32 {
+ var k: [8]u32 = undefined;
+ var i: usize = 0;
+ while (i < 8) : (i += 1) {
+ k[i] = mem.readIntLittle(u32, key[i * 4 ..][0..4]);
+ }
+ return k;
+}
+
+fn extend(key: [32]u8, nonce: [24]u8) struct { key: [32]u8, nonce: [8]u8 } {
+ return .{
+ .key = Salsa20Impl.hsalsa20(nonce[0..16].*, key),
+ .nonce = nonce[16..24].*,
+ };
+}
+
+/// The Salsa20 stream cipher.
+pub const Salsa20 = struct {
+ /// Nonce length in bytes.
+ pub const nonce_length = 8;
+ /// Key length in bytes.
+ pub const key_length = 32;
+
+ /// Add the output of the Salsa20 stream cipher to `in` and stores the result into `out`.
+ /// WARNING: This function doesn't provide authenticated encryption.
+ /// Using the AEAD or one of the `box` versions is usually preferred.
+ pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
+ debug.assert(in.len == out.len);
+
+ var d: [4]u32 = undefined;
+ d[0] = mem.readIntLittle(u32, nonce[0..4]);
+ d[1] = mem.readIntLittle(u32, nonce[4..8]);
+ d[2] = @truncate(u32, counter);
+ d[3] = @truncate(u32, counter >> 32);
+ Salsa20Impl.salsa20Internal(out, in, keyToWords(key), d);
+ }
+};
+
+/// The XSalsa20 stream cipher.
+pub const XSalsa20 = struct {
+ /// Nonce length in bytes.
+ pub const nonce_length = 24;
+ /// Key length in bytes.
+ pub const key_length = 32;
+
+ /// Add the output of the XSalsa20 stream cipher to `in` and stores the result into `out`.
+ /// WARNING: This function doesn't provide authenticated encryption.
+ /// Using the AEAD or one of the `box` versions is usually preferred.
+ pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
+ const extended = extend(key, nonce);
+ Salsa20.xor(out, in, counter, extended.key, extended.nonce);
+ }
+};
+
+/// The XSalsa20 stream cipher, combined with the Poly1305 MAC
+pub const XSalsa20Poly1305 = struct {
+ /// Authentication tag length in bytes.
+ pub const tag_length = Poly1305.mac_length;
+ /// Nonce length in bytes.
+ pub const nonce_length = XSalsa20.nonce_length;
+ /// Key length in bytes.
+ pub const key_length = XSalsa20.key_length;
+
+ /// c: ciphertext: output buffer should be of size m.len
+ /// tag: authentication tag: output MAC
+ /// m: message
+ /// ad: Associated Data
+ /// npub: public nonce
+ /// k: private key
+ pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
+ debug.assert(c.len == m.len);
+ const extended = extend(k, npub);
+ var block0 = [_]u8{0} ** 64;
+ const mlen0 = math.min(32, m.len);
+ mem.copy(u8, block0[32..][0..mlen0], m[0..mlen0]);
+ Salsa20.xor(block0[0..], block0[0..], 0, extended.key, extended.nonce);
+ mem.copy(u8, c[0..mlen0], block0[32..][0..mlen0]);
+ Salsa20.xor(c[mlen0..], m[mlen0..], 1, extended.key, extended.nonce);
+ var mac = Poly1305.init(block0[0..32]);
+ mac.update(ad);
+ mac.update(c);
+ mac.final(tag);
+ }
+
+ /// m: message: output buffer should be of size c.len
+ /// c: ciphertext
+ /// tag: authentication tag
+ /// ad: Associated Data
+ /// npub: public nonce
+ /// k: private key
+ pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void {
+ debug.assert(c.len == m.len);
+ const extended = extend(k, npub);
+ var block0 = [_]u8{0} ** 64;
+ const mlen0 = math.min(32, c.len);
+ mem.copy(u8, block0[32..][0..mlen0], c[0..mlen0]);
+ Salsa20.xor(block0[0..], block0[0..], 0, extended.key, extended.nonce);
+ var mac = Poly1305.init(block0[0..32]);
+ mac.update(ad);
+ mac.update(c);
+ var computedTag: [tag_length]u8 = undefined;
+ mac.final(&computedTag);
+ var acc: u8 = 0;
+ for (computedTag) |_, i| {
+ acc |= (computedTag[i] ^ tag[i]);
+ }
+ if (acc != 0) {
+ mem.secureZero(u8, &computedTag);
+ return error.AuthenticationFailed;
+ }
+ mem.copy(u8, m[0..mlen0], block0[32..][0..mlen0]);
+ Salsa20.xor(m[mlen0..], c[mlen0..], 1, extended.key, extended.nonce);
+ }
+};
+
+/// NaCl-compatible secretbox API.
+///
+/// A secretbox contains both an encrypted message and an authentication tag to verify that it hasn't been tampered with.
+/// A secret key shared by all the recipients must be already known in order to use this API.
+///
+/// Nonces are 192-bit large and can safely be chosen with a random number generator.
+pub const secretBox = struct {
+ /// Key length in bytes.
+ pub const key_length = XSalsa20Poly1305.key_length;
+ /// Nonce length in bytes.
+ pub const nonce_length = XSalsa20Poly1305.nonce_length;
+ /// Authentication tag length in bytes.
+ pub const tag_length = XSalsa20Poly1305.tag_length;
+
+ /// Encrypt and authenticate `m` using a nonce `npub` and a key `k`.
+ /// `c` must be exactly `tag_length` longer than `m`, as it will store both the ciphertext and the authentication tag.
+ pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
+ debug.assert(c.len == tag_length + m.len);
+ XSalsa20Poly1305.encrypt(c[tag_length..], c[0..tag_length], m, "", npub, k);
+ }
+
+ /// Verify and decrypt `c` using a nonce `npub` and a key `k`.
+ /// `m` must be exactly `tag_length` smaller than `c`, as `c` includes an authentication tag in addition to the encrypted message.
+ pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void {
+ if (c.len < tag_length) {
+ return error.AuthenticationFailed;
+ }
+ debug.assert(m.len == c.len - tag_length);
+ return XSalsa20Poly1305.decrypt(m, c[tag_length..], c[0..tag_length].*, "", npub, k);
+ }
+};
+
+/// NaCl-compatible box API.
+///
+/// A secretbox contains both an encrypted message and an authentication tag to verify that it hasn't been tampered with.
+/// This construction uses public-key cryptography. A shared secret doesn't have to be known in advance by both parties.
+/// Instead, a message is encrypted using a sender's secret key and a recipient's public key,
+/// and is decrypted using the recipient's secret key and the sender's public key.
+///
+/// Nonces are 192-bit large and can safely be chosen with a random number generator.
+pub const box = struct {
+ /// Public key length in bytes.
+ pub const public_length = X25519.public_length;
+ /// Secret key length in bytes.
+ pub const secret_length = X25519.secret_length;
+ /// Shared key length in bytes.
+ pub const shared_length = XSalsa20Poly1305.key_length;
+ /// Seed (for key pair creation) length in bytes.
+ pub const seed_length = X25519.seed_length;
+ /// Nonce length in bytes.
+ pub const nonce_length = XSalsa20Poly1305.nonce_length;
+ /// Authentication tag length in bytes.
+ pub const tag_length = XSalsa20Poly1305.tag_length;
+
+ /// A key pair.
+ pub const KeyPair = X25519.KeyPair;
+
+ /// Compute a secret suitable for `secretbox` given a recipent's public key and a sender's secret key.
+ pub fn createSharedSecret(public_key: [public_length]u8, secret_key: [secret_length]u8) ![shared_length]u8 {
+ var p: [32]u8 = undefined;
+ try X25519.scalarmult(&p, secret_key, public_key);
+ const zero = [_]u8{0} ** 16;
+ return Salsa20Impl.hsalsa20(zero, p);
+ }
+
+ /// Encrypt and authenticate a message using a recipient's public key `public_key` and a sender's `secret_key`.
+ pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) !void {
+ const shared_key = try createSharedSecret(public_key, secret_key);
+ return secretBox.seal(c, m, npub, shared_key);
+ }
+
+ /// Verify and decrypt a message using a recipient's secret key `public_key` and a sender's `public_key`.
+ pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) !void {
+ const shared_key = try createSharedSecret(public_key, secret_key);
+ return secretBox.open(m, c, npub, shared_key);
+ }
+};
+
+/// libsodium-compatible sealed boxes
+///
+/// Sealed boxes are designed to anonymously send messages to a recipient given their public key.
+/// Only the recipient can decrypt these messages, using their private key.
+/// While the recipient can verify the integrity of the message, it cannot verify the identity of the sender.
+///
+/// A message is encrypted using an ephemeral key pair, whose secret part is destroyed right after the encryption process.
+pub const sealedBox = struct {
+ pub const public_length = box.public_length;
+ pub const secret_length = box.secret_length;
+ pub const seed_length = box.seed_length;
+ pub const seal_length = box.public_length + box.tag_length;
+
+ /// A key pair.
+ pub const KeyPair = box.KeyPair;
+
+ fn createNonce(pk1: [public_length]u8, pk2: [public_length]u8) [box.nonce_length]u8 {
+ var hasher = Blake2b(box.nonce_length * 8).init(.{});
+ hasher.update(&pk1);
+ hasher.update(&pk2);
+ var nonce: [box.nonce_length]u8 = undefined;
+ hasher.final(&nonce);
+ return nonce;
+ }
+
+ /// Encrypt a message `m` for a recipient whose public key is `public_key`.
+ /// `c` must be `seal_length` bytes larger than `m`, so that the required metadata can be added.
+ pub fn seal(c: []u8, m: []const u8, public_key: [public_length]u8) !void {
+ debug.assert(c.len == m.len + seal_length);
+ var ekp = try KeyPair.create(null);
+ const nonce = createNonce(ekp.public_key, public_key);
+ mem.copy(u8, c[0..public_length], ekp.public_key[0..]);
+ try box.seal(c[box.public_length..], m, nonce, public_key, ekp.secret_key);
+ mem.secureZero(u8, ekp.secret_key[0..]);
+ }
+
+ /// Decrypt a message using a key pair.
+ /// `m` must be exactly `seal_length` bytes smaller than `c`, as `c` also includes metadata.
+ pub fn open(m: []u8, c: []const u8, keypair: KeyPair) !void {
+ if (c.len < seal_length) {
+ return error.AuthenticationFailed;
+ }
+ const epk = c[0..public_length];
+ const nonce = createNonce(epk.*, keypair.public_key);
+ return box.open(m, c[public_length..], nonce, epk.*, keypair.secret_key);
+ }
+};
+
+test "xsalsa20poly1305" {
+ var msg: [100]u8 = undefined;
+ var msg2: [msg.len]u8 = undefined;
+ var c: [msg.len]u8 = undefined;
+ var key: [XSalsa20Poly1305.key_length]u8 = undefined;
+ var nonce: [XSalsa20Poly1305.nonce_length]u8 = undefined;
+ var tag: [XSalsa20Poly1305.tag_length]u8 = undefined;
+ try crypto.randomBytes(&msg);
+ try crypto.randomBytes(&key);
+ try crypto.randomBytes(&nonce);
+
+ XSalsa20Poly1305.encrypt(c[0..], &tag, msg[0..], "ad", nonce, key);
+ try XSalsa20Poly1305.decrypt(msg2[0..], c[0..], tag, "ad", nonce, key);
+}
+
+test "xsalsa20poly1305 secretbox" {
+ var msg: [100]u8 = undefined;
+ var msg2: [msg.len]u8 = undefined;
+ var key: [XSalsa20Poly1305.key_length]u8 = undefined;
+ var nonce: [box.nonce_length]u8 = undefined;
+ var boxed: [msg.len + box.tag_length]u8 = undefined;
+ try crypto.randomBytes(&msg);
+ try crypto.randomBytes(&key);
+ try crypto.randomBytes(&nonce);
+
+ secretBox.seal(boxed[0..], msg[0..], nonce, key);
+ try secretBox.open(msg2[0..], boxed[0..], nonce, key);
+}
+
+test "xsalsa20poly1305 box" {
+ var msg: [100]u8 = undefined;
+ var msg2: [msg.len]u8 = undefined;
+ var nonce: [box.nonce_length]u8 = undefined;
+ var boxed: [msg.len + box.tag_length]u8 = undefined;
+ try crypto.randomBytes(&msg);
+ try crypto.randomBytes(&nonce);
+
+ var kp1 = try box.KeyPair.create(null);
+ var kp2 = try box.KeyPair.create(null);
+ try box.seal(boxed[0..], msg[0..], nonce, kp1.public_key, kp2.secret_key);
+ try box.open(msg2[0..], boxed[0..], nonce, kp2.public_key, kp1.secret_key);
+}
+
+test "xsalsa20poly1305 sealedbox" {
+ var msg: [100]u8 = undefined;
+ var msg2: [msg.len]u8 = undefined;
+ var boxed: [msg.len + sealedBox.seal_length]u8 = undefined;
+ try crypto.randomBytes(&msg);
+
+ var kp = try box.KeyPair.create(null);
+ try sealedBox.seal(boxed[0..], msg[0..], kp.public_key);
+ try sealedBox.open(msg2[0..], boxed[0..], kp);
+}
lib/std/crypto.zig
@@ -6,15 +6,14 @@
/// Authenticated Encryption with Associated Data
pub const aead = struct {
- const chacha20 = @import("crypto/chacha20.zig");
-
pub const Gimli = @import("crypto/gimli.zig").Aead;
- pub const ChaCha20Poly1305 = chacha20.Chacha20Poly1305;
- pub const XChaCha20Poly1305 = chacha20.XChacha20Poly1305;
+ pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").Chacha20Poly1305;
+ pub const XChaCha20Poly1305 = @import("crypto/chacha20.zig").XChacha20Poly1305;
pub const Aegis128L = @import("crypto/aegis.zig").Aegis128L;
pub const Aegis256 = @import("crypto/aegis.zig").Aegis256;
pub const Aes128Gcm = @import("crypto/aes_gcm.zig").Aes128Gcm;
pub const Aes256Gcm = @import("crypto/aes_gcm.zig").Aes256Gcm;
+ pub const XSalsa20Poly1305 = @import("crypto/salsa20.zig").XSalsa20Poly1305;
};
/// Authentication (MAC) functions.
@@ -101,6 +100,15 @@ pub const stream = struct {
pub const ChaCha20IETF = @import("crypto/chacha20.zig").ChaCha20IETF;
pub const XChaCha20IETF = @import("crypto/chacha20.zig").XChaCha20IETF;
pub const ChaCha20With64BitNonce = @import("crypto/chacha20.zig").ChaCha20With64BitNonce;
+ pub const Salsa20 = @import("crypto/salsa20.zig").Salsa20;
+ pub const XSalsa20 = @import("crypto/salsa20.zig").XSalsa20;
+};
+
+pub const nacl = struct {
+ const salsa20 = @import("crypto/salsa20.zig");
+ pub const box = salsa20.box;
+ pub const secretBox = salsa20.secretBox;
+ pub const sealedBox = salsa20.sealedBox;
};
const std = @import("std.zig");
@@ -134,6 +142,7 @@ test "crypto" {
_ = @import("crypto/sha1.zig");
_ = @import("crypto/sha2.zig");
_ = @import("crypto/sha3.zig");
+ _ = @import("crypto/salsa20.zig");
_ = @import("crypto/siphash.zig");
_ = @import("crypto/25519/curve25519.zig");
_ = @import("crypto/25519/ed25519.zig");