# Identity management

After importing and instantiating the SDK, it will need to have an "identity" to perform other operations.

A user is represented by several "identities", usually one per device. These identities are created through the SDK. They can either be kept only in volatile memory, or in a persistent local database. They can also be saved onto our servers in a secure way to be able to reuse them later.

To save an identity on our servers securely, there are two ways to protect it:

For a manual management of the protection of an identity that would not be covered by these plugins, do not hesitate to contact us or use the manual export.

uml diagram

# Creating identities

A Seald user is represented by several "identities". These are actually private keys generated locally on the user's device with the SDK.

They are all linked together by a signature chain. For more details about our technology, please visit the dedicated page (opens new window).

We distinguish two cases:

  • a first identity ;
  • the next identities that are attached to it, called "sub-identities".

# First identity

To create an identity for a new user, use the sdk.initiateIdentity function.

This function requires a JSON Web Token. A dedicated guide will explain how to generate these license JWTs.

# Personalized identifier

It is possible to add a custom identifier, called userId in string format, via the use of a JWT. This can be done when creating an identity, or later using the sdk.pushJwt function.

An identity can have multiple identifiers. Adding a custom ID is done via the connector_add key defined in this documentation

# Sub-identities

From an existing identity of a user you can create a "sub-identity" using the function sdk.createSubIdentity on an SDK instance which has already been initialized.

This sub-identity will then be able to decrypt everything that the initial identity can decrypt: when another user encrypts something for that user, both the identity and the subidentity will be able to decrypt it.

This is typically used to have a separate sub-identity per device. For example, when the user logs in to a new device, we can retrieve a "main" identity protected by password or with 2-man rule, then create a new sub-identity linked to it for this device. It is then possible to store this sub-identity in localStorage (opens new window), and to retrieve it when opening a new page, in order to have persistent sessions accross tabs.

This function returns a Buffer (opens new window) similar to the one to the one created by sdk.exportIdentity and described in the manual export paragraph.


To accelerate the creation of sub-identities, you can pre-generate the private keys by calling the seald.preGenerateIdentityKeys() function in advance.


You can then import this Buffer (opens new window) into another instance of the SDK with the function sdk.importIdentity, or save it directly on SSKS with the functions sdk.ssks2MR.saveIdentity or sdk.ssksPassword.saveIdentity.


Having a very large number of sub-identities for the same user (several dozen) can slow down operations concerning this user, whether it is the creation of a new sub-identity or even simply the creation of an encryption session for them.

In case a user has a very large number of devices, or re-logs in very regularly, we advise you not to create a separate sub-identity for each connection, but simply use the identity stored on SSKS directly.

# Persistent local database

# How it works

By default, the Seald SDK only saves its database in volatile memory: if you create an identity, then close and re-open your browser tab, the created identity will not be accessible anymore and will be lost, except if you have protected it onto SSKS with a password or with two-man-rule. It this case, even if the user has an authenticated session, they will have to re-type their password, or re-authenticate with their email, in order to retrieve their Seald identity.

In order to have a better user experience, we can use a persistent local database, automatically stored securely in the browser by the Seald SDK, encrypted with a key stored on your backend called databaseKey.


The Seald database uses nedb (opens new window), which in the browser does not necessarily use the localStorage, but choses (through localForage (opens new window)) the best storage method between IndexedDB, WebSQL or localStorage depending on your browser.

uml diagram

When the user re-opens the application, the databaseKey is retrieved from the backend through an authenticated session, and this key will be used to decrypt the encryptedDB.

uml diagram

# Using a persistent local database

To use a persistent local database, you just need to add a databasePath argument to save the database, and a databaseKey argument to encrypt it, when initializing the SDK.

The server can either use a single databaseKey per user for all their devices, or it can use a different databaseKey per session. Using a different databaseKey per session is a bit more secure, but implies a more complicated data model. We will assume in the following a different databaseKey per session.

Here, databaseKey and sessionID must be generated and returned by the server.

We add sessionID in the databasePath in order for it to be unique for each session.

Indeed, if the same user logs-out, then logs back in, the server will have generated a new databaseKey. We therefore cannot use the same database: the database must be unique for each session, so we make databasePath change depending on the sessionID.


You can also replace databaseKey with databaseRawKey.

Note that databaseRawKey must be the Base64 string encoding of a cryptographically random buffer of 64 bytes.

Technically, this avoids deriving the databaseKey with scrypt, which improves the speed of instantiation.

If in doubt, use databaseKey instead.


Using a databaseRawKey:

Generating a suitable databaseRawKey:

Unlike the protection of a user's identity on SSKS, here the usage is the same whether you want to create a new identity and store it in a persistent database, or retrieve an existing identity from it.

You will find more details, especially on what you will have to implement on the backend side, on the example project's page about the persistent database.

# Password protection

Password protection of an identity is based on a derivation of a password known by the user into a symmetric key that is used to encrypt and decrypt the identity.


A "forgotten password" functionality cannot be implemented with this method without loss of data. If it is needed in your application, use the protection with 2-man-rule instead.

# How it works

This encrypted identity is then stored on a dedicated service named Seald SDK Key Storage or SSKS.

The password protection of an identity works as follows:

uml diagram

The retrieval of a password protected identity works as follows:

uml diagram

The @seald-io/sdk-plugin-ssks-password plugin allows to do this automatically.


More precisely, the user's password is derived twice :

  • once by combining it with the userId and the appId to give a "storage key" which is not secret ;
  • a second time by combining it with a random "salt", the userId and the appId to give an "encryption key" which is secret.

The identity is encrypted with the "encryption key". Then, are sent to SSKS:

  • the "storage key" ;
  • the "salt" concatenated with the encrypted identity.

The identity is retrieved by sending to SSKS the storage key which returns the salt concatenated with the encrypted identity. The salt is then used to obtain the encryption key which is then used to decrypt the identity.

# Save the identity

To use this storage method, you need to add the @seald-io/sdk-plugin-ssks-password plugin when instantiating the SDK, then after creating the identity, store it on SSKS using the sdk.ssksPassword.saveIdentity function.

The identity is now saved.


You can also store another sub-identity, instead of the identity of the current SDK instance:

# Retrieve the identity

To retrieve the identity at login, call the sdk.ssksPassword.retrieveIdentity function, with the same userId and password arguments as used in the save-identity.

The SDK instance is now ready to be used!


It is recommended not to retrieve the same identity with ssksPassword.retrieveIdentity on multiple devices at the same time, at the same exact instant, for example during automated tests. Please wait until one of the devices has finished retrieving the identity before starting the retrieval on another device.

# Change the password

When the user changes the password, you can use the sdk.ssksPassword.changeIdentityPassword function to have the identity stored with the new password and delete the one stored with the old one.

The password is now changed and only the new one can be used.


If the old password is not known (typically a forgotten password), this plugin does not allow to recover the identity. If the "forgotten password" functionality is needed in your application, use the 2-man-rule protection instead.


This operation does not change the keys of the identity itself, it only re-protects it with a new password. If the old password is considered breached, you must also create a new identity and revoke the old one.

# Password also used for authentication

In case you want to use the same password for identity protection as for authentication, it cannot be transmitted to the server as is, otherwise the server could then reconstruct the user's key, thus breaking the end-to-end encryption.

In this case, the login and account creation steps must be modified to introduce password pre-derivation using secure derivation functions so that your server never has access to the raw password, but only to a derived version.

You can find an example implementation here.


In case you can't change the authentication function (e.g. in case of federated authentication), then you should use a second password or another identity protection mode like 2-man rule.

# Customize the password derivation

As explained previously, the password provided by the user is derived into a "storage key", and an "encryption key". This derivation is done with scrypt.

For advanced use however, you can bypass this mechanism, and directly pass raw "storage key" and "encryption key" as arguments.

The storage key does not have to be secret from your server. It must however be unique, and secret from any other user, as it would allow the saved identity to be deleted. It must be a string, maximum 256 characters long, and composed of the following allowed characters: /A-Za-z0-9+\/=\-_@./.

For this, several strategies are possible:

  • you can use a random string generated by the server, and simply pass it to the front-end
  • you can also derive it from the password, potentially with another salt stored on the server

The encryption key, however, can only be known by the user in their frontend and must absolutely remain secret from any other actor, including your server. It must be the Base64 string encoding of a 64 bytes buffer. The usual way is to derive it from the password, if possible with a unique random salt generated and stored by your server.

Example of custom derivation of rawEncryptionKey with PBKDF2:

# Protection with 2-man-rule

The 2-man-rule protection mode consists of "splitting" the identity to be protected in two and placing one half on the application's back-end subject to authentication, and the other half on Seald SDK Key Storage (SSKS) subject to independent authentication.

