deriveHybridSecretKey method

Future<SecretKeyData> deriveHybridSecretKey({
  1. required SecretKey postQuantumSecret,
  2. required Uint8List info,
  3. Uint8List? salt,
  4. PqHybridProfile profile = PqHybridProfile.balanced,
  5. int length = PqForgeCombiner.defaultLength,
})

Derives a hybrid session key from this classical secret and a post-quantum secret.

final session = await classicalSecret.deriveHybridSecretKey(
  postQuantumSecret: mlKemSecret,
  info: utf8.encode('myapp/session/v1') as Uint8List,
  profile: PqHybridProfile.heavy,
);
final cipher = AesGcm.with256bits();
final box = await cipher.encrypt(message, secretKey: session);

See PqForgeCombiner.combine for the meaning of info, salt, and length. info is mandatory for domain separation.

Memory hygiene: the secret bytes extracted from both keys are copied into private buffers and zeroized in a finally once derivation completes. The two input crypto.SecretKeys are owned by the caller and are left intact.

Implementation

Future<crypto.SecretKeyData> deriveHybridSecretKey({
  required crypto.SecretKey postQuantumSecret,
  required Uint8List info,
  Uint8List? salt,
  PqHybridProfile profile = PqHybridProfile.balanced,
  int length = PqForgeCombiner.defaultLength,
}) async {
  // `extractBytes()` may hand back a key's internal storage, so copy into
  // owned buffers before any in-place wiping.
  final classicalSharedSecret = Uint8List.fromList(await extractBytes());
  final postQuantumSharedSecret = Uint8List.fromList(
    await postQuantumSecret.extractBytes(),
  );

  try {
    final sessionKey = PqForgeCombiner(profile: profile).combine(
      classicalSharedSecret: classicalSharedSecret,
      postQuantumSharedSecret: postQuantumSharedSecret,
      info: info,
      salt: salt,
      length: length,
    );
    return crypto.SecretKeyData(sessionKey);
  } finally {
    PqForgeCombiner.wipe(classicalSharedSecret);
    PqForgeCombiner.wipe(postQuantumSharedSecret);
  }
}