Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

std.crypto: make the key pair API creation consistent #21955

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 29 additions & 17 deletions lib/std/crypto/25519/ed25519.zig
Original file line number Diff line number Diff line change
Expand Up @@ -245,36 +245,48 @@ 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
/// bit-swapped, clamped lower-half of the output as the secret scalar.
///
/// 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,
.secret_key = try SecretKey.fromBytes(sk_bytes),
};
}

/// 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
Expand All @@ -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{
Expand Down Expand Up @@ -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");
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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");
Expand All @@ -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());
Expand Down
30 changes: 20 additions & 10 deletions lib/std/crypto/25519/x25519.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down
14 changes: 7 additions & 7 deletions lib/std/crypto/benchmark.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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);

Expand All @@ -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);
}
}
Expand Down Expand Up @@ -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,
Expand Down
33 changes: 20 additions & 13 deletions lib/std/crypto/ecdsa.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
29 changes: 18 additions & 11 deletions lib/std/crypto/ml_kem.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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].*;

Expand All @@ -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
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions lib/std/crypto/salsa20.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand Down
Loading
Loading