# Encryption Sessions

When you intend to encrypt many messages in a row, for example in an instant chat, you can use an Encryption Session. An Encryption Session uses the same key to encrypt all messages in the session.

The advantages are that your integration code can be simpler, that it decreases the number of network requests related to retrieving decryption keys, and that you decrease the total number of keys used (opens new window).

The downside is that you lose the granularity offered by encrypting each message independently: you cannot add / revoke a recipient on a single message of the session, but only on the session as a whole.

# Creating an encryption session

You can create a session with the function sealdSDK.createEncryptionSession.

Upon creation, two elements must be defined:

  • userIds : an array of user IDs in your application that will be allowed for this encryption session;
  • metadata : an optional argument declaring a non-encrypted metadata to the Seald API. It can be used in the administration of a Seald account.

The session created is of type EncryptionSession. It has a sessionId property, and several methods.

The person who creates a session is the administrator: only this user will be able to revoke the whole session.

TIP

If you are creating a session for a group of which the user is a member, you can use the optional argument encryptForSelf: false so that you do not encrypt directly for their own identities. The user will still be able to decrypt the data thanks to their group membership. This can allow you to improve the performance of the encryption, and save keys from your quota.

# Encrypting messages

With the EncryptionSession object, we can then encrypt messages, which will be readable by all recipients of the session.

This will result in a string of the form:

'{"sessionId":"0000000000000000000000","data":"8RwaOppCD3uIJVFv2LoP3XGXpomj0xsMv4qmMVy30Vdqor2w0+DVCXu3j13PEyN2KfJm6SiSrWDRMDziiiOUjQ=="}'

This contains the sessionId which is the identifier of the encryption session and data which is the encrypted data.

# Retrieving a session

Once a session has been created and a message encrypted, it must be possible to retrieve the session afterwards, including by the sender himself.

For this, there are two possibilities:

  • from the sessionId, but this requires storing it in a separate field in the application;
  • from an encrypted message with this session directly, which has the advantage of being simpler.

TIP

You can get the sessionId from the session object with its session.sessionId property.

In both cases, we use the sealdSDK.retrieveEncryptionSession method, either with the argument named sessionId if we know it, or with the argument named encryptedMessage:

# Decrypting messages

Once the session is retrieved, we can use it to decrypt a message:

# Add / revoke session recipients

Any recipient of the session can add other recipients to it, using the method session.addRecipients.

On the other hand, to revoke a recipient of the session with the session.revokeRecipients method, only the person who added this recipient (directly or indirectly), or the creator of the session, can do so.

Let's imagine a case where user1 added user2 into the session, and user2 added user3.

  • user3 cannot revoke anyone
  • user2 can revoke user3 (because he added him), but cannot revoke user1
  • user1 can revoke user2 (because he added him) and user3 (because he was added by someone added by him)

The session creator can also use the session.revoke method to revoke the entire session and all its content.

# Encrypt & decrypt files

An EncryptionSession also exposes two methods encryptFile and decryptFile for using this encryption session to encrypt files.

As explained in the dedicated file encryption guide, these methods support different types (string, Buffer, Blob in the browser only, ReadableStream of Web Streams (opens new window), and Readable of Node.JS Streams (opens new window)).

# Difference with SDK#encryptFile and SDK#decryptFile

By using an EncryptionSession to encrypt/decrypt files, one can reuse the session encryption key on different files/messages, and one manages the recipients at the encryption session level.

Thus, the encryptFile method of an EncryptionSession does not take the recipients argument, and only has the following arguments:

  • clearFile: same as SDK#encryptFile;
  • filename: in this case it is not used as the metadata, but only as the file name in the encrypted tar (see the format of an encrypted file);
  • options: the options object of which only the fileSize property is used, and is only necessary when clearFile is of type Readable or ReadableStream.

# Encryption

Here is an example of encryption (with the Buffer type):

# Decryption

