| author | |
| committer | |
| log | 0088efc4b22645698faf328369a1deca2dc9070f |
| tree | 69f2fa8dd0325331f4a622bef8f545a18379e354 |
| parent | bbd1e122d443dce8342a82a91f529fdec0007d46 |
| parent | 0c7a99b38d2fb881cde5125ef0729ff0427c06a6 |
| signature |
std/crypto: add (X)Salsa20 and NaCl boxes5 files changed, 553 insertions(+), 98 deletions(-)
lib/std/crypto.zig+13-4| ... | ... | @@ -6,15 +6,14 @@ |
| 6 | 6 | |
| 7 | 7 | /// Authenticated Encryption with Associated Data |
| 8 | 8 | pub const aead = struct { |
| 9 | const chacha20 = @import("crypto/chacha20.zig"); | |
| 10 | ||
| 11 | 9 | 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; | |
| 14 | 12 | pub const Aegis128L = @import("crypto/aegis.zig").Aegis128L; |
| 15 | 13 | pub const Aegis256 = @import("crypto/aegis.zig").Aegis256; |
| 16 | 14 | pub const Aes128Gcm = @import("crypto/aes_gcm.zig").Aes128Gcm; |
| 17 | 15 | pub const Aes256Gcm = @import("crypto/aes_gcm.zig").Aes256Gcm; |
| 16 | pub const XSalsa20Poly1305 = @import("crypto/salsa20.zig").XSalsa20Poly1305; | |
| 18 | 17 | }; |
| 19 | 18 | |
| 20 | 19 | /// Authentication (MAC) functions. |
| ... | ... | @@ -101,6 +100,15 @@ pub const stream = struct { |
| 101 | 100 | pub const ChaCha20IETF = @import("crypto/chacha20.zig").ChaCha20IETF; |
| 102 | 101 | pub const XChaCha20IETF = @import("crypto/chacha20.zig").XChaCha20IETF; |
| 103 | 102 | 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 | ||
| 107 | pub 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; | |
| 104 | 112 | }; |
| 105 | 113 | |
| 106 | 114 | const std = @import("std.zig"); |
| ... | ... | @@ -134,6 +142,7 @@ test "crypto" { |
| 134 | 142 | _ = @import("crypto/sha1.zig"); |
| 135 | 143 | _ = @import("crypto/sha2.zig"); |
| 136 | 144 | _ = @import("crypto/sha3.zig"); |
| 145 | _ = @import("crypto/salsa20.zig"); | |
| 137 | 146 | _ = @import("crypto/siphash.zig"); |
| 138 | 147 | _ = @import("crypto/25519/curve25519.zig"); |
| 139 | 148 | _ = @import("crypto/25519/ed25519.zig"); |
lib/std/crypto/25519/ed25519.zig+61-45| ... | ... | @@ -4,6 +4,8 @@ |
| 4 | 4 | // The MIT license requires this copyright notice to be included in all copies |
| 5 | 5 | // and substantial portions of the software. |
| 6 | 6 | const std = @import("std"); |
| 7 | const crypto = std.crypto; | |
| 8 | const debug = std.debug; | |
| 7 | 9 | const fmt = std.fmt; |
| 8 | 10 | const mem = std.mem; |
| 9 | 11 | const Sha512 = std.crypto.hash.sha2.Sha512; |
| ... | ... | @@ -14,8 +16,8 @@ pub const Ed25519 = struct { |
| 14 | 16 | pub const Curve = @import("edwards25519.zig").Edwards25519; |
| 15 | 17 | /// Length (in bytes) of a seed required to create a key pair. |
| 16 | 18 | pub const seed_length = 32; |
| 17 | /// Length (in bytes) of a compressed key pair. | |
| 18 | pub const keypair_length = 64; | |
| 19 | /// Length (in bytes) of a compressed secret key. | |
| 20 | pub const secret_length = 64; | |
| 19 | 21 | /// Length (in bytes) of a compressed public key. |
| 20 | 22 | pub const public_length = 32; |
| 21 | 23 | /// Length (in bytes) of a signature. |
| ... | ... | @@ -23,41 +25,61 @@ pub const Ed25519 = struct { |
| 23 | 25 | /// Length (in bytes) of optional random bytes, for non-deterministic signatures. |
| 24 | 26 | pub const noise_length = 32; |
| 25 | 27 | |
| 26 | /// Derive a key pair from a secret seed. | |
| 27 | /// | |
| 28 | /// As in RFC 8032, an Ed25519 public key is generated by hashing | |
| 29 | /// the secret key using the SHA-512 function, and interpreting the | |
| 30 | /// bit-swapped, clamped lower-half of the output as the secret scalar. | |
| 31 | /// | |
| 32 | /// For this reason, an EdDSA secret key is commonly called a seed, | |
| 33 | /// from which the actual secret is derived. | |
| 34 | pub fn createKeyPair(seed: [seed_length]u8) ![keypair_length]u8 { | |
| 35 | var az: [Sha512.digest_length]u8 = undefined; | |
| 36 | var h = Sha512.init(.{}); | |
| 37 | h.update(&seed); | |
| 38 | h.final(&az); | |
| 39 | const p = try Curve.basePoint.clampedMul(az[0..32].*); | |
| 40 | var keypair: [keypair_length]u8 = undefined; | |
| 41 | mem.copy(u8, &keypair, &seed); | |
| 42 | mem.copy(u8, keypair[seed_length..], &p.toBytes()); | |
| 43 | return keypair; | |
| 44 | } | |
| 28 | /// An Ed25519 key pair. | |
| 29 | pub const KeyPair = struct { | |
| 30 | /// Public part. | |
| 31 | public_key: [public_length]u8, | |
| 32 | /// Secret part. What we expose as a secret key is, under the hood, the concatenation of the seed and the public key. | |
| 33 | secret_key: [secret_length]u8, | |
| 45 | 34 | |
| 46 | /// Return the public key for a given key pair. | |
| 47 | pub fn publicKey(key_pair: [keypair_length]u8) [public_length]u8 { | |
| 48 | var public_key: [public_length]u8 = undefined; | |
| 49 | mem.copy(u8, public_key[0..], key_pair[seed_length..]); | |
| 50 | return public_key; | |
| 51 | } | |
| 35 | /// Derive a key pair from an optional secret seed. | |
| 36 | /// | |
| 37 | /// As in RFC 8032, an Ed25519 public key is generated by hashing | |
| 38 | /// the secret key using the SHA-512 function, and interpreting the | |
| 39 | /// bit-swapped, clamped lower-half of the output as the secret scalar. | |
| 40 | /// | |
| 41 | /// For this reason, an EdDSA secret key is commonly called a seed, | |
| 42 | /// from which the actual secret is derived. | |
| 43 | pub fn create(seed: ?[seed_length]u8) !KeyPair { | |
| 44 | const ss = seed orelse ss: { | |
| 45 | var random_seed: [seed_length]u8 = undefined; | |
| 46 | try crypto.randomBytes(&random_seed); | |
| 47 | break :ss random_seed; | |
| 48 | }; | |
| 49 | var az: [Sha512.digest_length]u8 = undefined; | |
| 50 | var h = Sha512.init(.{}); | |
| 51 | h.update(&ss); | |
| 52 | h.final(&az); | |
| 53 | const p = try Curve.basePoint.clampedMul(az[0..32].*); | |
| 54 | var sk: [secret_length]u8 = undefined; | |
| 55 | mem.copy(u8, &sk, &ss); | |
| 56 | const pk = p.toBytes(); | |
| 57 | mem.copy(u8, sk[seed_length..], &pk); | |
| 58 | ||
| 59 | return KeyPair{ .public_key = pk, .secret_key = sk }; | |
| 60 | } | |
| 61 | ||
| 62 | /// Create a KeyPair from a secret key. | |
| 63 | pub fn fromSecretKey(secret_key: [secret_length]u8) KeyPair { | |
| 64 | return KeyPair{ | |
| 65 | .secret_key = secret_key, | |
| 66 | .public_key = secret_key[seed_length..].*, | |
| 67 | }; | |
| 68 | } | |
| 69 | }; | |
| 52 | 70 | |
| 53 | 71 | /// Sign a message using a key pair, and optional random noise. |
| 54 | 72 | /// Having noise creates non-standard, non-deterministic signatures, |
| 55 | 73 | /// but has been proven to increase resilience against fault attacks. |
| 56 | pub fn sign(msg: []const u8, key_pair: [keypair_length]u8, noise: ?[noise_length]u8) ![signature_length]u8 { | |
| 57 | const public_key = key_pair[32..]; | |
| 74 | pub fn sign(msg: []const u8, key_pair: KeyPair, noise: ?[noise_length]u8) ![signature_length]u8 { | |
| 75 | const seed = key_pair.secret_key[0..seed_length]; | |
| 76 | const public_key = key_pair.secret_key[seed_length..]; | |
| 77 | if (!mem.eql(u8, public_key, &key_pair.public_key)) { | |
| 78 | return error.KeyMismatch; | |
| 79 | } | |
| 58 | 80 | var az: [Sha512.digest_length]u8 = undefined; |
| 59 | 81 | var h = Sha512.init(.{}); |
| 60 | h.update(key_pair[0..seed_length]); | |
| 82 | h.update(seed); | |
| 61 | 83 | h.final(&az); |
| 62 | 84 | |
| 63 | 85 | h = Sha512.init(.{}); |
| ... | ... | @@ -186,50 +208,44 @@ pub const Ed25519 = struct { |
| 186 | 208 | test "ed25519 key pair creation" { |
| 187 | 209 | var seed: [32]u8 = undefined; |
| 188 | 210 | try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); |
| 189 | const key_pair = try Ed25519.createKeyPair(seed); | |
| 211 | const key_pair = try Ed25519.KeyPair.create(seed); | |
| 190 | 212 | var buf: [256]u8 = undefined; |
| 191 | std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); | |
| 192 | ||
| 193 | const public_key = Ed25519.publicKey(key_pair); | |
| 194 | std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{public_key}), "2D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); | |
| 213 | std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair.secret_key}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); | |
| 214 | std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair.public_key}), "2D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); | |
| 195 | 215 | } |
| 196 | 216 | |
| 197 | 217 | test "ed25519 signature" { |
| 198 | 218 | var seed: [32]u8 = undefined; |
| 199 | 219 | try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); |
| 200 | const key_pair = try Ed25519.createKeyPair(seed); | |
| 220 | const key_pair = try Ed25519.KeyPair.create(seed); | |
| 201 | 221 | |
| 202 | 222 | const sig = try Ed25519.sign("test", key_pair, null); |
| 203 | 223 | var buf: [128]u8 = undefined; |
| 204 | 224 | std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{sig}), "10A442B4A80CC4225B154F43BEF28D2472CA80221951262EB8E0DF9091575E2687CC486E77263C3418C757522D54F84B0359236ABBBD4ACD20DC297FDCA66808"); |
| 205 | const public_key = Ed25519.publicKey(key_pair); | |
| 206 | try Ed25519.verify(sig, "test", public_key); | |
| 207 | std.testing.expectError(error.InvalidSignature, Ed25519.verify(sig, "TEST", public_key)); | |
| 225 | try Ed25519.verify(sig, "test", key_pair.public_key); | |
| 226 | std.testing.expectError(error.InvalidSignature, Ed25519.verify(sig, "TEST", key_pair.public_key)); | |
| 208 | 227 | } |
| 209 | 228 | |
| 210 | 229 | test "ed25519 batch verification" { |
| 211 | 230 | var i: usize = 0; |
| 212 | 231 | while (i < 100) : (i += 1) { |
| 213 | var seed: [32]u8 = undefined; | |
| 214 | try std.crypto.randomBytes(&seed); | |
| 215 | const key_pair = try Ed25519.createKeyPair(seed); | |
| 232 | const key_pair = try Ed25519.KeyPair.create(null); | |
| 216 | 233 | var msg1: [32]u8 = undefined; |
| 217 | 234 | var msg2: [32]u8 = undefined; |
| 218 | 235 | try std.crypto.randomBytes(&msg1); |
| 219 | 236 | try std.crypto.randomBytes(&msg2); |
| 220 | 237 | const sig1 = try Ed25519.sign(&msg1, key_pair, null); |
| 221 | 238 | const sig2 = try Ed25519.sign(&msg2, key_pair, null); |
| 222 | const public_key = Ed25519.publicKey(key_pair); | |
| 223 | 239 | var signature_batch = [_]Ed25519.BatchElement{ |
| 224 | 240 | Ed25519.BatchElement{ |
| 225 | 241 | .sig = sig1, |
| 226 | 242 | .msg = &msg1, |
| 227 | .public_key = public_key, | |
| 243 | .public_key = key_pair.public_key, | |
| 228 | 244 | }, |
| 229 | 245 | Ed25519.BatchElement{ |
| 230 | 246 | .sig = sig2, |
| 231 | 247 | .msg = &msg2, |
| 232 | .public_key = public_key, | |
| 248 | .public_key = key_pair.public_key, | |
| 233 | 249 | }, |
| 234 | 250 | }; |
| 235 | 251 | try Ed25519.verifyBatch(2, signature_batch); |
lib/std/crypto/25519/x25519.zig+39-32| ... | ... | @@ -4,6 +4,7 @@ |
| 4 | 4 | // The MIT license requires this copyright notice to be included in all copies |
| 5 | 5 | // and substantial portions of the software. |
| 6 | 6 | const std = @import("std"); |
| 7 | const crypto = std.crypto; | |
| 7 | 8 | const mem = std.mem; |
| 8 | 9 | const fmt = std.fmt; |
| 9 | 10 | |
| ... | ... | @@ -13,40 +14,46 @@ pub const X25519 = struct { |
| 13 | 14 | pub const Curve = @import("curve25519.zig").Curve25519; |
| 14 | 15 | /// Length (in bytes) of a secret key. |
| 15 | 16 | pub const secret_length = 32; |
| 17 | /// Length (in bytes) of a public key. | |
| 18 | pub const public_length = 32; | |
| 16 | 19 | /// 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 | }; | |
| 18 | 44 | |
| 19 | 45 | /// 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..]); | |
| 31 | 49 | } |
| 32 | 50 | |
| 33 | 51 | /// Compute the scalar product of a public key and a secret scalar. |
| 34 | 52 | /// Note that the output should not be used as a shared secret without |
| 35 | 53 | /// 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..]); | |
| 50 | 57 | } |
| 51 | 58 | }; |
| 52 | 59 | |
| ... | ... | @@ -56,7 +63,7 @@ test "x25519 public key calculation from secret key" { |
| 56 | 63 | var pk_calculated: [32]u8 = undefined; |
| 57 | 64 | try fmt.hexToBytes(sk[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); |
| 58 | 65 | try fmt.hexToBytes(pk_expected[0..], "f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50"); |
| 59 | std.testing.expect(X25519.createPublicKey(pk_calculated[0..], &sk)); | |
| 66 | try X25519.recoverPublicKey(&pk_calculated, sk); | |
| 60 | 67 | std.testing.expectEqual(pk_calculated, pk_expected); |
| 61 | 68 | } |
| 62 | 69 | |
| ... | ... | @@ -68,7 +75,7 @@ test "x25519 rfc7748 vector1" { |
| 68 | 75 | |
| 69 | 76 | var output: [32]u8 = undefined; |
| 70 | 77 | |
| 71 | std.testing.expect(X25519.create(output[0..], secret_key[0..], public_key[0..])); | |
| 78 | try X25519.scalarmult(&output, secret_key, public_key); | |
| 72 | 79 | std.testing.expectEqual(output, expected_output); |
| 73 | 80 | } |
| 74 | 81 | |
| ... | ... | @@ -80,7 +87,7 @@ test "x25519 rfc7748 vector2" { |
| 80 | 87 | |
| 81 | 88 | var output: [32]u8 = undefined; |
| 82 | 89 | |
| 83 | std.testing.expect(X25519.create(output[0..], secret_key[0..], public_key[0..])); | |
| 90 | try X25519.scalarmult(&output, secret_key, public_key); | |
| 84 | 91 | std.testing.expectEqual(output, expected_output); |
| 85 | 92 | } |
| 86 | 93 | |
| ... | ... | @@ -94,7 +101,7 @@ test "x25519 rfc7748 one iteration" { |
| 94 | 101 | var i: usize = 0; |
| 95 | 102 | while (i < 1) : (i += 1) { |
| 96 | 103 | var output: [32]u8 = undefined; |
| 97 | std.testing.expect(X25519.create(output[0..], &k, &u)); | |
| 104 | try X25519.scalarmult(output[0..], k, u); | |
| 98 | 105 | |
| 99 | 106 | mem.copy(u8, u[0..], k[0..]); |
| 100 | 107 | mem.copy(u8, k[0..], output[0..]); |
| ... | ... | @@ -118,7 +125,7 @@ test "x25519 rfc7748 1,000 iterations" { |
| 118 | 125 | var i: usize = 0; |
| 119 | 126 | while (i < 1000) : (i += 1) { |
| 120 | 127 | 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)); | |
| 122 | 129 | |
| 123 | 130 | mem.copy(u8, u[0..], k[0..]); |
| 124 | 131 | mem.copy(u8, k[0..], output[0..]); |
| ... | ... | @@ -141,7 +148,7 @@ test "x25519 rfc7748 1,000,000 iterations" { |
| 141 | 148 | var i: usize = 0; |
| 142 | 149 | while (i < 1000000) : (i += 1) { |
| 143 | 150 | 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)); | |
| 145 | 152 | |
| 146 | 153 | mem.copy(u8, u[0..], k[0..]); |
| 147 | 154 | mem.copy(u8, k[0..], output[0..]); |
lib/std/crypto/benchmark.zig+10-17| ... | ... | @@ -96,12 +96,12 @@ pub fn benchmarkMac(comptime Mac: anytype, comptime bytes: comptime_int) !u64 { |
| 96 | 96 | const exchanges = [_]Crypto{Crypto{ .ty = crypto.dh.X25519, .name = "x25519" }}; |
| 97 | 97 | |
| 98 | 98 | pub 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); | |
| 100 | 100 | |
| 101 | var in: [DhKeyExchange.key_length]u8 = undefined; | |
| 101 | var in: [DhKeyExchange.shared_length]u8 = undefined; | |
| 102 | 102 | prng.random.bytes(in[0..]); |
| 103 | 103 | |
| 104 | var out: [DhKeyExchange.key_length]u8 = undefined; | |
| 104 | var out: [DhKeyExchange.shared_length]u8 = undefined; | |
| 105 | 105 | prng.random.bytes(out[0..]); |
| 106 | 106 | |
| 107 | 107 | var timer = try Timer.start(); |
| ... | ... | @@ -109,7 +109,7 @@ pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_c |
| 109 | 109 | { |
| 110 | 110 | var i: usize = 0; |
| 111 | 111 | while (i < exchange_count) : (i += 1) { |
| 112 | _ = DhKeyExchange.create(out[0..], out[0..], in[0..]); | |
| 112 | try DhKeyExchange.scalarmult(&out, out, in); | |
| 113 | 113 | mem.doNotOptimizeAway(&out); |
| 114 | 114 | } |
| 115 | 115 | } |
| ... | ... | @@ -124,10 +124,8 @@ pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_c |
| 124 | 124 | const signatures = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" }}; |
| 125 | 125 | |
| 126 | 126 | pub fn benchmarkSignature(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 { |
| 127 | var seed: [Signature.seed_length]u8 = undefined; | |
| 128 | prng.random.bytes(seed[0..]); | |
| 129 | 127 | const msg = [_]u8{0} ** 64; |
| 130 | const key_pair = try Signature.createKeyPair(seed); | |
| 128 | const key_pair = try Signature.KeyPair.create(null); | |
| 131 | 129 | |
| 132 | 130 | var timer = try Timer.start(); |
| 133 | 131 | const start = timer.lap(); |
| ... | ... | @@ -149,11 +147,8 @@ pub fn benchmarkSignature(comptime Signature: anytype, comptime signatures_count |
| 149 | 147 | const signature_verifications = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" }}; |
| 150 | 148 | |
| 151 | 149 | pub fn benchmarkSignatureVerification(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 { |
| 152 | var seed: [Signature.seed_length]u8 = undefined; | |
| 153 | prng.random.bytes(seed[0..]); | |
| 154 | 150 | const msg = [_]u8{0} ** 64; |
| 155 | const key_pair = try Signature.createKeyPair(seed); | |
| 156 | const public_key = Signature.publicKey(key_pair); | |
| 151 | const key_pair = try Signature.KeyPair.create(null); | |
| 157 | 152 | const sig = try Signature.sign(&msg, key_pair, null); |
| 158 | 153 | |
| 159 | 154 | var timer = try Timer.start(); |
| ... | ... | @@ -161,7 +156,7 @@ pub fn benchmarkSignatureVerification(comptime Signature: anytype, comptime sign |
| 161 | 156 | { |
| 162 | 157 | var i: usize = 0; |
| 163 | 158 | while (i < signatures_count) : (i += 1) { |
| 164 | try Signature.verify(sig, &msg, public_key); | |
| 159 | try Signature.verify(sig, &msg, key_pair.public_key); | |
| 165 | 160 | mem.doNotOptimizeAway(&sig); |
| 166 | 161 | } |
| 167 | 162 | } |
| ... | ... | @@ -176,16 +171,13 @@ pub fn benchmarkSignatureVerification(comptime Signature: anytype, comptime sign |
| 176 | 171 | const batch_signature_verifications = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" }}; |
| 177 | 172 | |
| 178 | 173 | pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 { |
| 179 | var seed: [Signature.seed_length]u8 = undefined; | |
| 180 | prng.random.bytes(seed[0..]); | |
| 181 | 174 | const msg = [_]u8{0} ** 64; |
| 182 | const key_pair = try Signature.createKeyPair(seed); | |
| 183 | const public_key = Signature.publicKey(key_pair); | |
| 175 | const key_pair = try Signature.KeyPair.create(null); | |
| 184 | 176 | const sig = try Signature.sign(&msg, key_pair, null); |
| 185 | 177 | |
| 186 | 178 | var batch: [64]Signature.BatchElement = undefined; |
| 187 | 179 | for (batch) |*element| { |
| 188 | element.* = Signature.BatchElement{ .sig = sig, .msg = &msg, .public_key = public_key }; | |
| 180 | element.* = Signature.BatchElement{ .sig = sig, .msg = &msg, .public_key = key_pair.public_key }; | |
| 189 | 181 | } |
| 190 | 182 | |
| 191 | 183 | var timer = try Timer.start(); |
| ... | ... | @@ -208,6 +200,7 @@ pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime |
| 208 | 200 | const aeads = [_]Crypto{ |
| 209 | 201 | Crypto{ .ty = crypto.aead.ChaCha20Poly1305, .name = "chacha20Poly1305" }, |
| 210 | 202 | Crypto{ .ty = crypto.aead.XChaCha20Poly1305, .name = "xchacha20Poly1305" }, |
| 203 | Crypto{ .ty = crypto.aead.XSalsa20Poly1305, .name = "xsalsa20Poly1305" }, | |
| 211 | 204 | Crypto{ .ty = crypto.aead.Gimli, .name = "gimli-aead" }, |
| 212 | 205 | Crypto{ .ty = crypto.aead.Aegis128L, .name = "aegis-128l" }, |
| 213 | 206 | Crypto{ .ty = crypto.aead.Aegis256, .name = "aegis-256" }, |
lib/std/crypto/salsa20.zig created+430| ... | ... | @@ -0,0 +1,430 @@ |
| 1 | const std = @import("std"); | |
| 2 | const crypto = std.crypto; | |
| 3 | const debug = std.debug; | |
| 4 | const math = std.math; | |
| 5 | const mem = std.mem; | |
| 6 | ||
| 7 | const Poly1305 = crypto.onetimeauth.Poly1305; | |
| 8 | const Blake2b = crypto.hash.blake2.Blake2b; | |
| 9 | const X25519 = crypto.dh.X25519; | |
| 10 | ||
| 11 | const 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 | ||
| 135 | const Salsa20Impl = Salsa20NonVecImpl; | |
| 136 | ||
| 137 | fn 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 | ||
| 146 | fn 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. | |
| 154 | pub 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. | |
| 176 | pub 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 | |
| 192 | pub 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. | |
| 258 | pub 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. | |
| 292 | pub 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. | |
| 337 | pub 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 | ||
| 378 | test "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 | ||
| 393 | test "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 | ||
| 407 | test "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 | ||
| 421 | test "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 | } |