I'm working on a system that shares data between a client and server. The end goal of this system is to store server-encrypted data on the client's hard drive, using keys generated by the server. the client needs copies of the keys so that they can decrypt the data when they're offline.
I am hoping to achieve some clarification on best practices and if I'm using the correct techniques.
The data is larger than the typical RSA Key size so using RSA to encrypt everything won't work. I've been using LibSodium's xChaCha-based encryption functions to share data.
For the key exchanges, I'm basically doing this:
client -> server: here's my publicKey
then
server -> client: here's my publicKey
then
client -> server: sends an encrypted message that was encrypted with the client's private key.
the server knows what message to expect when it is decrypted with client's public key.
This is how the server confirms that the client's public key is valid and can be used to encrypt/decrypt.
then
server -> client: sends an encrypted message that was encrypted with server's private key.
the Client knows what message to expect when it is decrypted with server's public key
This is how the client confirms that the server's public key is valid and can be used to encrypt/decrypt.
After RSA Key exchange between the client and the server has completed successfully, I exchange LibSodium KX keys (https://doc.libsodium.org/key_exchange) which can be used to generate TX/RX keys for the XChaCha encrypt/decrypt functions (https://doc.libsodium.org/secret-key_cryptography/aead/chacha20-poly1305/xchacha20-poly1305_construction):
These keys can be used by any functions requiring secret keys up to
crypto_kx_SESSIONKEYBYTES bytes, including crypto_secretbox_*() and crypto_aead_*().
Now both sides have generated RSA & KX key pairs and have each other's public keys (RSA and KX).
All further RSA-encrypted messages are encrypted using the recipient's public key, so that only the recipient can decrypt it with their private key.
I'm sending data between the server and client like this (pseudo-code/JSON-ish):
sender --> receiver:
{
"data": base64_encode( Encrypt(message = aReallyLongMessageOrLotsOfData,
key = TXKey,
algo = xChaCha20-poly1305,
additionalData,
nonce ) ),
"nonce": base64_encode( Encrypt(message = nonce,
key = receiversRSAPublicKey,
algo = RSA) )
}
In plain english:
- big chunks of data are encrypted with the TX key (which is derived from the sender's public/private keys + receiver's public key) using the xChaCha algorithm.
- the
nonce
for that xChaCha algorithm is encrypted with the recipient's RSA public key. - both sides know how to produce
additionalData
correctly, so they don't need to include it with the message like how thenonce
is being sent.
I have implemented all of this encrypted communication between my C++ desktop client and my web server (php) and it all works great.
Let me know if this is the correct way to send encrypted data in a client-server model, or if I'm missing some important detail that defeats all of this effort.
Thanks!!
crypto_box_easy
orcrypto_box_seal
? the former requires both parties to hold both public keys (and one secret), while the latter allows any sender to send a msg to the recipient without they themselves being able to decrypt once encrypted .. all of these are compatible inphp
and many other impls (including browser js), across multiple host osserver -> client
without the sender being able to decrypt later, and you also want a strong guarantee that it came from the sender, then you can have the sender sign the ciphertext as well usingcrypto_sign