Skip to main content

ADVANCED: Configure what is encrypted

The Cardinal SDK uses encryption to better protect the privacy of your end-users, but encryption always comes at a cost. By default, the SDK encrypts the data in a way that should be a good compromise between security and usability of the data for most applications. However, the default behavior may not be ideal for some applications, which is why when initializing the SDK you can provide a configuration to specify which parts of the various entities should be encrypted.

In this page, we will first cover what you need to consider when choosing what to encrypt, and then we will explain the encrypted fields configuration syntax.

Choosing what to encrypt​

We recommend that you encrypt at least everything that could contain identifying information. In particular, you should encrypt data coming from free text fields and raw documents (such as pdfs and pictures), since these entries may contain the patient name or other similar information.

note

The Cardinal SDK automatically creates and encrypts links between entities when needed. For example, when creating medical data, the SDK will automatically create a link to the patient. You don't need to manually add the patient id to the medical data, and you don't need to explicitly request for the link to be encrypted.

You can always configure the SDK to encrypt more than the content potentially containing identifying information, however, you should consider that in some cases the additional encryption may have some negative impact on your application.

Impact on querying​

The biggest and most impactful downside of encryption in the Cardinal SDK is that encrypted data is not queryable. All the filtering options provided by the querying system work only if the properties used by the filter are readable by the backend: if you encrypt them the filter will not work.

The default configuration of the SDK leaves all properties used by filter options unencrypted, including some sensitive information such as the patients' addresses. However, if you don't need to use the filters that depend on these sensitive properties, you can configure the SDK to encrypt them.

Impact on sensitive data sharing​

In most applications, you want to use the data produced by the end users for some internal processes, such as compiling statistics or training AI models. Some of these processes may require access to sensitive data, but this is not always the case: for example, if you're only interested in the number of times each type of exam was performed in a hospital, you don't need to know the results of the exam or the patient that was examined.

If you leave the non-sensitive data unencrypted, you will be able to use it easily in your internal processes, and you will not need to do any form of sharing from the end-user application. However, if you decide to encrypt this data, the end-users will have to share with you the encryption keys of the entities they create. Aside from the increase in computational costs, this also means that you will also gain access to some sensitive data that you don't need.

For these reasons, we recommend that you leave any non-sensitive data you may need to access unencrypted.

Encrypted fields configuration​

You can configure which parts of entities need to be encrypted when you initialize the SDK by providing an instance of EncryptedFieldsConfiguration through the SdkOptions. For example, the following configuration customizes how patients and documents are encrypted, while all other entities use the default configuration.

import com.icure.cardinal.sdk.CardinalSdk
import com.icure.cardinal.sdk.auth.UsernamePassword
import com.icure.cardinal.sdk.options.AuthenticationMethod
import com.icure.cardinal.sdk.options.EncryptedFieldsConfiguration
import com.icure.cardinal.sdk.options.SdkOptions
import com.icure.cardinal.sdk.storage.impl.FileStorageFacade

suspend fun initializeMySdk(username: String, password: String) =
CardinalSdk.initialize(
"MyCardinalApp",
"https://api.icure.cloud",
AuthenticationMethod.UsingCredentials(UsernamePassword(username, password)),
FileStorageFacade("/path/to/storage/directory"),
SdkOptions(
encryptedFields = EncryptedFieldsConfiguration(
patient = setOf("notes", "addresses"),
document = setOf("name")
)
)
)
warning

If you're developing multiple applications connected to the same database, you should make sure that the encrypted fields configuration between the applications is aligned: if not, you may have inconsistent behaviors.

For example, you may have an application creating data with some encrypted sensitive content, and the other application modifying the data and leaving the sensitive content unencrypted.

Syntax​

The encrypted fields configuration acts at the level of the base Cardinal SDK data model's json representation. Your entities are converted to the base data model (if you aren't already using the base data model), then encoded to json and only then they're encrypted.

Therefore, when specifying the encrypted fields you will need to use the json names of the entities properties. The json names of properties, unless specified otherwise, are always the camel case representation of that property.

info

If you're using the kotlin or typescript SDK the model classes properties names are the same as the json names.

If you're using python you will need to convert the snake_case representation. For example if you want to encrypt the patient first_name you need to use firstName in the encrypted fields configuration.

The grammar for each encrypted field entry is the following:

fieldName :=
regex([a-zA-Z_][a-zA-Z0-9_]+)
encryptedField :=
fieldName
| fieldName + ("." | ".*." | "[].") + encryptedField

This grammar allows you to specify the fields to encrypt for the object and recursively for nested objects.

  • A string containing only a single fieldName will encrypt the field with the given name.
  • A string starting with fieldName. allows to specify the encrypted fields of a nested object. The encrypted values of the fields in the nested object will be saved in the nested object.
  • A string starting with fieldName.*. treats fieldName as a map/dictionary data structure and allows to specify the encrypted fields of map values. Note that the values of the map must be objects as well. The encrypted content of each map value is stored in that value.
  • A string starting with fieldName[]. treats fieldName as an array and allows to specify the encrypted fields for the array values. Note that the values of the array must be objects as well. The encrypted content of each array element is stored in that element.
warning

You can't encrypt the security and encrypted links metadata added automatically by the SDK.

Example​

Consider the following object

{
a: { x: 0, y: 1 },
b: "hello",
c: [ { public: "a", secret: "b" }, { public: "c", secret: "d" } ],
d: "ok",
e: {
info: "something",
private: "secret",
dataMap: {
"en": {
a: 1,
b: 2
},
"fr": {
a: 3,
b: 4
}
}
}
}

and encryption keys

[
"a",
"c[].secret",
"d",
"e.private",
"e.datamap.*.a"
]

The encrypted entity json will have the following form:

{
b: "hello",
c: [
{ public: "a", encryptedSelf: "encrypted+encoded({ secret: \"b\" })" },
{ public: "c", encryptedSelf: "encrypted+encoded({ secret: \"d\" })" }
],
e: {
info: "something",
dataMap: {
"en": { b: 2, encryptedSelf: "encrypted+encoded({ a: 1 })" },
"fr": { b: 4, encryptedSelf: "encrypted+encoded({ a: 3 })" }
},
encryptedSelf: "encrypted+encoded({ private: \"secret\" })"
},
encryptedSelf: "encrypted+encoded({ a: { x: 0, y: 1 }, d: \"ok\" })"
}

Shortened representation​

You can also group encrypted fields having the same prefix by concatenating to the prefix the JSON representation of an array of all the postfixes. For example, the following encrypted fields:

["a.b.c.d.e.f1", "a.b.c.d.e.f2", "a.b.c.d.e.f3", "a.b.c.d.e.f4"]

can be shortened to

["a.b.c.d.e.[\"f1\",\"f2\",\"f3\",\"f4\"]"]

Note that if you use the shortened representation you may need to escape nested json representations.

Contact, service, and service content encryption​

You can't customize the encryption of services through the contacts' encrypted fields configuration. Instead, the encrypted fields configuration has a dedicated entry for the configuration of services encryption. This allows more easily handling the potentially recursive nature of services (services can be recursively nested using the compoundValue in their content).

Additionally, you can't customize the encryption of services content. The content of services is always encrypted in the following way:

  • If the content contains an entry for the compound value and all other entries are missing, null, or empty, then the contained services will be recursively encrypted.
  • In all other cases, the content is fully encrypted and included in the encryptedSelf at the root of the service.

Changing encrypted fields configuration​

The encrypted fields configuration is used only when encrypting data (or validating encrypted data). When decrypting entities, the configuration is not needed.

This means that you can change the encrypted fields configuration without impacting the accessibility of existing data: the existing data won't be automatically updated to use the new encrypted fields configuration, but it will still be readable without issues.

However, if you update an entity that was encrypted using an older configuration, it will be re-encrypted using the current configuration. This may be a problem if some sensitive data was encrypted using the old configuration and is not using the new one. Make sure to never remove encrypted fields that could have sensitive information when changing your encrypted fields configuration.