--- slug: encryption/003 id: encryption-003 title: Encryption Protocol Specification v003 sidebar_label: Encryption v003 description: Specification for the Standard Notes end-to-end encryption. keywords: - standard notes - docs - notes app - end-to-end encryption - encryption specification image: /img/logo.png hide_title: false hide_table_of_contents: 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.](https://en.wikipedia.org/wiki/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 1. Client makes GET request with user's email to `auth/params` to retrieve password nonce, cost, and version. 1. Client verifies cost >= minimum cost (100,000 for 003.) 1. Client computes `pw`, `mk`, and `ak` using 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) ``` 1. Client sends `pw` to the server as the user's "regular" password and stores `mk` and `ak` locally. (`mk` and `ak` are never sent to the server). #### Registration Steps 1. Client chooses default for `pw_cost` (minimum 100,000). 1. Client generates `pw_nonce`: ``` pw_nonce = random_string(256) ``` 1. Client computes `pw`, `mk`, and `ak` using step (3) from Login Steps. 1. Client registers with `email`, `pw`, `pw_cost`, `pw_nonce`, and `version`. ## 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: 1. Generate a random 512 bit key `item_key` (in hex format). 2. Split `item_key` in half; set item encryption key `item_ek = first_half` and item authentication key `item_ak = second_half`. 3. Encrypt `content` using `item_ek` and `item_ak` following the instructions "Encrypting a string using the 003 scheme" below and send to server as `content`. 4. Encrypt `item_key` using the global `mk` and global `ak` following the instructions "Encrypting a string using the 003 scheme" below and send to server as `enc_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](https://github.com/standardfile/standardfile.github.io/blob/master/doc/spec-001.md). - If it is equal to "002" or "003", decrypt as follows: 1. Decrypt `enc_item_key` using the global `mk` and global `ak` according to the "Decrypting a string using the 003 scheme" instructions below to get `item_key`. 2. Split `item_key` in half; set encryption key `item_ek = first_half` and authentication key `item_ak = second_half`. 3. Decrypt `content` using `item_ek` and `item_ak` according to the "Decrypting a string using the 003 scheme" instructions below. ### Encrypting a string using the 003 scheme: Given a `string_to_encrypt`, an `encryption_key`, and an `auth_key`: 1. Generate a random 128 bit string called IV. 1. Encrypt `string_to_encrypt` using `AES-CBC-256:Base64`, `encryption_key`, and `IV`: ``` ciphertext = AES-Encrypt(string_to_encrypt, encryption_key, IV) ``` 1. Generate `string_to_auth` by 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(":") ``` 1. Compute `auth_hash = HMAC-SHA256:Hex(string_to_auth, auth_key)`. 1. 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`: 1. Split the string into its constituent parts: `components = string_to_decrypt.split(":")`. 1. Assign local variables: ``` version = components[0] auth_hash = components[1] uuid = components[2] IV = components[3] ciphertext = components[4] ``` 1. Ensure that `uuid == item.uuid`. If not, abort decryption. 1. Generate `string_to_auth = [version, uuid, IV, ciphertext].join(":")`. 1. Compute `local_auth_hash = HMAC-SHA256(string_to_auth, auth_key)`. Compare `local_auth_hash` to `auth_hash`. If they are not the same, skip decrypting this item, as this indicates that the string has been tampered with. 1. Decrypt `ciphertext` to get final result: `result = AES-Decrypt(ciphertext, encryption_key, IV)`. ## Server-side For every received item: 1. (Optional but recommended) Encrypt `content` using server known key and store. Decrypt before sending back to client. ## Next Steps Join the [Slack group](https://standardnotes.com/slack) to discuss implementation details and ask any questions you may have. You can also email [help@standardnotes.org](mailto:help@standardnotes.org). Follow [@standardnotes on Twitter](https://twitter.com/standardnotes) for updates and announcements.