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
- The storage id must be associated with the user
- The association must be derived from the data the user has access to before they log in (so, passkey or Ethereum wallet)
- The user must authorize the store request
- 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
- Derive the storage authorization key pair (sask, sapk) and key encryption key kek
- Encrypt the keyring with the key encryption key
kek : ct = AES.Encrypt(kek, keyring)[3] - Sign the ciphertext with the storage authorization private key:
s = ECDSA.Sign(sask, ct) - Send the store request:
Backend.store(id = sapk, ciphertext = ct, signature = s)
1. retrieve
- Derive the storage authorization key pair (sask, sapk) and key encryption key kek
- Send the retrieve request:
ct = Backend.retrieve(id = sapk) - Decrypt keyring:
keyring = AES.Decrypt(kek, ct)
Backend flow
1. store
- Receive
(id, ciphertext, signature)from the user - Verify the signature
ECDSA.Verify(verifying_key = id, message = ciphertext, signature = signature) - If the signature is valid, store
ciphertextunderid
1. retrieve
- Receive the request
(id) - Return
ciphertextstored underidto the user [4]
sask stands for storage authorization secret key and sapk stands for storage authorization public key ↩︎
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 ↩︎
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 ↩︎
in the future we might want to consider authorisation for retrieve but hopefully in the future we won’t need this storage ↩︎