317 lines
18 KiB
Markdown
317 lines
18 KiB
Markdown
# Stateless Time-Scrambled Cyclic Hash Protocol
|
||
|
||
**S**a**TS CH**i**P**
|
||
|
||
|
||
<u>Vocabulary</u>
|
||
- A `token` is a fixed-length string sent by a client to a server to authenticate.
|
||
- A `private key` is a fairly unique and hardly guessable data that is generated by a machine and never shared.
|
||
- A `request` is sent from a client to a server, the server sends back a `response`.
|
||
|
||
## Abstract
|
||
|
||
After designing some APIs, I found out that you must have at some point a *token* system to authenticate to your server. For the simplest design, the server generates a unique token for each client, this token will then be sent over each request to authenticate.
|
||
> The server may also regenerate the token at some time to avoid token theft.
|
||
|
||
An easy MITM attack could be to repeat a previously sent request while the token is still valid or even catch the client *token* and build a malicious request with its authentication.
|
||
|
||
> In this document we will assume that what travels through the network is public, so any MITM can store it. Obviously I highly recommend using TLS for communicating, but we look here for a consistent token system, it only can be better through TLS.
|
||
|
||
A good alternative could be to use a *one-time token* where each request features a unique token. This design implies that the token travels through the network - which we consider public - before being used so any MITM can catch it. To overcome this flaw we could have a system where *one key* can generate all the tokens in a consistent manner. If so, the tokens do not have to be sent and can be guessed from the key. But this also implies that the key is shared at some point.
|
||
|
||
A better solution would be to generate a private key on each client and use it to generate a token for each request. Also, to avoid to share the key and allow the server to easily check tokens, we would like a system where each token can be checked from the previous token without having to know the private key (because it is private...). It would avoid attackers to repeat requests (because each token is unique) or guess the private key because it is never over the network but only on the client. In addition short-lived one-time passwords have a mechanism that we could use to build a time-dependent system.
|
||
|
||
**What we need**
|
||
|
||
1. Generate a *public token* for each request from a fixed *private key*
|
||
2. The token *never* to be the same
|
||
3. The token to be easily checked directly from the previous one
|
||
4. Each token to be only valid a few seconds after sending it
|
||
5. Each token to give no clue that could help guessing the next token.
|
||
6. A system where no key is shared ; only clients do the key processing and the server only to process simple checking algorithm
|
||
|
||
**Technology requirements**
|
||
|
||
1. Mixing 2 hashes in a way that without one of them, the other is *cryptographically impossible* to guess (*i.e. [one-time pad](https://en.wikipedia.org/wiki/One-time_pad)*).
|
||
2. Having a time-dependent unique hash, that could be found only a few seconds after sending it (as for *[TOTP](https://tools.ietf.org/html/rfc6238)*).
|
||
3. A cryptographic hash function that, from an input of any length, outputs a fixed-length digest in a way that is *impossible* to guess the input back from it.
|
||
|
||
**Protocols to define**
|
||
|
||
This document will define and bundle 2 distinct protocols to make up a token system that implements the previous statements.
|
||
|
||
1. a <u>Stateless Time Scrambling Protocol</u> to take care of the request's invalidation over time.
|
||
2. a <u>Stateless Cyclic Hash Algorithm</u> to use a private key as a one-time token generator in a way that no clue is given over published tokens (*i.e. one-way function*).
|
||
3. A key renewal mechanism in a way that no clue is given over neither the old nor the new key.
|
||
4. A rescue protocol to resynchronise the client with a new key in a way that no clue is given over the network and the client has to process a "proof of work".
|
||
|
||
|
||
## General knowledge & Notations
|
||
|
||
##### Notation
|
||
|
||
| Symbols | Description |
|
||
|:-----:|:----------|
|
||
| $\parallel a\parallel $ | The absolute value of $a$ ; *e.g.* $\parallel -12 \parallel = 12$ |
|
||
| $\mid a \mid$ | The integer value of $a$ ; *e.g.* $\mid 12.34 \mid = 12$ |
|
||
| $a \oplus b$ | The bitwise operation XOR between binary words $a$ and $b$ |
|
||
| $a \And b$ | The bitwise operation AND between binary words $a$ and $b$ |
|
||
| $h(m)$ | The digest of the message $m$ by a (consistent) secure cryptographic hash function $h()$ ; *e.g. sha512* |
|
||
| $h^n(m)$ | The digest of the $n$-recursive hash function $h()$ with the input data $m$ ; <br>$h^2(m) \equiv h(h(m))$, $h^1(m) \equiv h(m)$, and $h^0(m) \equiv m$. |
|
||
| $a \ \mathbb{Z}_{(b)}$ | The result of $a$ modulo $b$ ; *i.e.* the remainder of the Euclidean division of $a$ by $b$ |
|
||
| $t_{now}$ | The current *Unix Timestamp* in seconds |
|
||
|
||
## Variables
|
||
|
||
The whole system share a common **context**, each client holds a **private keyset**, and the server stores the **last valid token** of each client.
|
||
|
||
#### 1. Common context
|
||
|
||
These variables are both on the server and clients. They are specific to the server so each client must match its values. These variables shape the system's **context** $(W, min, belt, max)$.
|
||
|
||
| Notation | Name | Description |
|
||
|:--------:|:----:|:------------|
|
||
| $W$ | time window | A number of seconds that is typically the maximum transmission time from end to end. It will be used by the *time-scrambling aspect*. The lower the number, the less time an attacker has to try to brute-force the tokens. |
|
||
| $min$ | resynchonization range | A number that is used to resynchronize the client if there is a communication issue (*e.g. lost request, lost response, attack*). The higher the value, the higher the challenge for the client to recover the authentication, thus the harder for an attacker to guess it. |
|
||
| $sec$ | security range | A number that is used to resynchronize the client if there is a communication shift (*e.g. lost request, lost response, attack*). It corresponds of the number of desynchronizations the client can handle before never being able to gain the authentication again. |
|
||
| $max$ | maximum nonce | A number that is used to cap the value of client's nonces. A too high value will result on keys that will never be replaced, thus making them open to long-processing attacks (*e.g. brute-force*). |
|
||
|
||
#### 2. Client keyset
|
||
|
||
Every client holds a **keyset** $(K, n, s)$. It represents its private key and is used to generate the tokens. The secure hash function is extended as a **one-way function** to generate all the tokens from the keyset.
|
||
|
||
| Notation | Name | Description |
|
||
|:--------:|:----:|:------------|
|
||
| $K$ | private key | A secret binary data that must be large and *random* enough not to be brute forced. |
|
||
| $n$ | key nonce | A number that is decremented before each request. Before $n$ reaches 0, a new keyset must be generated. |
|
||
| $s$ | key state | A number that reflects the state of the keyset. It is used to know what to do on the **next request** : <br>- $0$ : normal request<br>- $1$ : will switch to the new key<br>- $2$ : rescue proof of work sent, waiting for the server's acknowledgement |
|
||
|
||
#### 3. Server variables
|
||
|
||
| Notation | Name | Description |
|
||
|:--------:|:----:|:------------|
|
||
| $T$ | last valid token | The server stores the last valid token from the client to check the next one. |
|
||
|
||
|
||
## Protocol
|
||
|
||
#### 1. Client authentication
|
||
|
||
In each request, the client will send a pair of tokens $(x_1, x_2)$ :
|
||
- $x1$ will hold the current one-time token
|
||
- $x2$ will hold data to check the next one-time token to be used
|
||
|
||
The client's current keyset has 3 states :
|
||
- `normal` - default authentication algorithm.
|
||
- `switch` - default algorithm variation to switch to a new keyset when the current one is consumed.
|
||
- `rescue` - process the proof of work after receiving the server's challenge when there is a desynchronisation and generate a new keyset.
|
||
|
||
When the client switches to a new key, it has to the new keyset along the current one, in order not to lose its authentication if the network fails.
|
||
- $(K,n,s)$ - the current keyset
|
||
- $(K',n',s')$ - the new keyset
|
||
|
||
##### a. `normal` protocol
|
||
|
||
1. Decrement $i$
|
||
2. $t\_c = \mid \frac{t\_{now}}{W}\mid$
|
||
3. $x\_1 = h^{n}(K) \oplus h(t\_c)$
|
||
4. $x\_2 = x\_1 \oplus (t\_c \ \mathbb{Z}\_{(2)})$
|
||
|
||
Send $x_1$ and $x_2$.
|
||
|
||
If $i \leq min+sec$, go to `switch` protocol.
|
||
|
||
##### b. `switch` protocol
|
||
|
||
1. $t\_c = \mid \frac{t\_{now}}{W}\mid$
|
||
2. $x\_1 = h^{n}(K) \oplus h(t\_c)$
|
||
3. Generate $(K',n',s')$ in a way where the following condition is met :
|
||
- $[(h^n(K) \oplus h^{n'}(K')) \And 1] \ \mathbb{Z}\_{(2)} = t\_c \ \mathbb{Z}\_{(2)} $
|
||
4. $x\_2 = h^{n'}(K') \oplus h(t\_c)$
|
||
|
||
Send $x_1$ and $x_2$.
|
||
|
||
#### c. `rescue` protocol
|
||
|
||
The rescue protocol is processed when receiving $y_1$ and $y_2$ from the server instead of the standard response.
|
||
|
||
1. $m_s = y_1 \oplus y_2$
|
||
2. $t\_c = \mid \frac{t\_{now}}{W} \mid $, $m\_c = t\_c \mathbb{Z}\_{(2)}$
|
||
3. $t'\_s = t\_c - \parallel m\_c - m\_s\parallel$
|
||
4. $T = x\_1 \oplus h(t'\_s)$
|
||
5. Find $N \in [min ; n-min[,\ h^{N}(K) = T$.
|
||
6. $x\_1 = h^N(K) \oplus h(t\_c)$
|
||
7. Generate $(K',n',s')$ in a way where the following conditions are met :
|
||
- $[(h^N(K) \oplus h^{n'}(K')) \And 1] \ \mathbb{Z}\_{(2)} = t\_c \ \mathbb{Z}\_{(2)} $
|
||
- $[(h^N(K) \oplus h^{n'}(K')) \And 11110000] \ \mathbb{Z}\_{(3)} = 2$
|
||
8. $x\_2 = h^{n'}(K') \oplus h(t\_c)$
|
||
|
||
Send $x_1$ and $x_2$.
|
||
|
||
|
||
## blablabla
|
||
|
||
##### Check a token
|
||
|
||
When the client sends its next token $h^{n-1}(K)$, the server has to <u>hash</u> it and compare it with the last token $T$. In fact, tokens are generated according to the following property :
|
||
$$h(h^{n-1}) = h^n(K)$$
|
||
|
||
*In other words, each token is the hash of the next one.*
|
||
|
||
Because of the main property of cryptographic hash functions, the original data is *cryprographically hard* to find from its *digest* (*i.e. the hashed data*). Since the next token is always the digest of the previous one (and not the opposite), an attacker has no clue about the next token.
|
||
|
||
**Limitations** : <span style='color:#f01800;'>It seams obvious that there is weaknesses due to hashing recursively a single data, but I do not know if such attack is known or even works.</span>
|
||
|
||
##### Time Scrambling algorithm
|
||
|
||
In order for the requests/responses to be only valid a few seconds in time, the tokens are scrambled using a [one-time pad](https://en.wikipedia.org/wiki/One-time_pad).
|
||
|
||
The sender processes the data as follows:
|
||
|
||
| Step | Description | Formula |
|
||
|:----:|:-----------|:-------|
|
||
| 1 | Process the sender's time id | $t\_s = \mid \frac{t_{now}^s}{W} \mid $ |
|
||
| 2 | process the sender's time parity | $ m_s = t_s \mod 2$ |
|
||
| 3 | Send the time parity | $ Send\ m_s$ |
|
||
|
||
The receiver has to guess $t_s$ with the following steps:
|
||
|
||
| Step | Description | Formula |
|
||
|:----:|:-----------|:-------|
|
||
| 1 | receive the sender's time parity | $Receive \ m_s$ |
|
||
| 2 | process the receiver's time id | $t\_r = \mid \frac{t_{now}^r}{W} \mid $ |
|
||
| 3 | process the receiver's time parity | $m_r = t_r \mod 2$ |
|
||
| 4 | Try to guess the sender's time id | $t'_s2 = t_r - \parallel m_s - m_r \parallel $ |
|
||
|
||
As a result, the receiver will only be able to retrieve the sender's time id if the reception time is at most $W$ seconds after the sending time :
|
||
|
||
$$t\_{now}^r \in [t\_{now}^s ; t\_{now}^s + W] \Longrightarrow t\_s = t'_s2$$
|
||
|
||
In practice, the time id is **hashed** and used to achieve a [one-time pad](https://en.wikipedia.org/wiki/One-time_pad) with the token. Because they both result from the same hash function their sizes are the same.
|
||
|
||
As a result from the sent token $x_1 = token \oplus h(t_s)$, it is impossible to guess neither the token nor $h(t_s)$ without the other.
|
||
|
||
**Explanation**
|
||
* The time id corresponds to the index of the time slice where slices are $W$ seconds wide. By dividing the time in slices of $W$ seconds, if we process the same calculation at an interval of $W$ or less seconds, we will have either the same result or a result greater by 1.
|
||
* The time id parity $m_s$ allows us to adjust the receiver's time id $t_r$ when it receives the request. This difference of 1 second is caused by the division of time in slices, the precision is also divided by $W$.
|
||
|
||
- $t\_{now}\mod W = 0 \Longrightarrow \mid \frac{t\_{now}}{W} \mid = \mid \frac{t\_{now+(W-1)}}{W} \mid $; no need for adjustment
|
||
- $t\_{now}\mod W = 1 \Longrightarrow \mid \frac{t\_{now}}{W} \mid = \mid \frac{t\_{now+(W-1)}}{W} \mid + 1 $; need to subtract $1$
|
||
- $t\_{now}\mod W = 2 \Longrightarrow \mid \frac{t\_{now}}{W} \mid = \mid \frac{t\_{now+(W-1)}}{W} \mid + 1 $; need to subtract $1$
|
||
- $...$
|
||
- $t\_{now}\mod W = (W-1) \Longrightarrow \mid \frac{t\_{now}}{W} \mid = \mid \frac{t\_{now}+(W-1)}{W} \mid + 1 $; need to subtract $1$
|
||
|
||
## Protocol properties
|
||
|
||
##### (1) Token Chain
|
||
|
||
As a result, <u>each token is the digest* of its predecessor.</u>
|
||
>_* : the output of the hash function._
|
||
|
||
This property allows the server to easily check if a token is valid by comparing it with the previous one $T$. It just has to check if the digest of the token is equal to the previous one.
|
||
|
||
As a result, <u>each token is bound to the previous one</u>. If a request fails (*e.g. network issue*) and a token is lost, the next one won't be valid.
|
||
|
||
|
||
##### (2) Time Limitation
|
||
|
||
Any request received more than $W$ seconds after being sent is invalidated by the server. It protects against manual, slow and process-intensive request forgery. Also a same token is *never* observed over the network.
|
||
|
||
##### (3) Token unicity
|
||
|
||
The clients never sends a token more than once. It avoids attackers to reuse tokens. Note that usually the client <u>must not generate</u> a same token twice. The keyset $(K, n, s)$ must never be used twice to generate tokens.
|
||
|
||
##### (4) One-way Hash Chain
|
||
|
||
For a given key, <u>every token sent has a lower nonce</u> $n$ than every other that has already been sent. In that way it gives no clue about the next tokens to be used.
|
||
|
||
|
||
##### Constraints
|
||
|
||
- The server must be able to recover the token if the data is received within the time window.
|
||
- If the window expired, the token must be invalidated by the server.
|
||
- The server must be able to validate a token if it holds the previous one.
|
||
- The server must invalidate a token if it has not the previous one.
|
||
- The client must be able to recover the authentication with a challenge.
|
||
|
||
|
||
### Limitations
|
||
|
||
- If an arbitrary catches, then blocks a request and sends it afterwards, it will be authenticated. This case is equivalent to being the client (with all secret variables), which can *never* occur if you use TLS. Notice that you won't be able to extract anything from the token anyway.
|
||
- With requests meta data (*e.g. HTTP headers containing the date*), an attacker knowing $W$ can forge the time hash $h(t_C)$ and be able to recover the private key $K$ by processing a simple *XOR* on the public token. Because the cyclic-hash algorithm generates a unique pseudo-random token from $K$ for each request, this case does not give the attacker any clue about the next token to be sent.
|
||
|
||
|
||
|
||
## Protocol definition
|
||
|
||
Each request will hold a <u>pair of tokens</u> $(x_1, x_2)$. If the server's check fails (*i.e. the client is not authenticated*), it will send back to the client a <u>pair of tokens</u> $(y_1, y_2)$ for resynchronization purposes.
|
||
|
||
Each token has a size defined by the *hash function*, which is `sha512` for this specification ; the size is then 512 bits.
|
||
|
||
> For readability purposes, the tokens are sent as their hexadecimal representation ; each token is then a string of 128 characters.
|
||
|
||
### 1. Send the request
|
||
|
||
This case is the default where $i$ is far from $1$ so there is no key generation or synchronization to do.
|
||
|
||
| Step | Description | Formula |
|
||
|:----:|-------------|:--------|
|
||
| `c1` | Decrement the shifting nonce | $i = i - 1$ |
|
||
| `c2` | Calculate the one-time token | $T_c = h^{i}(K)$ |
|
||
| `c3` | Get the current time id | $t\_c =\ \mid \frac{t\_{now}}{W} \mid$ |
|
||
| `c4` | Calculate the parity of $t_c$ | $m_c = t_c \mod 2$ |
|
||
| `c5` | Calculate the time hash $h_{t_c}$ | $h_{t_c} = h(t_c)$ |
|
||
| `c6` | Calculate $x_1$, the scrambled *request token* | $x\_1 = T\_c \oplus h\_{t\_c}$ |
|
||
| `c7` | Calculate $x_2$, the scrambled *next token* | $x_2 = x_1 \oplus m_c$ |
|
||
|
||
**Security** : We process a one-time pad between $T_c$ and $h_{t_c}$ it is crucial that both values have the same size of $L$ bits. It makes $T_c$ impossible to extract without having the value $h_{t_c}$, this property applies in both ways.
|
||
|
||
|
||
**Short formulas**
|
||
|
||
| Field | Short formula |
|
||
| :-----------: | ------------------------------------------------ |
|
||
| $t_c$ | $\mid\frac{t_{now}}{W}\mid$ |
|
||
| $x_1$ | $h^{i}(K) \oplus h(t_c)$ |
|
||
| $x_2$ | $x_1 \oplus (t_c \mod 2)$ |
|
||
|
||
Send $(x_1, x_2)$.
|
||
|
||
|
||
### 2. Receive the request
|
||
|
||
The server receives $(x_1, x_2)$. It does have to :
|
||
1. Guess the client's time id to un-scramble the token
|
||
2. Check the token
|
||
|
||
| Step | Description | Formula |
|
||
|:----:|-------------|:--------|
|
||
| `s1` | Extract the client's time parity | $m_c = x_1 \oplus x_2$ |
|
||
| `s2` | Get the current time id | $t\_s =\ \mid \frac{t_{now}}{W} \mid$ |
|
||
| `s3` | Calculate the parity of $t_s$ | $m_s = t_s \mod 2$ |
|
||
| `s4` | Guess the client's time id | $t'_c = t_s - \parallel m_s - m_c \parallel$ |
|
||
| `s5` | Calculate the time hash $h_{t'_c}$ | $h_{t'_c} = h(t'_c)$ |
|
||
| `s6` | Extract the token $T'_c$ | $T'\_c = x_1 \oplus h\_{t'\_c}$ |
|
||
| `s7` | Extract the next token $T_{next}$ | $T\_{next} = x\_2 \oplus m\_c \oplus h\_{t'\_c}$ |
|
||
| `s8` | Check the token | $h(T'\_c) = T \Longrightarrow T = T_{next}$ |
|
||
|
||
If the token is valid on step `s8`, the next validation token $T_{next}$ is stored and the request can be processed by the server. If the token mismatches, the recovery mode is enabled.
|
||
|
||
**Short formulas**
|
||
|
||
The whole unscrambling process can be shortened into the following formula resulting in $0$ if the client is authenticated.
|
||
|
||
$$x\_1 \oplus h(\mid \frac{t\_{now}}{W}\mid - \parallel (x\_1 \oplus x\_2) - (\mid \frac{t\_{now}}{W}\mid \mod 2) \parallel) \oplus T$$
|
||
|
||
| Step | Description | Formula |
|
||
|:----:|-------------|:--------|
|
||
| `rs1` | Get the current time id | $t\_s = \mid \frac{t\_{now}}{W} \mid$ |
|
||
| `rs2` | Calculate the parity of $t_s$ | $m_s = t_s \mod 2$ |
|
||
| `rs3` | Calculate the time hash $h_{t_s}$ | $h_{t_s} = h(t_s)$ |
|
||
| `rs4` | Calculate $y_1$, the recovery hash | $y\_1 = T \oplus h\_{t_s}$ |
|
||
| `rs5` | Calculate $y_2$, used for parity | $y_2 = y_1 \oplus m_s$ |
|
||
|
||
Send $(y_1, y_2)$.
|
||
|
||
|