You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The *`Strandlock`* Protocol is a composite encryption protocol designed to intertwine multiple cryptographic primitives to achieve robust security. Its purpose is to ensure that the compromise of one, two, or even three different cryptographic primitives does not jeopardize the confidentiality or integrity of messages.
17
17
18
-
Even if `ML-KEM-1024` and `Classic McEliece-8192128` are broken, messages remain secure, provided that the initial `SMP` verification request is not intercepted. If the initial SMP request is intercepted, security is maintained as long as the SMP answer retains sufficient entropy.
18
+
The protocol retains high-availability asynchronous behaviour, without reducing security and or privacy like similar protocols.
19
+
20
+
21
+
If `ML-KEM-1024` and `Classic McEliece-8192128` are broken, messages remain secure, provided that the initial `SMP` verification request is not intercepted. If the initial SMP request is intercepted, security is maintained as long as the SMP answer retains sufficient entropy.
19
22
20
23
If `xChaCha20poly1305` is broken, messages remain safe as long as (at least) one `KEM` is uncompromised.
21
24
22
25
If `OTP` implementation has mistakes, messages remain safe as long as `xChaCha20Poly1305` is remains unbroken.
23
26
24
27
If Both `KEMs`, and `xChaCha20Poly1305` are compromised in future, as long as `OTP batch` request was not intercepted nor logged, messages remain safe.
25
28
29
+
All cryptographic primitives are not just stacked on top of each other, but interwined. Each primitive both aids each other, and acts as a fallback if one or more are broken.
30
+
31
+
26
32
*`Strandlock`* is transport-agnostic. It can operate over any underlying protocol, including federated chat systems like *`Coldwire`* or raw `TCP` sockets.
27
33
28
34
### 1. Terminology
@@ -43,7 +49,7 @@ A logical state maintained between two parties that tracks shared secrets, nonce
43
49
A cryptographically secure random value used for entropy injection, whitening, and rotation. Strand nonces are exclusively used for `xChaCha20Poly1305` wrapping encryption. Every request contains the next nonce that will be used for the next request. Such nonces we call "Strand Nonces". Alice and Bob both save each other strand nonces.
44
50
45
51
#### Strand Key
46
-
Any key material derived, rotated, or combined from multiple primitives within Strandlock. Strand Key is fed to `xChaCha20Poly1305` to "wrap encrypt" everything. Applies to PFS, MSGS requests and responses, but not SMP. SMP process uses a temporary key simply dubbed "Temporary xChaCha key" or "SMP key"
52
+
Any key material derived, rotated, or combined from multiple primitives within Strandlock. Strand Key is fed to `xChaCha20Poly1305` to "wrap encrypt" everything. Applies to `SMP` (step 3 and onward), `PFS`, `MSGS` requests and responses.
47
53
48
54
49
55
#### SMP (Socialist Millionaire Protocol)
@@ -58,17 +64,39 @@ A collection of one-time pad material derived from multiple primitives and signe
58
64
#### Request Types:
59
65
Every request includes a message type identifier, which may be visible only in the very first `SMP` stage request. For all other requests that come afterwards, the type is encrypted within the payload.
60
66
61
-
#### Nonces:
62
-
Each request contains a `24-bytes nonce` immediately following the `type` field. These `nonces` are meant to be used for the next request to prevent metadata leakage, replay attacks, and on the small off-chance that a randomly generated nonce repeats twice, network adversaries wouldn't know a nonce reuse occured.
63
-
64
-
Additionally, while all public security proofs of `xChaCha20Poly1305` assume nonce is public, encrypting and or hiding the `nonce` might actually "future-proof" `xChaCha20Poly1305` against potentinal future attacks, leaving only open window through pure dumb brute-forcing of `32 bytes` key space.
65
67
66
68
#### Encryption:
67
-
All payloads are encrypted with `XChaCha20Poly1305`, except for the `SMP` initiation stage.
69
+
All payloads are encrypted with `XChaCha20Poly1305`, except for `SMP` step 1 (initiation).
70
+
71
+
#### Forward ratchet and Nonces:
72
+
All data is "wrap encrypted" using `xChaCha20Poly1305`
73
+
74
+
Each request starts with a `32-bytes next_strand_key` and a `24-bytes nonce` before the `type` field.
75
+
76
+
The key is saved on the receiver's end, to be that sender's next `strand_key`. Nonce is also saved as the sender's next `nonce`.
77
+
78
+
This is a forward, stateful ratchet for the `xChaCha20Poyl1305`. By the rotating the key for every encryption operation, we reduce the "blast radius" in-case a key compormise occurs, then only all data afterwards could be encrypted, until a `PFS` is triggered and new `xChaCha20Poyl1305` strand key is derived from there.
79
+
80
+
Hiding the `nonces` prevents metadata leakage, and in our scheme it also prevents replay attacks.
81
+
Additionally, while all public security proofs of `xChaCha20Poly1305` assume nonce is public, encrypting and or hiding the `nonce` might actually "future-proof" `xChaCha20Poly1305` against potentinal future attacks, leaving only open window through pure dumb brute-forcing of the `32 bytes` key space.
82
+
83
+
We deliberately avoided using a `KDF`, because this ratchet design is stronger. In `KDF` scheme, an attacker only need to brute-force 1 key to break all previous and future keys. Additionally, keys derived from a single master key suffer from being not true entropy.
84
+
85
+
This ratchet is not the most optiomal ratchet in the world, but it should be fine, as the primary objective of ``xChaCha20Poly1305` encryption in our protocol, is to simply protect metadata.
86
+
87
+
Actual "ratchet" and "perfect-forward secrecy" and "post-compromise security" are an inherit part of One-time-pads, which is used to actually encrypt plaintext message contents.
88
+
89
+
**NOTE**: This ratchet applies to *all* data types, unless expliclity stated otherwise. Receiver, and sender **must** send and save new keys and nonce from / in every request.
90
+
68
91
69
92
#### Human Verification:
70
93
`SMP` enforces a human-verifiable question-and-answer process before any chat communication. This prevents "trust on first use"-style attacks that plagues other encrypted protocols.
71
94
95
+
#### General Acknowledgements:
96
+
When you longpoll for new data, each data is appended with a `32 byte` id, once you process all the data, you should send back the acknowledged ids, so that they may be deleted off the server.
97
+
98
+
This applies to all requests.
99
+
72
100
### 3. Socialist Millionaire Protocol (`SMP`)
73
101
#### 3.1 Initialization (`Alice` -> `Bob`)
74
102
@@ -84,27 +112,35 @@ The `payload` is prefixed with `SMP_TYPE = 0x00`.
84
112
85
113
#### 3.2 Initialization response (`Bob`)
86
114
87
-
`Bob` generates a shared secret using `Alice’s``ML-KEM-1024` public key (this is called temporary xchacha key, only used for `SMP` encryption).
115
+
`Bob` generates a 4 shared secrets (`128 bytes` total) using `Alice’s``ML-KEM-1024` public key, and concatenate the ciphertext together:
116
+
- 1st key (`0:32` index) is `Bob's``bob_strand_key`
117
+
- 2nd key (`32:64` index) is `Alice's``alice_strand_key`
118
+
- 3rd key (`64:96` index) is `Bob's``bob_backup_strand_key`
119
+
- 4th key (`96:128` index) is `Alice's``alice_backup_strand_key`.
120
+
121
+
`Bob` generates two `Strand Nonces` (one for himself, one for `Alice`).
88
122
89
-
`Bob` generates two `Strand Nonces` (one for himself, one for `Alice`) and hashes each with `SHA3-512`, truncating output to `24 bytes`.
123
+
Note: Every key, and nonce, *must* be generated from a cryptographically secure random generator. And all keys and nonces must be `SHA3_512` hashed, then the output must be truncated to the original key or nonce original sizes.
90
124
91
-
`Bob` also generates an `SMP nonce` for the verification process.
125
+
`Bob` also generates an `SMP nonce` for the `SMP`verification process.
92
126
93
127
`Bob` generates an `ML-DSA-87` key pair for signing (this is called `per-contact signing public key` or just `signing key`).
94
128
129
+
Note: This key is a long-term key, and **must** be saved, and only used with the contact in question.
`Bob` saves `Alice` to his contact list locally, however `Bob`**must** flag `Alice` as `unverified` or `pending_verification`.
143
+
`Bob` saves `Alice` to his contact list locally, however `Bob`**must** flag `Alice` as `unverified` or `pending_verification` (i.e. do not process any non-SMP requests until the verification succeeds.).
108
144
`Bob` also stores all `nonces` and the `temporary XChaCha key` for this `SMP` session.
109
145
110
146
#### 3.3 Proof 1 (`Alice's` proof of `Bob's` public-key)
`Alice` normalizes her `SMP answer` (strip whitespace, lowercase only the first character) and `UTF-8` encodes it.
154
+
`Alice` normalizes her `SMP answer` (strip leading and trailing whitespaces, and lowercase *only* the first character) and `UTF-8` encodes it.
119
155
120
156
Alice generates an `Argon2Id salt` by concatenating `ALICE_SMP_NONCE` to `BOB_SMP_NONCE`, hashes it with `SHA3_512` and truncates back to `16 bytes` (16 bytes for interoperability with libsodium):
121
157
```
@@ -148,14 +184,14 @@ Alice generates an `ML-DSA-87` key pair for herself.
148
184
149
185
`Alice` prepares `SMP` request (Question must be `UTF-8` encoded):
`Alice` encrypts the payload with `XChaCha20` using `temporary xchacha key` as key, and uses `ALICE_NEXT_STRAND_NONCE` as nonce, and sends it to `Bob`
190
+
`Alice` encrypts the payload with the `XChaCha20Poly1305` wrapping scheme sends it to `Bob`.
155
191
156
192
#### 3.4 Verification & Proof 2 (`Bob`):
157
193
158
-
`Bob` decrypts the payload with the `temporary key` and asks the user for an `SMP answer`.
194
+
`Bob` decrypts the `xChaCha20Poly1305` wrapper, and parses the payload and asks the user for an `SMP answer`.
159
195
160
196
`Bob` checks if `BOB_SMP_NONCE` is equal to `ALICE_SMP_NONCE`, aborting and sending a `SMP failure request` if they match.
161
197
@@ -166,7 +202,7 @@ If verification *fails*:
166
202
```
167
203
SMP_REQUEST_DATA = SMP_TYPE || b"failure".
168
204
```
169
-
`Bob` encrypts the payload using `temporary chacha key`, but does not use his **Strand Nonce**, instead he generates a random nonce and bundles it at start of the **ciphertext**.
205
+
`Bob` encrypts the payload with the `XChaCha20Poly1305` wrapping scheme sends it to `Alice
`Bob` encrypts the `SMP request data` with `temporary xchacha key` as key, and previous `BOB_NEXT_STRAND_NONCE` as nonce, and sends to `Alice`.
228
+
`Bob` encrypts the `SMP request data` with the `XChaCha20Poly1305` wrapping scheme sends it to `Alice`
193
229
194
-
`Bob` modifies both `ALICE_STRAND_KEY` and `BOB_STRAND_KEY` by `XOR-ing` each key with the `SHA3-512` hash of `ANSWER_SECRET`:
230
+
`Bob` modifies both the ratchet's new `ALICE_STRAND_KEY` and `BOB_STRAND_KEY` by `XOR-ing` each key with the `SHA3-512` hash of `ANSWER_SECRET` and truncating the hash output back to `32 bytes`:
`Bob` then saves the new keys as always (in all throughout the protocol, we implicility imply you must save new ratchet keys, but in this specific stage, we add a special transformation before saving them).
200
237
201
-
`Bob` then saves the new keys, and marks `Alice` as `SMP` verified.
238
+
`Bob` then marks `Alice` as `SMP`"verified".
202
239
203
240
#### 3.5 Final Verification (`Alice`):
204
241
205
242
`Alice` decrypts `Bob’s``SMP` payload and verifies `Bob’s` proof.
206
243
207
-
If valid, she applies the same `XOR` transformation to the `Strand Keys`, and saves them.
244
+
If valid, she applies the same `XOR` transformation to the `Next Strand Keys`, and saves them.
208
245
209
246
`Alice` marks `Bob` as verified.
210
247
211
-
`Alice` sends her first PFS keys.
248
+
`Alice` sends her first `PFS` keys.
212
249
213
250
#### 3.6. Notes on SMP:
214
251
215
252
Step 1: No encryption
216
253
217
-
Step 2: Encryption is being set up
254
+
Step 2: `xChaCha20Poly1305` ratchet encryption is being set up
218
255
219
-
Step 3 and onwards: All requests are encrypted with the `temporary xchacha key` and `nonces` protected using the `strand nonces`by bundling next nonce to be used in every request.
256
+
Step 3 and onwards: All requests are encrypted with `xChaCha20Poly1305` wrapping, by bundling next nonce and key to be used in every request. This applies not just to `SMP`, but to all other steps.
220
257
221
-
Nonces are embedded in payloads, not sent in clear, except in step 2 and SMP failure requests
258
+
Nonces are embedded in payloads, not sent in clear, except in step 2.
222
259
223
260
Do not confuse `Strand Nonces` with `SMP Nonces`, the latter is only used for SMP process (as salt for `Argon2id`, etc, not for encryption), while the former is used in Step 3 and onwards, even in other requests types (`PFS`, `MSGS`, etc.)
224
261
@@ -231,7 +268,7 @@ We highly recommend implementations to only allow user to set a `8+ character` a
231
268
232
269
Even though the `question` is encrypted, an active *Man-in-the-middle* adversary **can still retrieve it**. The verification would fail, but the adversary would have the `question` plaintext.
233
270
234
-
This is acceptable, as the purpose of encrypting `SMP` process is to hide *metadata* against **passive** adversaries, not an **active** adversary.
271
+
This is acceptable, as the purpose of encrypting the `SMP` process is to hide *metadata* against **passive** adversaries, not an **active** adversary.
235
272
236
273
The question **must not** contain any senstive data. And it must not contain any hints to the answer.
`Alice`then encrypts the `PFS_PAYLOAD`, with `xChaCha20Poly1305` using her `ALICE_STRAND_KEY` key, and using previous `ALICE_NEXT_STRAND_NONCE` as nonce.
303
+
`Alice`does not use the new keys, but saves them until she receives an `PFS ack` request, which she then deletes old keys, and uses the new keys.
`Bob` determines which keys were sent (`ML-KEM-1024` only or `ML-KEM-1024` + `Classic-McEliece-8192128`), and saves them.
277
314
315
+
Afterwards, `Bob` then sends an `PFS ack` (`PFS_ACK` = `\x02`) request to `Alice`, informing her that he received keys:
316
+
```
317
+
PFS_ACK_PAYLOAD = PFS_ACK
318
+
```
319
+
278
320
`Bob` then checks if he already sent keys to `Alice`, if he never sent any keys before to `Alice`, he performs the `4.1. Key Rotation` as well.
279
321
280
322
#### 4.3. PFS Notes:
@@ -283,6 +325,9 @@ Even though the use of hash-chains and signatures may appear redundant here, as
283
325
The reason we opted for a hash-chain based design, instead of a simple counter, is to ensure metadata of how many key rotations occured never gets leaked, even when `xChaCha20Poly1305` is broken.
284
326
Even if `Alice's` or `Bob's` endpoint get compromised, no metadata of how many key rotation occured could be recovered.
285
327
328
+
Acknowlegement make this design fully async, and arguably more secure than other async `PFS` schemes, as only one set of keypairs are at use at any time.
329
+
330
+
286
331
### 5. Messaging (`MSGS`)
287
332
#### 5.1 OTP Batch Generation (`Alice`)
288
333
`Alice` uses `Bob's``ML-KEM-1024` public-key to generates many shared secrets *in chunks*, concatenating them until their total size reaches (or exceeds) `OTP_PAD_SIZE` size (default `11264 bytes`).
0 commit comments