From 84534bc3dc6da18cd5cfce0dae4e8511c543538d Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sun, 10 Nov 2024 17:42:53 +0100 Subject: [PATCH] std.crypto: make the key pair API creation consistent Our key pair creation API was ugly and inconsistent between ecdsa keys and other keys. The same `generate()` function can now be used to generate key pairs, and that function cannot fail. For deterministic keys, a `generateDeterministic()` function is available for all key types. Fix comments and compilation of the benchmark by the way. Fixes #21002 --- lib/std/crypto/25519/ed25519.zig | 46 ++++++++++++++++++++------------ lib/std/crypto/25519/x25519.zig | 30 ++++++++++++++------- lib/std/crypto/benchmark.zig | 14 +++++----- lib/std/crypto/ecdsa.zig | 33 ++++++++++++++--------- lib/std/crypto/ml_kem.zig | 29 ++++++++++++-------- lib/std/crypto/salsa20.zig | 8 +++--- lib/std/crypto/tls/Client.zig | 8 +++--- 7 files changed, 102 insertions(+), 66 deletions(-) diff --git a/lib/std/crypto/25519/ed25519.zig b/lib/std/crypto/25519/ed25519.zig index 3e5fb8bb3cb5..082650328e88 100644 --- a/lib/std/crypto/25519/ed25519.zig +++ b/lib/std/crypto/25519/ed25519.zig @@ -245,7 +245,9 @@ pub const Ed25519 = struct { /// Secret scalar. secret_key: SecretKey, - /// Derive a key pair from an optional secret seed. + /// Deterministically derive a key pair from a cryptograpically secure secret seed. + /// + /// Except in tests, applications should generally call `generate()` instead of this function. /// /// As in RFC 8032, an Ed25519 public key is generated by hashing /// the secret key using the SHA-512 function, and interpreting the @@ -253,20 +255,15 @@ pub const Ed25519 = struct { /// /// For this reason, an EdDSA secret key is commonly called a seed, /// from which the actual secret is derived. - pub fn create(seed: ?[seed_length]u8) IdentityElementError!KeyPair { - const ss = seed orelse ss: { - var random_seed: [seed_length]u8 = undefined; - crypto.random.bytes(&random_seed); - break :ss random_seed; - }; + pub fn generateDeterministic(seed: [seed_length]u8) IdentityElementError!KeyPair { var az: [Sha512.digest_length]u8 = undefined; var h = Sha512.init(.{}); - h.update(&ss); + h.update(&seed); h.final(&az); const pk_p = Curve.basePoint.clampedMul(az[0..32].*) catch return error.IdentityElement; const pk_bytes = pk_p.toBytes(); var sk_bytes: [SecretKey.encoded_length]u8 = undefined; - sk_bytes[0..ss.len].* = ss; + sk_bytes[0..seed_length].* = seed; sk_bytes[seed_length..].* = pk_bytes; return KeyPair{ .public_key = PublicKey.fromBytes(pk_bytes) catch unreachable, @@ -274,7 +271,22 @@ pub const Ed25519 = struct { }; } - /// Create a KeyPair from a secret key. + /// Generate a new, random key pair. + /// + /// `crypto.random.bytes` must be supported by the target. + pub fn generate() KeyPair { + var random_seed: [seed_length]u8 = undefined; + while (true) { + crypto.random.bytes(&random_seed); + return generateDeterministic(random_seed) catch { + @branchHint(.unlikely); + continue; + }; + } + } + + /// Create a key pair from an existing secret key. + /// /// Note that with EdDSA, storing the seed, and recovering the key pair /// from it is recommended over storing the entire secret key. /// The seed of an exiting key pair can be obtained with @@ -285,7 +297,7 @@ pub const Ed25519 = struct { // With runtime safety, we can still afford checking that the public key is correct. if (std.debug.runtime_safety) { const pk_p = try Curve.fromBytes(secret_key.publicKeyBytes()); - const recomputed_kp = try create(secret_key.seed()); + const recomputed_kp = try generateDeterministic(secret_key.seed()); debug.assert(mem.eql(u8, &recomputed_kp.public_key.toBytes(), &pk_p.toBytes())); } return KeyPair{ @@ -492,7 +504,7 @@ pub const Ed25519 = struct { test "key pair creation" { var seed: [32]u8 = undefined; _ = try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); - const key_pair = try Ed25519.KeyPair.create(seed); + const key_pair = try Ed25519.KeyPair.generateDeterministic(seed); var buf: [256]u8 = undefined; try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&key_pair.secret_key.toBytes())}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&key_pair.public_key.toBytes())}), "2D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); @@ -501,7 +513,7 @@ test "key pair creation" { test "signature" { var seed: [32]u8 = undefined; _ = try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); - const key_pair = try Ed25519.KeyPair.create(seed); + const key_pair = try Ed25519.KeyPair.generateDeterministic(seed); const sig = try key_pair.sign("test", null); var buf: [128]u8 = undefined; @@ -513,7 +525,7 @@ test "signature" { test "batch verification" { var i: usize = 0; while (i < 100) : (i += 1) { - const key_pair = try Ed25519.KeyPair.create(null); + const key_pair = Ed25519.KeyPair.generate(); var msg1: [32]u8 = undefined; var msg2: [32]u8 = undefined; crypto.random.bytes(&msg1); @@ -645,7 +657,7 @@ test "with blind keys" { const BlindKeyPair = Ed25519.key_blinding.BlindKeyPair; // Create a standard Ed25519 key pair - const kp = try Ed25519.KeyPair.create(null); + const kp = Ed25519.KeyPair.generate(); // Create a random blinding seed var blind: [32]u8 = undefined; @@ -665,7 +677,7 @@ test "with blind keys" { } test "signatures with streaming" { - const kp = try Ed25519.KeyPair.create(null); + const kp = Ed25519.KeyPair.generate(); var signer = try kp.signer(null); signer.update("mes"); @@ -681,7 +693,7 @@ test "signatures with streaming" { } test "key pair from secret key" { - const kp = try Ed25519.KeyPair.create(null); + const kp = Ed25519.KeyPair.generate(); const kp2 = try Ed25519.KeyPair.fromSecretKey(kp.secret_key); try std.testing.expectEqualSlices(u8, &kp.secret_key.toBytes(), &kp2.secret_key.toBytes()); try std.testing.expectEqualSlices(u8, &kp.public_key.toBytes(), &kp2.public_key.toBytes()); diff --git a/lib/std/crypto/25519/x25519.zig b/lib/std/crypto/25519/x25519.zig index 8bd5101b3756..ee40f34940f3 100644 --- a/lib/std/crypto/25519/x25519.zig +++ b/lib/std/crypto/25519/x25519.zig @@ -29,19 +29,29 @@ pub const X25519 = struct { /// Secret part. secret_key: [secret_length]u8, - /// Create a new key pair using an optional seed. - pub fn create(seed: ?[seed_length]u8) IdentityElementError!KeyPair { - const sk = seed orelse sk: { - var random_seed: [seed_length]u8 = undefined; - crypto.random.bytes(&random_seed); - break :sk random_seed; + /// Deterministically derive a key pair from a cryptograpically secure secret seed. + /// + /// Except in tests, applications should generally call `generate()` instead of this function. + pub fn generateDeterministic(seed: [seed_length]u8) IdentityElementError!KeyPair { + const kp = KeyPair{ + .public_key = try X25519.recoverPublicKey(seed), + .secret_key = seed, }; - var kp: KeyPair = undefined; - kp.secret_key = sk; - kp.public_key = try X25519.recoverPublicKey(sk); return kp; } + /// Generate a new, random key pair. + pub fn generate() KeyPair { + var random_seed: [seed_length]u8 = undefined; + while (true) { + crypto.random.bytes(&random_seed); + return generateDeterministic(random_seed) catch { + @branchHint(.unlikely); + continue; + }; + } + } + /// Create a key pair from an Ed25519 key pair pub fn fromEd25519(ed25519_key_pair: crypto.sign.Ed25519.KeyPair) (IdentityElementError || EncodingError)!KeyPair { const seed = ed25519_key_pair.secret_key.seed(); @@ -171,7 +181,7 @@ test "rfc7748 1,000,000 iterations" { } test "edwards25519 -> curve25519 map" { - const ed_kp = try crypto.sign.Ed25519.KeyPair.create([_]u8{0x42} ** 32); + const ed_kp = try crypto.sign.Ed25519.KeyPair.generateDeterministic([_]u8{0x42} ** 32); const mont_kp = try X25519.KeyPair.fromEd25519(ed_kp); try htest.assertEqual("90e7595fc89e52fdfddce9c6a43d74dbf6047025ee0462d2d172e8b6a2841d6e", &mont_kp.secret_key); try htest.assertEqual("cc4f2cdb695dd766f34118eb67b98652fed1d8bc49c330b119bbfa8a64989378", &mont_kp.public_key); diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index 3e979175d8c1..8bb651f73b12 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -140,7 +140,7 @@ const signatures = [_]Crypto{ pub fn benchmarkSignature(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 { const msg = [_]u8{0} ** 64; - const key_pair = try Signature.KeyPair.create(null); + const key_pair = Signature.KeyPair.generate(); var timer = try Timer.start(); const start = timer.lap(); @@ -163,7 +163,7 @@ const signature_verifications = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .na pub fn benchmarkSignatureVerification(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 { const msg = [_]u8{0} ** 64; - const key_pair = try Signature.KeyPair.create(null); + const key_pair = Signature.KeyPair.generate(); const sig = try key_pair.sign(&msg, null); var timer = try Timer.start(); @@ -187,7 +187,7 @@ const batch_signature_verifications = [_]Crypto{Crypto{ .ty = crypto.sign.Ed2551 pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 { const msg = [_]u8{0} ** 64; - const key_pair = try Signature.KeyPair.create(null); + const key_pair = Signature.KeyPair.generate(); const sig = try key_pair.sign(&msg, null); var batch: [64]Signature.BatchElement = undefined; @@ -219,7 +219,7 @@ const kems = [_]Crypto{ }; pub fn benchmarkKem(comptime Kem: anytype, comptime kems_count: comptime_int) !u64 { - const key_pair = try Kem.KeyPair.create(null); + const key_pair = Kem.KeyPair.generate(); var timer = try Timer.start(); const start = timer.lap(); @@ -239,7 +239,7 @@ pub fn benchmarkKem(comptime Kem: anytype, comptime kems_count: comptime_int) !u } pub fn benchmarkKemDecaps(comptime Kem: anytype, comptime kems_count: comptime_int) !u64 { - const key_pair = try Kem.KeyPair.create(null); + const key_pair = Kem.KeyPair.generate(); const e = key_pair.public_key.encaps(null); @@ -266,7 +266,7 @@ pub fn benchmarkKemKeyGen(comptime Kem: anytype, comptime kems_count: comptime_i { var i: usize = 0; while (i < kems_count) : (i += 1) { - const key_pair = try Kem.KeyPair.create(null); + const key_pair = Kem.KeyPair.generate(); mem.doNotOptimizeAway(&key_pair); } } @@ -409,7 +409,7 @@ fn benchmarkPwhash( comptime count: comptime_int, ) !f64 { const password = "testpass" ** 2; - const opts = .{ + const opts = ty.HashOptions{ .allocator = allocator, .params = @as(*const ty.Params, @ptrCast(@alignCast(params))).*, .encoding = .phc, diff --git a/lib/std/crypto/ecdsa.zig b/lib/std/crypto/ecdsa.zig index 649c96721836..96b348079822 100644 --- a/lib/std/crypto/ecdsa.zig +++ b/lib/std/crypto/ecdsa.zig @@ -296,21 +296,28 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type { /// Secret scalar. secret_key: SecretKey, - /// Create a new random key pair. `crypto.random.bytes` must be supported for the target. - pub fn generate() IdentityElementError!KeyPair { - var random_seed: [seed_length]u8 = undefined; - crypto.random.bytes(&random_seed); - return create(random_seed); - } - - /// Create a new key pair. The seed must be secret and indistinguishable from random. - pub fn create(seed: [seed_length]u8) IdentityElementError!KeyPair { + /// Deterministically derive a key pair from a cryptograpically secure secret seed. + /// + /// Except in tests, applications should generally call `generate()` instead of this function. + pub fn generateDeterministic(seed: [seed_length]u8) IdentityElementError!KeyPair { const h = [_]u8{0x00} ** Hash.digest_length; const k0 = [_]u8{0x01} ** SecretKey.encoded_length; const secret_key = deterministicScalar(h, k0, seed).toBytes(.big); return fromSecretKey(SecretKey{ .bytes = secret_key }); } + /// Generate a new, random key pair. + pub fn generate() KeyPair { + var random_seed: [seed_length]u8 = undefined; + while (true) { + crypto.random.bytes(&random_seed); + return generateDeterministic(random_seed) catch { + @branchHint(.unlikely); + continue; + }; + } + } + /// Return the public key corresponding to the secret key. pub fn fromSecretKey(secret_key: SecretKey) IdentityElementError!KeyPair { const public_key = try Curve.basePoint.mul(secret_key.bytes, .big); @@ -387,7 +394,7 @@ test "Basic operations over EcdsaP384Sha384" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; const Scheme = EcdsaP384Sha384; - const kp = try Scheme.KeyPair.generate(); + const kp = Scheme.KeyPair.generate(); const msg = "test"; var noise: [Scheme.noise_length]u8 = undefined; @@ -403,7 +410,7 @@ test "Basic operations over Secp256k1" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; const Scheme = EcdsaSecp256k1Sha256oSha256; - const kp = try Scheme.KeyPair.generate(); + const kp = Scheme.KeyPair.generate(); const msg = "test"; var noise: [Scheme.noise_length]u8 = undefined; @@ -419,7 +426,7 @@ test "Basic operations over EcdsaP384Sha256" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; const Scheme = Ecdsa(crypto.ecc.P384, crypto.hash.sha2.Sha256); - const kp = try Scheme.KeyPair.generate(); + const kp = Scheme.KeyPair.generate(); const msg = "test"; var noise: [Scheme.noise_length]u8 = undefined; @@ -893,7 +900,7 @@ test "Sec1 encoding/decoding" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; const Scheme = EcdsaP384Sha384; - const kp = try Scheme.KeyPair.generate(); + const kp = Scheme.KeyPair.generate(); const pk = kp.public_key; const pk_compressed_sec1 = pk.toCompressedSec1(); const pk_recovered1 = try Scheme.PublicKey.fromSec1(&pk_compressed_sec1); diff --git a/lib/std/crypto/ml_kem.zig b/lib/std/crypto/ml_kem.zig index 950b72016b43..99cb493b34ee 100644 --- a/lib/std/crypto/ml_kem.zig +++ b/lib/std/crypto/ml_kem.zig @@ -370,15 +370,10 @@ fn Kyber(comptime p: Params) type { secret_key: SecretKey, public_key: PublicKey, - /// Create a new key pair. - /// If seed is null, a random seed will be generated. - /// If a seed is provided, the key pair will be deterministic. - pub fn create(seed_: ?[seed_length]u8) !KeyPair { - const seed = seed_ orelse sk: { - var random_seed: [seed_length]u8 = undefined; - crypto.random.bytes(&random_seed); - break :sk random_seed; - }; + /// Deterministically derive a key pair from a cryptograpically secure secret seed. + /// + /// Except in tests, applications should generally call `generate()` instead of this function. + pub fn generateDeterministic(seed: [seed_length]u8) !KeyPair { var ret: KeyPair = undefined; ret.secret_key.z = seed[inner_seed_length..seed_length].*; @@ -399,6 +394,18 @@ fn Kyber(comptime p: Params) type { return ret; } + + /// Generate a new, random key pair. + pub fn generate() KeyPair { + var random_seed: [seed_length]u8 = undefined; + while (true) { + crypto.random.bytes(&random_seed); + return generateDeterministic(random_seed) catch { + @branchHint(.unlikely); + continue; + }; + } + } }; // Size of plaintexts of the in @@ -1698,7 +1705,7 @@ test "Test happy flow" { inline for (modes) |mode| { for (0..10) |i| { seed[0] = @as(u8, @intCast(i)); - const kp = try mode.KeyPair.create(seed); + const kp = try mode.KeyPair.generateDeterministic(seed); const sk = try mode.SecretKey.fromBytes(&kp.secret_key.toBytes()); try testing.expectEqual(sk, kp.secret_key); const pk = try mode.PublicKey.fromBytes(&kp.public_key.toBytes()); @@ -1745,7 +1752,7 @@ test "NIST KAT test" { g2.fill(kseed[0..32]); g2.fill(kseed[32..64]); g2.fill(&eseed); - const kp = try mode.KeyPair.create(kseed); + const kp = try mode.KeyPair.generateDeterministic(kseed); const e = kp.public_key.encaps(eseed); const ss2 = try kp.secret_key.decaps(&e.ciphertext); try testing.expectEqual(ss2, e.shared_secret); diff --git a/lib/std/crypto/salsa20.zig b/lib/std/crypto/salsa20.zig index d2ace6446f79..6adcd4a2443f 100644 --- a/lib/std/crypto/salsa20.zig +++ b/lib/std/crypto/salsa20.zig @@ -535,7 +535,7 @@ pub const SealedBox = struct { /// `c` must be `seal_length` bytes larger than `m`, so that the required metadata can be added. pub fn seal(c: []u8, m: []const u8, public_key: [public_length]u8) (WeakPublicKeyError || IdentityElementError)!void { debug.assert(c.len == m.len + seal_length); - var ekp = try KeyPair.create(null); + var ekp = KeyPair.generate(); const nonce = createNonce(ekp.public_key, public_key); c[0..public_length].* = ekp.public_key; try Box.seal(c[Box.public_length..], m, nonce, public_key, ekp.secret_key); @@ -607,8 +607,8 @@ test "xsalsa20poly1305 box" { crypto.random.bytes(&msg); crypto.random.bytes(&nonce); - const kp1 = try Box.KeyPair.create(null); - const kp2 = try Box.KeyPair.create(null); + const kp1 = Box.KeyPair.generate(); + const kp2 = Box.KeyPair.generate(); try Box.seal(boxed[0..], msg[0..], nonce, kp1.public_key, kp2.secret_key); try Box.open(msg2[0..], boxed[0..], nonce, kp2.public_key, kp1.secret_key); } @@ -619,7 +619,7 @@ test "xsalsa20poly1305 sealedbox" { var boxed: [msg.len + SealedBox.seal_length]u8 = undefined; crypto.random.bytes(&msg); - const kp = try Box.KeyPair.create(null); + const kp = Box.KeyPair.generate(); try SealedBox.seal(boxed[0..], msg[0..], kp.public_key); try SealedBox.open(msg2[0..], boxed[0..], kp); } diff --git a/lib/std/crypto/tls/Client.zig b/lib/std/crypto/tls/Client.zig index 20ea485049ed..9aff6d82c9d2 100644 --- a/lib/std/crypto/tls/Client.zig +++ b/lib/std/crypto/tls/Client.zig @@ -1649,10 +1649,10 @@ const KeyShare = struct { fn init(seed: [112]u8) error{IdentityElement}!KeyShare { return .{ - .ml_kem768_kp = try .create(null), - .secp256r1_kp = try .create(seed[0..32].*), - .secp384r1_kp = try .create(seed[32..80].*), - .x25519_kp = try .create(seed[80..112].*), + .ml_kem768_kp = .generate(), + .secp256r1_kp = try .generateDeterministic(seed[0..32].*), + .secp384r1_kp = try .generateDeterministic(seed[32..80].*), + .x25519_kp = try .generateDeterministic(seed[80..112].*), .sk_buf = undefined, .sk_len = 0, };