Terms:
Client: Browser, mobile app or desktop application written using using JavaScript, HTML, CSS and packaged with Capacitor / Tauri.
Server: NodeJS based API.
Data: Username / password
Vendor: Third party integration
Background:
I am writing an integration with various vendors that requires users to enter their vendors data for our automated processes to log in and perform their task. For the benefit of the customers mindset on security, we're not storing the data on the server (encrypted or otherwise). The processing is done client side and in turn, the server doesn't need to have access to the data.
Problem:
I want to encrypt this data in such a way that it can only be decrypted when the user is logged in and a connection has been made to the server.
My solutions:
Idea 1: Key stored on server
Idea 2: Asymmetric Encryption
Question:
From my understanding, Idea 1 seems safe and preferable to me because the users data never leaves the device, even to be decrypted. Idea 2 seems safe but I can't think of any advantages over using the stored key approach in idea 1.
Is my understanding correct or should I be using some other method in order to safely encrypt this data?
The client will have full access to any unencrypted data before it is encrypted, or after the server permits the client to decrypt it.
This means that you will need to trust the client to not export the unencrypted data. It's impossible to fully lock the device down - someone could, for example, simply point a video camera at their monitor to "export" the unencrypted data. However, you can take steps to make it difficult for the user to install their own extraction software on the device.
All you need to do is store a per-vendor secret key on the server, keep a different per-vendor secret key known only to the client, and combine them both to get the actual encryption/decryption key. You can simply XOR them both together to derive the key.
Now, when it's time to access the data, the client logs in to the server and asks for a copy of the server's key, and decrypts the content by combining the server's key with the client's key.
If you want to be more fine grained, you can have a client and server key per unit of data that is stored. Therefore, the server can log access to different units of data for a particular vendor, and so can limit the rate at which the client requests access. Now, the client will have to request multiple keys from the server according to the data that needs accessing. The client can use HKDF to generate many per-unit client keys, but the server keys should be independently generated so that the client has to ask for each server key for each unit that needs to be decrypted.
Since the client has to be trusted, the client can then immediately discard the server's key and the derived encryption/decryption key as soon as it has finished decrypting/re-encrypting the modified vendor data. You'd probably make the client software automatically time out after a certain period, and require the client to log back in to the server to continue working on the vendor data.
Note that this is effectively a secret-sharing approach, which can be extended to require assistance from multiple parties. You can use Shamir's Secret Sharing to require a threshold of parties to grant access to the data on the client.
External links referenced by this document: