When integrating Tanker into an existing application, it may be needed to encrypt all the clear data that is currently present.

To do this migration it is possible to set up a server to encrypt all the data in background, share the decryption keys with the intended recipients, and discard them immediately afterwards.

We offer SDKs in various languages that run server-side. If your platform is not supported, please contact us at contact@tanker.io.

Preparation

Integration prerequisites

Tanker must have been integrated and deployed to your users. Since this operation will encrypt data, all users who don't have the last version of your app with Tanker will not be able to decrypt the data.

Pre-registration sharing must be implemented. Some of your app's users will not have a Tanker account yet, but you will need to encrypt and share data with them. Sharing data with these users can only be done through pre-registration sharing.

This migration is destructive, so it is recommended that you have a backup of your database before you start it. Do not forget to delete it after you have confirmed that the migration was successful.

Preliminary steps

Until the process is finished, your database will contain both encrypted and clear data. For that transition time, you will need to add a boolean flag for each piece of data that specifies whether it is in clear, or encrypted. This flag will help clients know when to call decrypt() on the data. Also, all of the new data pushed by users during the migration will need to be flagged as encrypted.

As this operation will encrypt data and share decryption keys, it must be clear beforehand which users should have access to which pieces of data. For example, you might want to encrypt and share a document with their owner, and all the people they shared it with. It might be necessary here to create a group, and share the document with the group instead of the individual users. Beware to save the group ID appropriately so that your application can reuse it, as if it had been created by the application itself in the first place.

We recommend to run this migration in batches make recovery easier if the process fails in the middle. It is possible to run one kind of migration at a time, e.g. encrypt all users' chat messages, and later encrypt all the attached documents.

Opening a session

Since the decryption keys are meant to be discarded by the migration server, the identity used to open the session should be discarded too. You can import both the identity SDK and the Tanker SDK into your server and generate identities there.

To create a disposable identity for your server, just create an identity with a random user ID every time your server starts. Do not persist that identity to your database.

// We use uuid but you can use another source of random
import { v4 as uuidv4 } from 'uuid';
import { createIdentity } from '@tanker/identity';

const disposableIdentity = createIdentity(appId, appSecret, 'disposable-' + uuidv4());

require 'securerandom'
require 'tanker/identity'

disposable_identity = Tanker::Identity::create_identity(
  app_id,
  app_secret,
  "disposable-#{SecureRandom.uuid}",
)

Then you can use that identity and open a session. Use the verification key method, and discard it after the session is open to really make the session not reopenable.

const tanker = new Tanker({ appId: 'tanker-app-id' });
const status = await tanker.start(disposableIdentity);
// discard disposableIdentity
// status will be IDENTITY_REGISTRATION_NEEDED because the identity is always a new one
const verificationKey = await tanker.generateVerificationKey();
await tanker.registerIdentity({ verificationKey });
// discard verificationKey

require 'tanker/core'

config = Tanker::Core::Options.new(app_id: 'tanker-app-id', writable_path: './tanker_persistent_data')
tanker = Tanker::Core.new(config)
status = tanker.start(disposable_identity)
# discard disposable_identity
# status will be IDENTITY_REGISTRATION_NEEDED because the identity is always a new one
verification_key = tanker.generate_verification_key
tanker.register_identity(Tanker::VerificationKeyVerification.new(verification_key))
# discard verification_key

Encrypting data

Now your session is open and you can start encrypting your data.

Here is an example on how we could retrieve batches of records from a database, encrypt them, share them, and store them back:

const rows = await sql.query('SELECT * FROM records WHERE encrypted = false LIMIT 1000');
for (row of rows) {
  const clearData = row.contents;
  const identity = await app.getUserPublicIdentity(row.author_id);
  const encryptionOptions = { shareWithUsers: [identity], shareWithSelf: false };
  const encryptedData = await tanker.encrypt(clearData, encryptionOptions);
  row.contents = encryptedData;
  row.encrypted = true;
}
await sql.update('records', rows);

rows = sql.query('SELECT * FROM records WHERE encrypted = false LIMIT 1000')
rows.each do |row|
  clear_data = row.contents
  identity = app.get_user_public_identity(row.author_id)
  encryption_options = Tanker::EncryptionOptions.new(
      share_with_users: [identity],
      share_with_self: false,
  )
  encrypted_data = tanker.encrypt_utf8(clear_data, encryption_options)
  row.contents = encrypted_data
  row.encrypted = true
end
sql.update('records', rows)

The shareWithSelf argument will ensure that the key is discarded after encryption and only the recipients will be able to decrypt the data. It is important to set it to false to prevent the SDK from persisting the encryption keys on your device and from sending an additional encrypted key to the Tanker server.

Warning

It is important to share the data with the correct users. Failure to do so will not be recoverable unless a user who has the key connects.

Alternative solution

Instead of running a server in the background to encrypt all the data, it is possible to do that operation client-side. This strategy has several downsides:

  • There may be users that will not reconnect to your app before a long time, if ever. This means that if a data leak occurs during that time, or one of your company's employees accesses your database, they will be able to see clear user data.
  • Users may only have limited connectivity and processing power (on a smartphone) and such operations may consume all their data plan and battery if the amount of data to encrypt is considerable.
  • Encrypting existing data in a distributed manner involves more work to handle conditions like network failure, or the fact that multiple users might connect and try to encrypt the same piece of data.

For these reasons, we recommend the server approach described above.