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