To decrypt a file encrypted with a session, we must use the same EncryptionSession (previously retrieved with SDK#retrieveEncryptionSession):

One can also directly use SDK#decryptFile which will automatically retrieve the associated EncryptionSession (the EncryptionSession ID is always included in the result of a file encryption):

WARNING

Using the decryptFile method of an EncryptionSession on file encrypted with another session will not work.

TIP

For examples with other types, refer to the examples' section of the file encryption guide. You will just have to take into account the above-mentioned differences.

# Caching of encryption sessions

# Enabling Caching

To minimize network calls during decryption, you can cache encryption sessions.

In order to enable caching, you need to set a value to the encryptionSessionCacheTTL argument when initializing the SDK. This argument defines the duration, in milliseconds, during which this cache is kept valid (with the value -1 for no cache expiry). By default encryptionSessionCacheTTL is set at 0, which doesn't save the encryptions sessions in cache at all.

This cache will be automatically populated when creating or retrieving an encryption session, and will be automatically used when retrieving an encryption session. You can, when calling these functions, decide to change this behavior with the useCache: false argument.

# Cache cleanup

By default, the cache is cleaned up automatically, with an interval equal to the encryptionSessionCacheTTL, but with a minimum time of 10 seconds. The cleanup removes expired cache entries (older than encryptionSessionCacheTTL) from storage. This means that, in this default configuration and in the worst-case scenario, a cache entry could be stored for up to twice the duration of encryptionSessionCacheTTL.

You can also change this automatic cleanup interval, with the encryptionSessionCacheCleanupInterval argument. This argument defines the interval, in milliseconds, between two automatic cache cleanups (with the value -1 to disable automatic cleanup).

You can also manually run a cache cleanup, with the sdk.utils.cleanCache function:

# Customizing the cache storage

By default, this cache is only in memory, specific to each SDK instance. If you want a persistent cache, you can customize the cache storage method, with the argument createEncryptionSessionCache when initializing the SDK.

# Cache in the `localStorage

In the browser, the most likely use case is to want to use the localStorage in order to have a persistent cache between page loads and between tabs. The browser version of the SDK contains an implementation of createEncryptionSessionCache which stores data in localStorage, so you don't have to reimplement this common use case yourself. It is called createLocalStorageEncryptionSessionCache, and encrypts the cache entries with the same key as your database before storing them in the localStorage.

This createLocalStorageEncryptionSessionCache is available as a named import of the SDK, in its browser version only.

createLocalStorageEncryptionSessionCache is also available as a property of the SealdSDK constructor, in its browser version only.

# Manual cache

You can also make your own implementation of createEncryptionSessionCache.

This argument takes a function, which receives several arguments that may help you in your implementation. It must return an object that will serve as a cache, EncryptionSessionCache.

This object must have four methods:

  • set (id: string, value: { sessionSymKey: string, serializationDate: number }): void: takes an id and an object, and must store it.
  • get (id: string, dateOnly?: boolean): { sessionSymKey: string|null, serializationDate: number }: takes an id and must return any object previously stored with that id, or null. If the dateOnly argument is set to true, this function may only return serializationDate in the returned object, with sessionSymKey set to null.
  • keys(): Array<string>: must return the list of ids of currently stored entries.
  • delete(id: string): void: must delete the cache entry corresponding to the passed id.

All of these functions (createEncryptionSessionCache, cache.set, cache.get, cache.keys, and cache.delete) can also be asynchronous and return Promises returning these same objects, the null return values can also be undefined, and the void return values can also be a reference to the cache object.

TIP

You do not have to worry, in this implementation, about managing the cache validity time, or cleaning it up. This is handled internally by the SDK.

If you are implementing persistent storage, it is advisable to encrypt the stored data, to avoid keeping encryption keys on disk in cleartext. To facilitate this, the createEncryptionSessionCache function receives as argument an instantiated dbKey encryption key, derived from the databaseKey used to instantiate the SDK. Also, it is essential to separate the cache from possible different users on the same machine.

TIP

If you are using an encrypted cache, you can encrypt only the received sessionSymKey, not the serializationDate, so that you can return the serializationDate without needing to perform a decryption operation when calling cache.get with the dateOnly argument set to true. This will optimize cache cleanup operations.

Below, as an example, is the code for createLocalStorageEncryptionSessionCache, which is a full implementation of a custom cache that stores cache data encrypted in the browser's localStorage.