The goal is that a user has "the right" to lose all their passwords and all their devices without losing any data. The trade-off is that this mode is not strictly "end-to-end".

It is necessary to implement on the server side the storage of a secret stored and delivered against authentication, as well as an interaction with SSKS.

# How it works

More specifically, to protect an identity, the protocol is as follows (in the normal case):

uml diagram


In the case where an identity has previously been stored onto SSKS with the same email address, the SSKS server will then demand an authentication with an email challenge, similarly to what is described in the following section about retrieving an identity.

For more details, you may refer to the guide about Integrating 2-man-rule on your backend.

To retrieve an identity, the protocol is as follows:

uml diagram

Thus, if the backend is breached, the encryptedIdentity stored on SSKS will not be breached, and the backend cannot simulate an authentication to SSKS.

Conversely, if SSKS is breached, the twoManRuleKey will not be breached and SSKS cannot simulate authentication to the backend.


This mode is not strictly end-to-end in the sense that the key to decrypt a user's data can be retrieved without the user's consent by combining twoManRuleKey on SSKS and encryptedIdentity on the backend.


Seald takes the following precautions to ensure the privacy of data stored on SSKS :

  • use of servers not subject to the Cloud Act (opens new window) (using OVH (opens new window) in France) so that the 2-man rule protection mode is robust when using host providers subject to the extra-territoriality of US law for the backend;
  • over-encryption at rest of sensitive fields (encryptedIdentity) ;
  • hashing and salting of the users' e-mail addresses to which the encryptedIdentity are associated, so that they are only known at runtime. Thus, a breach at rest of SSKS won't reveal to which e-mail address an encryptedIdentity is associated.

# Protection of an identity

To use this method of protection, you must install, import & add when instantiating the SDK the @seald-io/sdk-plugin-ssks-2mr plugin.

Your application server must then :

  • create a new user on SSKS by giving its email address ;
  • generate an SSKS session for this user;
  • generate a secret twoManRuleKey for this user (a sufficiently robust random string of characters, for example a UUID would be a good choice)
  • transmit the sessionId and the secret twoManRuleKey to the front-end which we will symbolize by API.getSSKSSession() in the example.

The user can then store their identity using the sdk.ssks2MR.saveIdentity function.

You can find the SSKS API documentation for your application server here.

The identity is now saved.


You can also store another sub-identity, instead of the identity of the current SDK instance:


The SSKS server cannot access the user's identity, because it does not have access to the secret key twoManRuleKey kept by your server. Conversely, your application server cannot access it either, because to retrieve an identity it would need a challenge sent by email or SMS, which it nerver receives and cannot forge.

# Retrieving an identity

Then, upon a new instantiation of the SDK, your backend must generate in the same way a session for the user, and pass the sessionId and the twoManRuleKey to the front-end. The SSKS server then sends an email containing a challenge (valid for 6h) to the user's email address.


In a test environment, your server can use the fake_otp: true argument during session generation, so that the challenge is not actually sent. It will then be set to 'aaaaaaaa'.

For more details, see the dedicated paragraph in the guide about the 2-man-rule.

Then you need to call the function sdk.ssks2MR.retrieveIdentity with the arguments userId, sessionId, email, challenge and twoManRuleKey.

Your SDK instance is now ready to be used!


It is recommended not to retrieve the same identity with ssks2MR.retrieveIdentity on multiple devices at the same time, at the same exact instant, for example during automated tests. Please wait until one of the devices has finished retrieving the identity before starting the retrieval on another device.

# Using a raw TwoManRule Key

When calling ssks2MR.saveIdentity or ssks2MR.retrieveIdentity, you can also replace twoManRuleKey with rawTwoManRuleKey.

Note that rawTwoManRuleKey must be the Base64 string encoding of a cryptographically random buffer of 64 bytes.

Technically, this avoids deriving the twoManRuleKey with scrypt, which improves the speed of execution.

If in doubt, use twoManRuleKey instead.

Using a rawTwoManRuleKey:

Generating a suitable rawTwoManRuleKey:

# Manual export

It is also possible to manage the storage of an identity manually.

For this, you can use the sdk.exportIdentity and sdk.importIdentity functions.

sdk.exportIdentity allows you to export a Buffer (opens new window) containing the current identity of the SDK instance.

Conversely, sdk.importIdentity imports an identity Buffer (opens new window) in the SDK instance in order to make it functional.


It is then up to you to ensure the security of these identity exports. This security is of course paramount to ensure the confidentiality of the encrypted data for this user.