# Parameters for the new encryption

**URL:** <https://discourse.joplinapp.org/t/parameters-for-the-new-encryption/38742>\
**Category:** Native Encryption\
**Created:** [24 June 2024 17:14 UTC](https://discourse.joplinapp.org/t/parameters-for-the-new-encryption/38742 "2024-06-24T17:14:55Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![wh201906](https://yyz2.discourse-cdn.com/flex028/user_avatar/discourse.joplinapp.org/wh201906/32/14835_2.png) [@wh201906](https://discourse.joplinapp.org/u/wh201906)\
**Post date:** [24 June 2024 17:14 UTC](https://discourse.joplinapp.org/t/parameters-for-the-new-encryption/38742/1 "2024-06-24T17:14:55Z")

</div>

# Current Implementation

According to the [technical specifications](https://joplinapp.org/help/dev/spec/e2ee/) and the code, When encryption is enabled, the following steps are performed:

1. User inputs password which is stored in the local SQLite database.
2. A random [256-byte](https://github.com/laurent22/joplin/blob/v3.0.1/packages/lib/services/e2ee/EncryptionService.ts#L216) [master key](https://github.com/laurent22/joplin/blob/v3.0.1/packages/lib/services/e2ee/EncryptionService.ts#L213-L232) (or [shorter](https://github.com/laurent22/joplin/blob/v3.0.1/packages/app-mobile/utils/shim-init-react.js#L40)) is generated.
3. The master key is encrypted using the [`AES-256-CCM`](https://github.com/laurent22/joplin/blob/v3.0.1/packages/lib/services/e2ee/EncryptionService.ts#L372-L386) encryption method. The encryption key for the master key is derived from the password using the `PBKDF2` function ([source1](https://github.com/laurent22/joplin/blob/v3.0.1/packages/lib/services/e2ee/EncryptionService.ts#L375), [source2](https://github.com/bitwiseshiftleft/sjcl/blob/1.0.8/core/convenience.js#L49)). You can find the encrypted master key in the `info.json` in your sync target.
4. Notes and resources are encrypted using the [`AES-128-CCM`](https://github.com/laurent22/joplin/blob/v3.0.1/packages/lib/services/e2ee/EncryptionService.ts#L295-L312) encryption method. The data is not directly encrypted by the master key. We use the master key and a random `salt` to generate the actual encryption key with `PBKDF2` function, just like how we generate the encryption key for encrypting master key.

## Notes:

1. The `PBKDF2` implementation in `sjcl` is slow. However, `sjcl` cached the key derivation output with the same parameters (password, salt, iteration count), so reusing the same derivation parameters for multiple times is faster than expected.
2. The `salt` of the data encryption key changes when Joplin restarts. This brings some resistance of reused nonce vulnerability because the generated data encryption key changes. However, we can set a maximum size threshold to reset the `salt` forcefully in order to increase the security.
3. The chunk size is [5k bytes](https://github.com/laurent22/joplin/blob/v3.0.1/packages/lib/services/e2ee/EncryptionService.ts#L71) due to the slowness of `sjcl`. Considering we could get a much higher encryption speed (base64 encoding/decoding is not included) by switching to native encryption library, we can set the chunk size to a higher value like 16k or 64k
4. The size overhead should be around 33.3% due to the base64 encoding. However, when checking the ciphertext on the sync target, I found the size overhead is more than 77.7%. It looks like the base64 encoding is performed twice. I'm now finding the reason of it.

# Future Considerations

## The key derivation function: `PBKDF2`

- `PBKDF2` is widely supported so I choose it. This is also what `sjcl` uses so the security of this part won't decrease. There are a few better key derivation functions like `scrypt` and `Argon2` but they are not available in `node:crypto`/`react-native-quick-crypto`
- Increase the key iteration count of encrypting master key from 10000 to 210000 (suggested by [OWASP](https://web.archive.org/web/20230123232056/https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2)). The native implementation of PBKDF2 is around 200 times faster than `sjcl`, with this key iteration count we can get higher security and faster speed.
- Change the salt length from 64 bit to 128 bit (or higher). 64 bit is the minimum requirement of `PBKDF2` and we'd like to be higher than the boundary. Also the 128 bit salt length is widely used now.
- Change the digest algorithm from `SHA-256` to `SHA-512` for longer key length. The extra part could be used in the future.
- `scrypt` might be a better choice once it's available in `react-native-quick-crypto`.

## The cipher and the mode: `AES-GCM`

- `AES-GCM` is widely supported so I choose it. There are a few better ciphers/modes but they are not available in `node:crypto`/`react-native-quick-crypto`.
- Change the key size from 128 bits to 256 bits. We've [tried it](https://github.com/laurent22/joplin/issues/7686) before but [reverted it](https://github.com/laurent22/joplin/issues/8657) due to the performance issues. Now with the powerful native implementation we are good to use it.
- Change the IV size to 12 bytes (96 bits). This is [recommended](https://eprint.iacr.org/2012/438.pdf) for the `AES-GCM` mode. We could use a longer IV but for `AES-GCM` it will be hashed to 96 bits finally. As for the potential nonce(IV) collision, we use the derived key from master key with a random salt to mitigate it.
  - The default IV size in `sjcl` is 16 bytes, but for the old `CCM` mode only the first 13 bytes are used. We use 12 bytes for `GCM` mode so it's easier to get collision (though in a small possibility) but as I noted before we will use a longer and frequently refreshed `salt` to mitigate this.

- Change the authentication tag size from 64 bits to 128 bits (suggested in [this paper](https://csrc.nist.gov/csrc/media/projects/block-cipher-techniques/documents/bcm/comments/cwc-gcm/ferguson2.pdf)).
- As far as I know the known defects about `AES-GCM` are only about leaking the authentication key, which doesn't matter for Joplin.

Please feel free to discuss the old/new encryption parameters.

---

<div class="post-metadata">

**Author:** ![personalizedrefriger](https://yyz2.discourse-cdn.com/flex028/user_avatar/discourse.joplinapp.org/personalizedrefriger/32/13210_2.png) [@personalizedrefriger](https://discourse.joplinapp.org/u/personalizedrefriger)\
**Post date:** [24 June 2024 22:23 UTC](https://discourse.joplinapp.org/t/parameters-for-the-new-encryption/38742/2 "2024-06-24T22:23:48Z")

</div>

[AES-GCM](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt#aes-gcm) and [PBKDF2](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey#pbkdf2) both seem to be supported by the web [crypto.subtle API](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey) and, as such, should also both be usable in the [web port of Joplin mobile](https://discourse.joplinapp.org/t/web-client-running-joplin-mobile-in-a-web-browser-with-react-native-web/38749).

**Edit** : It looks like `crypto.subtle` is also [supported natively in NodeJS](https://nodejs.org/api/webcrypto.html). Would it make sense to use this API also for the CLI and desktop apps?

---

<div class="post-metadata">

**Author:** ![wh201906](https://yyz2.discourse-cdn.com/flex028/user_avatar/discourse.joplinapp.org/wh201906/32/14835_2.png) [@wh201906](https://discourse.joplinapp.org/u/wh201906)\
**Post date:** [28 June 2024 11:45 UTC](https://discourse.joplinapp.org/t/parameters-for-the-new-encryption/38742/3 "2024-06-28T11:45:17Z")

</div>

Is there any reason to use `crypto.subtle` rather than `node:crypto`? I have tested the latter and it works fine.

---

<div class="post-metadata">

**Author:** ![personalizedrefriger](https://yyz2.discourse-cdn.com/flex028/user_avatar/discourse.joplinapp.org/personalizedrefriger/32/13210_2.png) [@personalizedrefriger](https://discourse.joplinapp.org/u/personalizedrefriger)\
**Post date:** [28 June 2024 13:43 UTC](https://discourse.joplinapp.org/t/parameters-for-the-new-encryption/38742/4 "2024-06-28T13:43:02Z")

</div>

Unlike `node:crypto`, `crypto.subtle` should work in a web browser. As such, `crypto.subtle` might allow using the same encryption/decryption logic on desktop, CLI, and web.

**Edit** : Additional notes:

- [crypto-browserify](https://github.com/browserify/crypto-browserify) might allow using `node:crypto` in the browser. However, its last release was [7 years ago](https://www.npmjs.com/package/crypto-browserify), so might not be well maintained.
- [A browser and NodeJS compatibility table for crypto.subtle](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto#browser_compatibility)
  - Firefox [might support PBKDF2](https://bugzilla.mozilla.org/show_bug.cgi?id=1856679), even though the compatibility table says otherwise.

---

<div class="post-metadata">

**Author:** ![laurent](https://avatars.discourse-cdn.com/v4/letter/l/ce7236/32.png) [@laurent](https://discourse.joplinapp.org/u/laurent)\
**Post date:** [28 June 2024 14:18 UTC](https://discourse.joplinapp.org/t/parameters-for-the-new-encryption/38742/5 "2024-06-28T14:18:18Z")

</div>

I'm not familiar with these various libraries, but one thing to note is that we don't have to use crypto.subtle for the native encryption - it's just that ideally we should check that whatever library configuration we use can be compatible with crypto.subtle.

It's the same situation we have now for RSA - we use two different libraries, one for mobile, one for desktop, but both can decrypt what the other library encrypts.

I would assume that as long as we use standard, widely used algorithms, there's a good chance they are supported by crypto.subtle too.

---

<div class="post-metadata">

**Author:** ![wh201906](https://yyz2.discourse-cdn.com/flex028/user_avatar/discourse.joplinapp.org/wh201906/32/14835_2.png) [@wh201906](https://discourse.joplinapp.org/u/wh201906)\
**Post date:** [30 June 2024 13:22 UTC](https://discourse.joplinapp.org/t/parameters-for-the-new-encryption/38742/6 "2024-06-30T13:22:59Z")

</div>

I think it's not a good idea to use `crypto-broweserify` because it's a pure JS implementation and could be very slow.

By the way, I wonder if the web support is on the roadmap of Joplin. If so, I will do some compatibility tests for the selected parameters on `node:crypto`/`react-native-quick-crypto`/`Web Crypto API`

---

<div class="post-metadata">

**Author:** ![wh201906](https://yyz2.discourse-cdn.com/flex028/user_avatar/discourse.joplinapp.org/wh201906/32/14835_2.png) [@wh201906](https://discourse.joplinapp.org/u/wh201906)\
**Post date:** [30 June 2024 13:50 UTC](https://discourse.joplinapp.org/t/parameters-for-the-new-encryption/38742/7 "2024-06-30T13:50:08Z")

</div>

# The source of the extra size overhead

The file content is actually base64 encoded twice during the encryption flow.

1. When reading the file data, the result is a base64 encoded string. ([source code](https://github.com/laurent22/joplin/blob/v3.0.1/packages/lib/services/e2ee/EncryptionService.ts#L544))
2. When encrypting, The `sjcl.json.encrypt()` will treat the plainText as a normal UTF-8 string rather than a bunch of base64 encoded data. After the encryption, the ciphertext is base64 encoded. ([source code](https://github.com/bitwiseshiftleft/sjcl/blob/1.0.8/core/convenience.js#L57-L58))

To reduce the unnecessary size overflow, we could convert the base64 string from Step 1 to the bitArray first, then encrypt it with `sjcl.json.encrypt()`. However, this introduces an extra step and slows down the encryption process.

---

<div class="post-metadata">

**Author:** ![personalizedrefriger](https://yyz2.discourse-cdn.com/flex028/user_avatar/discourse.joplinapp.org/personalizedrefriger/32/13210_2.png) [@personalizedrefriger](https://discourse.joplinapp.org/u/personalizedrefriger)\
**Post date:** [1 July 2024 00:02 UTC](https://discourse.joplinapp.org/t/parameters-for-the-new-encryption/38742/8 "2024-07-01T00:02:39Z")

</div>

> [@wh201906](#):
>
> By the way, I wonder if the web support is on the roadmap of Joplin.

I think so — [the proof of concept](https://discourse.joplinapp.org/t/web-client-running-joplin-mobile-in-a-web-browser-with-react-native-web/38749) is going well and seems to support most of what the mobile client does (though work is still needed to make it easier to use on a desktop computer).
