7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 1 | A general purpose encryption scheme for key/value stores. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 2 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 3 | # Usage |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 4 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 5 | ```rust |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 6 | use matrix_sdk_store_encryption::StoreCipher; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 7 | use serde_json::{json, value::Value}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 8 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 9 | fn main() -> anyhow::Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 10 | let store_cipher = StoreCipher::new()?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 11 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 12 | // Export the store cipher and persist it in your key/value store |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 13 | let export = store_cipher.export("secret-passphrase")?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 14 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 15 | let value = json!({ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 16 | "some": "data", |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 17 | }); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 18 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 19 | let encrypted = store_cipher.encrypt_value(&value)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 20 | let decrypted: Value = store_cipher.decrypt_value(&encrypted)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 21 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 22 | assert_eq!(value, decrypted); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 23 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 24 | let key = "bulbasaur"; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 25 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 26 | // Hash the key so people don't know which pokemon we have collected. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 27 | let hashed_key = store_cipher.hash_key("list-of-pokemon", key.as_ref()); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 28 | let another_table = store_cipher.hash_key("my-starter", key.as_ref()); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 29 | let same_key = store_cipher.hash_key("my-starter", key.as_ref()); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 30 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 31 | assert_ne!(key.as_ref(), hashed_key); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 32 | assert_ne!(hashed_key, another_table); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 33 | assert_eq!(another_table, same_key); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 34 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 35 | Ok(()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 36 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 37 | ``` |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 38 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 39 | ## Security warning: hazmat! ⚠️ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 40 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 41 | This crate only implements the low-level block cipher function, to be used |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 42 | _only_ as a building block for higher-level constructions. It is NOT intended |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 43 | for direct use in applications. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 44 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 45 | USE AT YOUR OWN RISK! |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 46 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 47 | ## Encryption scheme |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 48 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 49 | The central component of the encryption scheme is the `StoreCipher` type, used |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 50 | for both obfuscating keys and encrypting values of the key/value store. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 51 | A `StoreCipher` object consists of two randomly-generated 32 byte secrets. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 52 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 53 | The first secret is used to encrypt values. XChaCha20Poly1305 with a random |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 54 | nonce is used to encrypt each value. The nonce is saved with the ciphertext. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 55 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 56 | The second secret is used as a seed to derive table-specific keys, used to key |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 57 | a keyed hash construction, which is in turn used to hash table data. Currently |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 58 | we use blake3 as the keyed hash construction. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 59 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 60 | ```text |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 61 | ┌───────────────────────────────────────┐ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 62 | │ StoreCipher │ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 63 | │ Encryption key | Hash key seed │ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 64 | │ [u8; 32] | [u8; 32] │ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 65 | └───────────────────────────────────────┘ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 66 | ``` |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 67 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 68 | The `StoreCipher` has some Matrix-specific assumptions built in, which ensure |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 69 | that the limits of the cryptographic primitives are not exceeded. If this crate |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 70 | is used for non-Matrix data, users need to ensure: |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 71 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 72 | 1. That individual values are chunked, otherwise decryption might be susceptible |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 73 | to a DOS attack. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 74 | 2. The `StoreCipher` is periodically rotated/rekeyed. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 75 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 76 | ## WASM support |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 77 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 78 | This crate relies on the `random` and `getrandom` crates which don't support |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 79 | WASM automatically. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 80 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 81 | Either turn the `js` feature on directly on this crate or depend on `getrandom` |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 82 | with the `js` feature turned on. More info can be found in the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 83 | [`getrandom` docs](https://docs.rs/getrandom/latest/getrandom/index.html#webassembly-support). |