7.5 KiB
slug, id, title, sidebar_label, description, keywords, image, hide_title, hide_table_of_contents
| slug | id | title | sidebar_label | description | keywords | image | hide_title | hide_table_of_contents | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| encryption/003 | encryption-003 | Encryption Protocol Specification v003 | Encryption v003 | Specification for the Standard Notes end-to-end encryption. |
|
/img/logo.png | false | false |
Version 0.0.3
It is important that there exist a separation of concerns between the server and the client. That is, the client should not trust the server, and vice versa.
Encryption keys are generated by stretching the user's input password using a key derivation function.
The resulting key is split in three — the first third is sent to the server as the user's password, the second third is saved locally as the user's master encryption key, and the last third is used as an authentication key. In this setup, the server is never able to compute the encryption key or the user's original password given just a fraction of the resulting key.
Note: client-server connections must be made securely through SSL/TLS.
Elaboration on User model encryption related fields
| name | details |
|---|---|
| pw_cost | The number of iterations to be used by the KDF. The minimum for version 003 is 100,000. However note that non-native clients (web clients not using WebCrypto) will not be able to handle any more than 3,000 iterations. |
| pw_nonce | A nonce for password derivation. This value is initially created by the client during registration. |
Key Generation
Client Instructions
Given a user inputted password uip, the client's job is to generate a password pw to send to the server, a master key mk that the user stores locally to encrypt/decrypt data, and an auth key ak for authenticating encrypted data.
Login Steps
-
Client makes GET request with user's email to
auth/paramsto retrieve password nonce, cost, and version. -
Client verifies cost >= minimum cost (100,000 for 003.)
-
Client computes
pw,mk, andakusing PBKDF2 with SHA512 as the hashing function and output length of 768 bits:salt = SHA256:Hexdigest([email, "SF", version, pw_cost, pw_nonce].join(":")) key = pbkdf2(uip, salt, sha512, 768, pw_cost) // hex encoded pw = key.substring(0, key.length/3) mk = key.substring(key.length/3, key.length/3) ak = key.substring(key.length/3 * 2, key.length/3) -
Client sends
pwto the server as the user's "regular" password and storesmkandaklocally. (mkandakare never sent to the server).
Registration Steps
-
Client chooses default for
pw_cost(minimum 100,000). -
Client generates
pw_nonce:pw_nonce = random_string(256) -
Client computes
pw,mk, andakusing step (3) from Login Steps. -
Client registers with
email,pw,pw_cost,pw_nonce, andversion.
Item Encryption
In general, when encrypting a string, one should use an IV so that two subsequent encryptions of the same content yield different results, and one should authenticate the data as to ascertain its authenticity and lack of tampering.
In Standard Notes, two strings are encrypted for every item:
- The item's
content. - The item's
enc_item_key.
Client-side
An item is encrypted using a random key generated for each item.
Encryption:
Note that when encrypting/decrypting data, keys should be converted to the proper format your platform function supports. It's best to convert keys to binary data before running through any encryption/hashing algorithm.
For every item:
- Generate a random 512 bit key
item_key(in hex format). - Split
item_keyin half; set item encryption keyitem_ek = first_halfand item authentication keyitem_ak = second_half. - Encrypt
contentusingitem_ekanditem_akfollowing the instructions "Encrypting a string using the 003 scheme" below and send to server ascontent. - Encrypt
item_keyusing the globalmkand globalakfollowing the instructions "Encrypting a string using the 003 scheme" below and send to server asenc_item_key.
Decryption:
Check the first 3 characters of the content string. This will be the encryption version.
-
If it is equal to "001", which is a legacy scheme, decrypt according to the 001 instructions found here.
-
If it is equal to "002" or "003", decrypt as follows:
- Decrypt
enc_item_keyusing the globalmkand globalakaccording to the "Decrypting a string using the 003 scheme" instructions below to getitem_key. - Split
item_keyin half; set encryption keyitem_ek = first_halfand authentication keyitem_ak = second_half. - Decrypt
contentusingitem_ekanditem_akaccording to the "Decrypting a string using the 003 scheme" instructions below.
- Decrypt
Encrypting a string using the 003 scheme:
Given a string_to_encrypt, an encryption_key, and an auth_key:
- Generate a random 128 bit string called IV.
- Encrypt
string_to_encryptusingAES-CBC-256:Base64,encryption_key, andIV:
ciphertext = AES-Encrypt(string_to_encrypt, encryption_key, IV)
- Generate
string_to_authby combining the encryption version (003), the item's UUID, the IV, and the ciphertext using the colon ":" character:
string_to_auth = ["003", uuid, IV, ciphertext].join(":")
- Compute
auth_hash = HMAC-SHA256:Hex(string_to_auth, auth_key). - Generate the final result by combining the five components into a
:separated string:
result = ["003", auth_hash, uuid, IV, ciphertext].join(":")
Decrypting a string using the 003 scheme:
Given a string_to_decrypt, an encryption_key, and an auth_key:
- Split the string into its constituent parts:
components = string_to_decrypt.split(":"). - Assign local variables:
version = components[0]
auth_hash = components[1]
uuid = components[2]
IV = components[3]
ciphertext = components[4]
- Ensure that
uuid == item.uuid. If not, abort decryption. - Generate
string_to_auth = [version, uuid, IV, ciphertext].join(":"). - Compute
local_auth_hash = HMAC-SHA256(string_to_auth, auth_key). Comparelocal_auth_hashtoauth_hash. If they are not the same, skip decrypting this item, as this indicates that the string has been tampered with. - Decrypt
ciphertextto get final result:result = AES-Decrypt(ciphertext, encryption_key, IV).
Server-side
For every received item:
- (Optional but recommended) Encrypt
contentusing server known key and store. Decrypt before sending back to client.
Next Steps
Join the Discord group to discuss implementation details and ask any questions you may have.
You can also email help@standardnotes.org.
Follow @standardnotes on Twitter for updates and announcements.