We want to wrap / encapsulate

  1. a symmetric key with an asymmetric keypair,
  2. a symmetric key with a symmetric key,
  3. a private key with a symmetric key.

In each case, we'd like to allow several keys to be wrapped / encapsulated with the same key (probably not needed in case 2, could be avoided in case 3). We also want to combine numbers 1 and 2; that is, wrap / encapsulate a key with both asymmetric keypair(s) and symmetric key(s).

Number 3 is the standard case for key encapsulation, while in cases 1 and 2 I believe key wrap is the preferred method.

We use Python, and pyNaCl ("Python binding to libsodium") seemed generally a decent library. I am open to other suggestions, though.

However, pyNaCl/libsodium does not seem to provide methods for key wrap or key encapsulation. I assume the latter to be used implicitly for "sealed boxes" (asymmetric encryption of large data). This does not suffice, because we want to encapsulate single keys for multiple asymmetric keypairs (and be able to add further keypairs later).

Should we attempt to build key encapsulation and key wrap ourselves based upon the given methods? If so, how?

  • Key encapsulation seems(!) easy (we'd presumably get an additional implicit key encapsulation, though, as mentioned above).
  • For key wrap, I am less sure what to do. Calculate a synthetic IV (SIV) to get AES-GCM-SIV?
    • I'd prefer a key wrap algorithm that uses libsodium's preferred AE cipher, SXalsa20/Poly1305.

We could probably also use key encapsulation even with symmetric key encryption keys.

Generally, I would prefer to avoid writing my own methods for key encapsulation and key wrap.

1 Answer 1

  • Use crypto_kdf() to derive two keys: one for authentication (let's call it kH) and one for encryption (let's call it kE).
  • Compute h=crypto_generichash(kH, key_to_be_wrapped) with a 24-byte output. This is gonna be both the encryption nonce and the authenticator.
  • Your wrapped key will be wk=crypto_stream_xor(kE, h, key_to_be_wrapped). Return the concatenation of h and wk.

To unwrap, compute Kh and Ke, then:

  • crypto_stream_xor(kE, h, wk) returns the wrapped secret.
  • Use it to recompute h, and if it doesn't match the one from the wrapped key, return an error

h can be truncated to 16 bytes, but in that case, add some padding when calling crypto_stream_xor().

  • This is the SIV construction from A Provable-Security Treatment of the Key-Wrap Problem, correct? That would require crypto_generichash() to be a PRF. Is it a PRF? Commented Dec 10, 2022 at 15:45
  • Am I correct in assuming the 24 bytes are crypto_stream_NONCEBYTES bytes? Am I further correct in assuming, that 16 bytes are to give the often desired 128 bits of securiy? Commented Dec 10, 2022 at 15:51
  • The unwrapping seems incorrect. The only h we can compoute prior to decrypting is the prefix of wk. So the comparison should be done after decryption, with the re-calculated hash, I think. (Like in the mentioned paper.) Commented Dec 10, 2022 at 15:58
  • Yes, crypto_generichash(), when a key is supplied, is a PRF. Commented Dec 11, 2022 at 13:17
  • The 24 bytes are indeed from crypto_stream_NONCEBYTES. Commented Dec 11, 2022 at 13:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .