A better idea is to do this:
# You kind of have to define this since most libraries don't have it
def classic_kem(pk):
[eph_sk, eph_pk] = classic_keygen()
d = classic_shared_secret(eph_sk, pk)
return hash(d + eph_pk + pk), eph_pk
# Two pieces ...
[ss1, ct1] = classic_kem(pk1)
[ss2, ct2] = postquantum_kem(pk2)
# ... combine into one:
# note: for some KEMs, ct1 and/or ct2 can safely be omitted
shared_secret = hash(ss1 + ss2 + ct1 + ct2)
ciphertext = symmetric_encrypt(plaintext, shared_secret, context)
send_to_other_party(
ct1,
ct2,
ciphertext
)
This sounds more complex, but I'm just filling in the details implied by your pseudocode and making it at least 2x as fast.On the opposite side, their code looks like this:
# I'm ignoring implicit vs explicit rejection for simplicity
def classic_kem_decaps(ct, sk, pk):
d = classic_shared_secret(ct, sk)
return hash(d + ct + pk)
ss1 = classic_kem_decaps(ct1, sk1, pk1)
ss2 = postquantum_kem_decaps(ct2, sk2, pk2)
shared_secret = hash(ss1, ss2, ct1, ct2)
# raises an exception on decrypt failure (e.g., invalid auth tag)
plaintext = symmetric_decrypt(ciphertext, shared_secret, context)
If you mean "doing two different KEMs and then securely combining them", then just say that. "Hybrid KEM" is short enough and distinct from other verbage."Encrypt" means something specific, not just the vague use of cryptography.