You're storing potentially sensitive user data in the browser, client-side, and from time to time referencing the data. I would store the information using an authenticated symmetric cipher, which (depending on how your key management works) gives a strong guarantee of confidentiality as well as integrity. You use data by:
- sending the ciphertext to a separate (maybe 3rd-party) web service
- or, sending the ciphertext back to the application server as part of the http protocol exchange
- or, decrypting the ciphertext and reviewing the data in the client browser to make some sort of decision
In the first two scenarios, you never have the requirement to decrypt on the client-side - you're fundamentally relying on the client to store session data reliably. I'll assume you're not in the first scenario, as you would be making a service call to obtain the Plaid data (basically what you're trying to avoid!). I'll also assume the key-management strategy code can't be interfered with (... which is a loaded assumption if you plan to do decryption on the client side... more on this later).
I would propose that you create input key-material for each client also, for example, vested in the cookie. (A cookie could be a good choice because the user may have stricter rules around cookie destruction.) Another choice would be to store the session key material on the http server or in the data store. I wouldn't store it with the encrypted session data. The client provides the encrypted session data from local-storage, as well as any
input_key_material it holds, and the server would then derive key material as needed.
You would be using an authenticated symmetric cipher: Authenticated Encryption with Associated Data. Preferably something like X/ChaCha20Poly1305 or AES-GCM will be up to the task, however, you may need to consult any governance or legislative constraint (eg. some standards restrict your choice to AES-GCM and SHA2).
Generally speaking, this cipher follows the definition:
AE( key , nonce , plain_text , assoc_data ), where
plain_text is the data you want encrypted, and
assoc_data is additional clear-text data that won't be encrypted, but must be authenticated.
It is of critical importance that if
assoc_data changes then so too must the
nonce and/ or the
key. If the code results in
key + nonce re-use for two different
plain_text values, the
XOR of the two ciphertexts will be the same as the
XOR of the two plaintexts. This may lead to data leakage, particularly if the format is known.
A common way to guard against nonce re-use is to use a counter if the nonce is smaller than 16-bytes, or use a random value if it is 16-bytes or greater (TODO ref). This will depend on what cipher you use for
AE. In the case of the former, one solution I've used in the past is to produce a client-session-specific prefix of 8-bytes and then grab 4-bytes from the epoch time. The client holds
input_key_material including the 4-byte timer value, while the prefix (as well as the key) are computed on the server as needed, and disposed of immediately after use. The choice of the timer is a relatively safe one, because any adversary will capture packet timing anyway. (Once you've considered all this, you might ask separately about a potential solution.)
The fundamental problem here is delivering code and key to the client securely. If you search on this site, there's plenty of discussion weighing up pros and cons. There is a recent WebCrypto API that will limit you to AES-GCM and SHA2. You'll need a 'good way' to get key material to the browser.