Encryption details - fastaddons/GroupSpeedDial GitHub Wiki
Basic info about End-to-end encryption (E2EE)
- When you protect shared dials with a password, all dials data will be encrypted on your device before they are uploaded to the server.
- Your password is never stored anywhere and cannot be recovered.
- Anyone who tries to access your shared dials (with your shared link) will not be able to use offline brute-force attack to crack your password (encrypted data are not accessible without "proof of password").
The rest of this article will explain in detail how is the encryption implemented.
How E2E shared data encryption works:
-
When user enters his password to protect the data, the password is first hashed using PBKDF2 with SHA-256 with 5 million iterations and random 128 bit "SALT".
The result of this operation is "HASH", which can be locally stored in the memory or in the browser "sessionStorage" (which is cleared when the tab is discarded). -
From the result "HASH" we compute "ACCESS_SECRET" (like a CRC) by hashing it with SHA-256.
This "ACCESS_SECRET" will be uploaded to the server with encrypted data and server will return encrypted data ONLY if you provide correct "ACCESS_SECRET". -
Before we encrypt the data, we will generate strong random master password.
We are usingcrypto.subtle.generateKey
with "AES-GCM" to generate 256 bit long key.
This key is wrapped with the wrapping key which we get by usingcrypto.subtle.deriveKey
on the user password "HASH" (see step 1).
Wrapped master password will be stored with encrypted data on the server as "SECRET". Note that you still need user password "HASH" to unwrap the actual master password! -
Data are encrypted using the generated master password.
We are usingcrypto.subtle.encrypt
with "AES-GCM" with random "IV" (96 bit) and the unwrapped master password. Ecnrypted data will be uploaded to the server as "CIPHERTEXT". -
Data are uploaded to the server, specifically:
- name of the hashing function (SHA-256), algorithm (PBKDF2) and number of iterations (5,000,000)
- "ACCESS_SECRET" - to verify that the user that requests the encrypted data can compute correct 256 bit long hash of the hashed password
- "CIPHERTEXT" - the actual encrypted data
- "SECRET" - wrapped master password (needs to be unwrapped with user password "HASH" before it can be used to decrypt the data)
- "SALT" - random password salt
- "IV" - random initialization vector
-
Data are stored in the database and UUID (converted to base64) is returned. This ID is then used to create sharing link, for example: "/cloud/?share=E6oL0frVQ4qoN51im-wrNQ"
How shared data decryption works:
-
When user opens shared link to the encrypted dials data, he will receive following data from the server:
- name of the hashing function (SHA-256), algorithm (PBKDF2) and number of iterations (5e6)
- "SALT"- that needs to be added to the user password
-
User is now presented with a password input.
-
Entered password is merged with "SALT" and hashed using PBKDF2 with SHA-256 using 5e6 iterations (this creates "HASH" of the password).
The result "HASH" is hashed using SHA-256 to create "ACCESS_SECRET". -
Browser will send new request to the server, this time with "ACCESS_SECRET" (in a header).
-
Server will compare "ACCESS_SECRET" with the "ACCESS_SECRET" in the database using "timingSafeEqual" operation.
If the secret matches, server will reply with the missing data - CIPHERTEXT, SECRET and IV. -
Browser will unwrap "SECRET" using the "HASH" - which will give us master password. The "CIPHERTEXT" is then decrypted using this master password and "IV".
How are the data protected against brute-force attacks?
Since attacker that tries to access the encrypted data doesn't receive actual data nor wrapped master password, there is no possibility for offline attack.
Attacker would have to compute complex hash and submit it to the server to test it.
Server API is protected with "leaky bucket" so attacker would be limited to test ~1 password per second (per IP).
Additionally, server is running with 1 CPU (1 thread) so testing huge amount of passwords is simply not computationally possible.