Storage request authorisation

In the proposed design for key management, to allow users to retrieve data from the remote storage, we assume the storage ids being associated with users in some way. Initially we didn’t plan how to authorize store requests from the users, which allows a trivial attack where an attacker just puts garbage in the slots associated with other users. The goal of this post is to describe a potential solution for this issue.

Our constraints

  1. The storage id must be associated with the user
  2. The association must be derived from the data the user has access to before they log in (so, passkey or Ethereum wallet)
  3. The user must authorize the store request
  4. Users cannot authorize storage requests that are not associated with their identity

The proposed solution

We derive a second deterministic keypair, storage authorization keypair (sask, sapk)[1]. We use the same mechanism as for key encryption key, but now add the domain separator to differentiate between key encryption key derivation and storage authorization key pair.

kek = HKDF(ikm, info, "key\ encryption\ key")[2]
sask = HKDF(ikm, info, "storage\ authorisation\ secret\ key")
sapk = [sask]*G

Note that this key pair is asymmetric (unlike key encryption key).

Frontend flow

1. store
  1. Derive the storage authorization key pair (sask, sapk) and key encryption key kek
  2. Encrypt the keyring with the key encryption key kek : ct = AES.Encrypt(kek, keyring)[3]
  3. Sign the ciphertext with the storage authorization private key: s = ECDSA.Sign(sask, ct)
  4. Send the store request: Backend.store(id = sapk, ciphertext = ct, signature = s)
1. retrieve
  1. Derive the storage authorization key pair (sask, sapk) and key encryption key kek
  2. Send the retrieve request: ct = Backend.retrieve(id = sapk)
  3. Decrypt keyring: keyring = AES.Decrypt(kek, ct)

Backend flow

1. store
  1. Receive (id, ciphertext, signature) from the user
  2. Verify the signature ECDSA.Verify(verifying_key = id, message = ciphertext, signature = signature)
  3. If the signature is valid, store ciphertext under id
1. retrieve
  1. Receive the request (id)
  2. Return ciphertext stored under id to the user [4]

@cdetroye @pedro @xuyang


  1. sask stands for storage authorization secret key and sapk stands for storage authorization public key ↩︎

  2. the domain separator can be different, can just use constant values or maybe it is a part of info. We just care that they derived differently ↩︎

  3. If the library function you call has a different signature (e.g., includes iv explicitly that I didn’t write here), please use the correct signature. This description is meant to capture the flow conceptually modulo appropriate function signatures ↩︎

  4. in the future we might want to consider authorisation for retrieve but hopefully in the future we won’t need this storage ↩︎

3 Likes

I think this looks good. If Pedro has an example of such keys I can finish the backend.

Just to be sure: the cipher for the id is the same cipher used for discovery payloads? This matters for the implementation on the backend to verify.

The cipher is the same (AES), the encrypted payload is different