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
noncefor that xChaCha algorithm is encrypted with the recipient's RSA public key. - both sides know how to produce
additionalDatacorrectly, so they don't need to include it with the message like how thenonceis 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_easyorcrypto_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 inphpand many other impls (including browser js), across multiple host osserver -> clientwithout 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