Skip to content

Commit

Permalink
Website: Fix compatibility issues of Mermaid graphs (laurent22#11809)
Browse files Browse the repository at this point in the history
  • Loading branch information
wh201906 authored Feb 9, 2025
1 parent be18655 commit 0b287d1
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 54 deletions.
1 change: 1 addition & 0 deletions packages/tools/cspell/dictionary4.txt
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,4 @@ keyshortcuts
atag
HMAC
Siri
cipherdecipher
116 changes: 62 additions & 54 deletions readme/dev/spec/e2ee/native_encryption.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Native Encryption Method Specification

Starting with Joplin v3.2, we introduced a series of new encryption methods. These methods are built on native libraries and are designed to enhance both performance and security. This document provides an overview and detailed specifications of these new encryption methods.
Starting with Joplin v3.2, we introduced a series of new encryption methods. These methods are built on native libraries and are designed to enhance both performance and security. This document provides an overview and detailed specifications of these new encryption methods.

**Related Links:**
[GitHub Pull Request](https://github.com/laurent22/joplin/pull/10696)
[GSoC Final Report](https://discourse.joplinapp.org/t/final-report-of-the-native-encryption-project/40171)
**Related Links:**</br>
[GitHub Pull Request](https://github.com/laurent22/joplin/pull/10696)</br>
[GSoC Final Report](https://discourse.joplinapp.org/t/final-report-of-the-native-encryption-project/40171)</br>

## 1. General Steps for Encryption/Decryption
### 1.1. Encryption Steps:
Expand All @@ -13,10 +13,10 @@ graph LR;
pwd[Master<br/>Password]
mk[Master Key]
emk[Encrypted<br/>Master Key]
enc_1((EncryptionService<br/>.encrypt#40;#41;))
enc_1(("EncryptionService<br/>.encrypt()"))
sync[(Sync Target)]
pt[Notes/Resources]
enc_2((EncryptionService<br/>.encrypt#40;#41;))
enc_2(("EncryptionService<br/>.encrypt()"))
ct[Encrypted<br/>Notes/Resources]
mk-->enc_1
Expand All @@ -39,10 +39,10 @@ graph LR;
pwd[Master<br/>Password]
mk[Master Key]
emk[Encrypted<br/>Master Key]
dec_1((EncryptionService<br/>.decrypt#40;#41;))
dec_1(("EncryptionService<br/>.decrypt()"))
sync[(Sync Target)]
pt[Notes/Resources]
dec_2((EncryptionService<br/>.decrypt#40;#41;))
dec_2(("EncryptionService<br/>.decrypt()"))
ct[Encrypted<br/>Notes/Resources]
sync-->ct
Expand All @@ -64,7 +64,7 @@ graph LR;
- **Master Key Encryption/Decryption**: In these parts, the Master Key is encrypted or decrypted using the Master Password.
- **Data Encryption/Decryption**: In these parts, notes and resources are encrypted or decrypted using the Master Key.

The Master Password is stored locally in the database and is never updated to the Sync Target. As a result, third-party Sync Targets cannot decrypt the Master Key, notes or resources.
The Master Password is stored locally in the database and is never updated to the Sync Target. As a result, third-party Sync Targets cannot decrypt the Master Key, notes or resources.

## 2. General Implementation for `encrypt()` and `decrypt()`
### 2.1. encrypt():
Expand All @@ -75,21 +75,22 @@ graph LR;
kdf((KDF))
key[Key]
pt_str[Plaintext<br/>#40;string#41;]
pt_bin[Plaintext<br/>#40;binary#41;]
ct_str[Ciphertext<br/>#40;string#41;]
ct_bin[Ciphertext<br/>#40;binary#41;]
pt_str["Plaintext<br/>(string)"]
pt_bin["Plaintext<br/>(binary)"]
ct_str["Ciphertext<br/>(string)"]
ct_bin["Ciphertext<br/>(binary)"]
cipher((Cipher))
codec_1((Encoder/<br/>Decoder))
codec_2((Encoder/<br/>Decoder))
pwd~~~salt
pt_str~~~salt
pwd---salt
pt_str---salt
linkStyle 0,1 stroke-width:0px
pwd-->kdf
pt_str-->codec_1
subgraph sub_1 [EncryptionService.encrypt#40;#41;]
subgraph sub_1 ["EncryptionService.encrypt()"]
direction LR
codec_1-->pt_bin
pt_bin-->cipher
Expand All @@ -109,21 +110,22 @@ graph LR;
kdf((KDF))
key[Key]
pt_str[Plaintext<br/>#40;string#41;]
pt_bin[Plaintext<br/>#40;binary#41;]
ct_str[Ciphertext<br/>#40;string#41;]
ct_bin[Ciphertext<br/>#40;binary#41;]
pt_str["Plaintext<br/>(string)"]
pt_bin["Plaintext<br/>(binary)"]
ct_str["Ciphertext<br/>(string)"]
ct_bin["Ciphertext<br/>(binary)"]
decipher((Decipher))
codec_1((Encoder/<br/>Decoder))
codec_2((Encoder/<br/>Decoder))
pwd~~~salt
ct_str~~~salt
pwd---salt
ct_str---salt
linkStyle 0,1 stroke-width:0px
pwd-->kdf
ct_str-->codec_1
subgraph sub_1 [EncryptionService.decrypt#40;#41;]
subgraph sub_1 ["EncryptionService.decrypt()"]
direction LR
codec_1-->ct_bin
ct_bin-->decipher
Expand All @@ -136,39 +138,41 @@ graph LR;
codec_2-->pt_str
```
### 2.3. Explanations
(In this section, `encrypt()` and `decrypt()` refer to `EncryptionService.encrypt()` and `EncryptionService.decrypt()` respectively.)
(In this section, `encrypt()` and `decrypt()` refer to `EncryptionService.encrypt()` and `EncryptionService.decrypt()` respectively.)</br>
- **KDF**: Key Derivation Function, used to derive cryptographically strong keys from passwords.
- **Encoder/Decoder**: The `encrypt()` and `decrypt()` methods take strings as input and produce strings as output. Since the cipher operates on binary data and generates binary outputs, encoders and decoders are used to convert between strings and binary data.
- **KDF (Key Derivation Function)**: The `encrypt()` and `decrypt()` methods are designed for password-based encryption, but passwords may not meet cryptographic key strength requirements. The KDF addresses this by taking a password and a salt as inputs and generating a secure cryptographic key for the actual encryption process.
- **Salt**: The salt is managed internally by the `encrypt()` and `decrypt()` methods (or more generally, within the `EncryptionService`), so callers don't need to handle it explicitly.
## 3. Specifications for the New Native Encryption Methods
### 3.1. Encryption Flow
The expanded flow is shown below. Some of the elements have been replaced with the specific implementation compared with the general implementation graphs. Additionally, extra elements have been introduced for the chosen cipher.
The expanded flow is shown below. Some of the elements have been replaced with the specific implementation compared with the general implementation graphs. Additionally, extra elements have been introduced for the chosen cipher.

```mermaid
graph LR;
pwd[Password]
salt[Salt]
kdf((PBKDF2))
key[Key]
pt_str[Plaintext<br/>#40;string#41;]
pt_bin[Plaintext<br/>#40;binary#41;]
ct_str[Ciphertext<br/>#40;string#41;]
ct_bin[Ciphertext<br/>#40;binary#41;]
pt_str["Plaintext<br/>(string)"]
pt_bin["Plaintext<br/>(binary)"]
ct_str["Ciphertext<br/>(string)"]
ct_bin["Ciphertext<br/>(binary)"]
iv[Initialization Vector]
adata[Associated Data]
atag[Authentication Tag]
cipher((AES-256-GCM))
codec((Encoder/<br/>Decoder))
b64enc((Base64<br/>Encoder))
pwd~~~salt
pt_str~~~salt
pwd---salt
pt_str---salt
linkStyle 0,1 stroke-width:0px
pwd-->kdf
pt_str-->codec
subgraph sub_1 [EncryptionService.encrypt#40;#41;]
subgraph sub_1 ["EncryptionService.encrypt()"]
direction LR
codec-->pt_bin
pt_bin-->cipher
Expand All @@ -184,32 +188,34 @@ graph LR;
b64enc-->ct_str
```
### 3.2. Decryption Flow
The decryption flow is similar to the encryption flow.
The decryption flow is similar to the encryption flow.

```mermaid
graph LR;
pwd[Password]
salt[Salt]
kdf((PBKDF2))
key[Key]
pt_str[Plaintext<br/>#40;string#41;]
pt_bin[Plaintext<br/>#40;binary#41;]
ct_str[Ciphertext<br/>#40;string#41;]
ct_bin[Ciphertext<br/>#40;binary#41;]
pt_str["Plaintext<br/>(string)"]
pt_bin["Plaintext<br/>(binary)"]
ct_str["Ciphertext<br/>(string)"]
ct_bin["Ciphertext<br/>(binary)"]
iv[Initialization Vector]
adata[Associated Data]
atag[Authentication Tag]
decipher((AES-256-GCM))
codec((Encoder/<br/>Decoder))
b64dec((Base64<br/>Decoder))
pwd~~~salt
ct_str~~~salt
pwd---salt
ct_str---salt
linkStyle 0,1 stroke-width:0px
pwd-->kdf
ct_str-->b64dec
subgraph sub_1 [EncryptionService.decrypt#40;#41;]
subgraph sub_1 ["EncryptionService.decrypt()"]
direction LR
b64dec-->ct_bin
ct_bin-->decipher
Expand All @@ -225,7 +231,7 @@ graph LR;
codec-->pt_str
```
### 3.3. Encoder/Decoder
Different encoders/decoders are used for different types of plaintext:
Different encoders/decoders are used for different types of plaintext:

| Method Name | Plaintext Type | Encoder |
|---|---|---|
Expand All @@ -237,7 +243,7 @@ Different encoders/decoders are used for different types of plaintext:
- For `StringV1`, UTF-16 encoding ensures compatibility with all possible JavaScript characters.

### 3.4. PBKDF2 Parameters
PBKDF2 is used as the KDF for its compatibility across all platforms. The parameters are listed below:
PBKDF2 is used as the KDF for its compatibility across all platforms. The parameters are listed below:

<table>
<tbody>
Expand All @@ -261,11 +267,11 @@ PBKDF2 is used as the KDF for its compatibility across all platforms. The parame
</tbody>
</table>

The iteration count of `KeyV1` follows [OWASP recommendations](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2). For `StringV1` and `FileV1`, the randomly generated Master Key already provides sufficient cryptographic strength so the low iteration count is acceptable.
However, applying the KDF to the Master Key is still necessary to resolve the short IV problem in the AES-GCM cipher. The details are provided in [Section 3.6](#36-extended-equivalent-nonce).
The iteration count of `KeyV1` follows [OWASP recommendations](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2). For `StringV1` and `FileV1`, the randomly generated Master Key already provides sufficient cryptographic strength so the low iteration count is acceptable.</br>
However, applying the KDF to the Master Key is still necessary to resolve the short IV problem in the AES-GCM cipher. The details are provided in [Section 3.6](#36-extended-equivalent-nonce).</br>

### 3.5. Cipher/Decipher Parameters
The parameters for the cipher `AES-256-GCM`, used in all three new encryption methods (`KeyV1`, `StringV1`, `FileV1`), are listed below:
The parameters for the cipher `AES-256-GCM`, used in all three new encryption methods (`KeyV1`, `StringV1`, `FileV1`), are listed below:

| Parameter | Value |
|---|---|
Expand All @@ -276,17 +282,19 @@ The parameters for the cipher `AES-256-GCM`, used in all three new encryption me
| Associated Data | (Empty) |
| Authentication Tag Length | 128 Bits |

The Initialization Vector (IV) length is set to 96 bits because extending it doesn't improve the cryptographic strength. Actually, the low-level implementation of AES-GCM always reduces longer IVs to 96 bits.
The Initialization Vector (IV) length is set to 96 bits because extending it doesn't improve the cryptographic strength. Actually, the low-level implementation of AES-GCM always reduces longer IVs to 96 bits.

### 3.6. Extended Equivalent Nonce
Although AES-GCM has been used in TLS for years and has not shown significant vulnerabilities, there are still some security considerations:
Although AES-GCM has been used in TLS for years and has not shown significant vulnerabilities, there are still some security considerations:</br>
- The Galois Counter Mode (GCM) is vulnerable when the IV and key is reused.
- While a simple counter could serve as the IV, it's not easy to maintain a reliable monotonic counter across all clients.
- The AES-GCM cipher has a maximum IV length of 96 bits (as discussed in Section 3.5), which is relatively short.
- The AES-GCM cipher has a maximum IV length of 96 bits (as discussed in [Section 3.5](#35-cipherdecipher-parameters)
), which is relatively short.

Although unlikely, a Joplin user could run into two pieces of ciphertext encrypted with the same IV even if the IV is randomly generated. This cause security vulnerabilities if the notes and resources is encrypted with the same Master Key directly. To resolve this, a 256-bit generated salt is used for each encryption to derive a new encryption key from the Master Key, so the key passed to the cipher changes every time. In theory, this approach provides a equivalent nonce with a length of (256+96) bits.

Although unlikely, a Joplin user could run into two pieces of ciphertext encrypted with the same IV even if the IV is randomly generated. This cause security vulnerabilities if the notes and resources is encrypted with the same Master Key directly. To resolve this, a 256-bit generated salt is used for each encryption to derive a new encryption key from the Master Key, so the key passed to the cipher changes every time. In theory, this approach provides a equivalent nonce with a length of (256+96) bits.
The salt is generated using the following formula:

The salt is generated using the following formula:
```
encryptionNonce = concat(<168-bit Random Data>, <64-bit Timestamp in ms>, <56-bit Counter Value>)
salt = sha256(encryptionNonce)
Expand Down

0 comments on commit 0b287d1

Please sign in to comment.