Skip to content

Commit

Permalink
Add note to E2E encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
petrdvorak committed Sep 11, 2024
1 parent dd2c407 commit 048ae43
Showing 1 changed file with 29 additions and 25 deletions.
54 changes: 29 additions & 25 deletions docs/End-To-End-Encryption.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ Assume we have the following constants and variables defined in our scheme:
- `KEY_EPH_PUB` - Public part of `EPH_KEYPAIR`.
- `SHARED_INFO_2` - Input parameter to MAC calculation.

### Temporary Encryption Keys

To provide required cryptographic features, such as forward secrecy, encryption uses [temporary encryption keys](./Temporary-Encryption-Keys.md) since protocol version `3.3`.

### Encryption Scope

Expand All @@ -37,17 +40,17 @@ PowerAuth protocol defines two basic usage scopes for ECIES encryption:

#### Application Scope

ECIES in application scope has following configuration of parameters:
ECIES in application scope has the following configuration of parameters:

- `KEY_ENC_PUB` is `KEY_SERVER_MASTER_PUBLIC`
- `KEY_ENC_PUB` is a [temporary key](./Temporary-Encryption-Keys.md) with given `TEMP_KEY_ID` identifier fetched from the server associated with a specific application version and signed with `KEY_SERVER_MASTER_PRIVATE` (to prove it was intended for the application scope).
- `SHARED_INFO_1` is a pre-shared constant and is different for each endpoint (see [Pre-shared constants](#pre-shared-constants))
- `SHARED_INFO_2_BASE` is calculated from `APPLICATION_SECRET`:
```java
byte[] SHARED_INFO_2_BASE = Hash.sha256(APPLICATION_SECRET);
```
- `ASSOCIATED_DATA` is calculated as:
```java
byte[] ASSOCIATED_DATA = ByteUtils.concatWithSizes(VERSION, APPLICATION_KEY);
byte[] ASSOCIATED_DATA = ByteUtils.concatWithSizes(VERSION, APPLICATION_KEY, TEMP_KEY_ID);
```

<!-- begin box warning -->
Expand All @@ -56,17 +59,17 @@ Note that the `APPLICATION_SECRET` constant is in Base64 form, so we need to rei

#### Activation Scope

ECIES in activation scope has following configuration of parameters:
ECIES in activation scope has the following configuration of parameters:

- `KEY_ENC_PUB` is `KEY_SERVER_PUBLIC` (e.g. key which is unique for each activation)
- `KEY_ENC_PUB` is a [temporary key](./Temporary-Encryption-Keys.md) with given `TEMP_KEY_ID` identifier fetched from the server associated with a specific application version and activation, and signed with `KEY_SERVER_PUBLIC` (the key which is unique for each activation, to prove it was intended for the activations cope).
- `SHARED_INFO_1` is a pre-shared constant and is different for each endpoint (see [Pre-shared constants](#pre-shared-constants))
- `SHARED_INFO_2_BASE` is calculated from `APPLICATION_SECRET` and `KEY_TRANSPORT`:
```java
byte[] SHARED_INFO_2_BASE = Mac.hmacSha256(KEY_TRANSPORT, APPLICATION_SECRET);
```
- `ASSOCIATED_DATA` is calculated as:
```java
byte[] ASSOCIATED_DATA = ByteUtils.concatWithSizes(VERSION, APPLICATION_KEY, ACTIVATION_ID);
byte[] ASSOCIATED_DATA = ByteUtils.concatWithSizes(VERSION, APPLICATION_KEY, ACTIVATION_ID, TEMP_KEY_ID);
```

<!-- begin box warning -->
Expand All @@ -75,7 +78,7 @@ Note that the `APPLICATION_SECRET` constant is in Base64 form, so we need to rei

### ECIES Encryption

Assume we have a public key `KEY_ENC_PUB`, data `PLAINTEXT` to be encrypted, `ASSOCIATED_DATA` to be included in mac calculation and a `SHARED_INFO_1` and `SHARED_INFO_2_BASE` constants (`byte[]`) as encryption parameters. ECIES encryption works in a following way:
Assume we have a public key `KEY_ENC_PUB`, data `PLAINTEXT` to be encrypted, `ASSOCIATED_DATA` to be included in MAC calculation and a `SHARED_INFO_1` and `SHARED_INFO_2_BASE` constants (`byte[]`) as encryption parameters. ECIES encryption works in the following way:

1. Generate an ephemeral key pair:
```java
Expand Down Expand Up @@ -111,16 +114,16 @@ Assume we have a public key `KEY_ENC_PUB`, data `PLAINTEXT` to be encrypted, `AS
1. Derive `IV` from `NONCE` and encrypt ata using AES.
```java
byte[] IV = KDF_INTERNAL.derive(KEY_IV, NONCE);
byte[] DATA_ENCRYPTED = AES.encrypt(PLAINTEXT, IV, KEY_ENC)
byte[] DATA_ENCRYPTED = AES.encrypt(PLAINTEXT, IV, KEY_ENC);
```
1. Compute the MAC of encrypted data, include `SHARED_INFO_2`.
```java
byte[] DATA = Bytes.concat(DATA_ENCRYPTED, SHARED_INFO_2);
byte[] MAC = Mac.hmacSha256(KEY_MAC, DATA)
byte[] MAC = Mac.hmacSha256(KEY_MAC, DATA);
```
1. Prepare ECIES payload.
```java
EciesPayload payload = (DATA_ENCRYPTED, MAC, KEY_EPH_PUB, NONCE, TIMESTAMP)
EciesPayload payload = (DATA_ENCRYPTED, MAC, KEY_EPH_PUB, NONCE, TIMESTAMP);
```

If this is a response encryption, then we omit `KEY_EPH_PUB` and set it to `null` in steps 3. and 9. to make the response shorter. For example, `SHARED_INFO_2` is then calculated as:
Expand All @@ -140,13 +143,13 @@ Assume we have a private key `KEY_ENC_PRIV`, encrypted data as an instance of th
```
1. Derive base secret key from the private key and ephemeral public key from the ECIES payload (in this step, we do not trim the key to 16b only, we keep all 32b).
```java
SecretKey KEY_BASE = ECDH.phase(KEY_ENC_PRIV, KEY_EPH_PUB)
SecretKey KEY_BASE = ECDH.phase(KEY_ENC_PRIV, KEY_EPH_PUB);
```
1. Derive a secret key using X9.63 KDF function (using SHA256 internally). When calling the KDF, we use `VERSION`, `SHARED_INFO_1` together with `KEY_EPH_PUB` value (as raw `byte[]`) as an `info` parameter.
```java
byte[] VERSION_BYTES = ByteUtils.encode(VERSION);
byte[] INFO = Bytes.concat(VERSION_BYTES, SHARED_INFO_1, KEY_EPH_PUB);
SecretKey KEY_SECRET = KDF_X9_63_SHA256.derive(KEY_BASE, INFO, 48)
SecretKey KEY_SECRET = KDF_X9_63_SHA256.derive(KEY_BASE, INFO, 48);
```
1. Split the 48 bytes long `KEY_SECRET` to three 16B keys. The first part is used as an encryption key `KEY_ENC`. The second part is used as MAC key `KEY_MAC`. The final part is a key for IV derivation `KEY_IV`.
```java
Expand All @@ -166,7 +169,7 @@ Assume we have a private key `KEY_ENC_PRIV`, encrypted data as an instance of th
1. Decrypt the data using AES, with `IV` value derived from `NONCE`.
```java
byte[] IV = KDF_INTERNAL.derive(KEY_IV, NONCE);
byte[] PLAINTEXT = AES.decrypt(DATA_ENCRYPTED, IV, KEY_ENC)
byte[] PLAINTEXT = AES.decrypt(DATA_ENCRYPTED, IV, KEY_ENC);
```

If this is a response decryption, then we omit `KEY_EPH_PUB` and set it to `null` in step 1.
Expand All @@ -175,9 +178,9 @@ If this is a response decryption, then we omit `KEY_EPH_PUB` and set it to `null

Practical implementation of ECIES encryption in PowerAuth accounts for a typical request-response cycle, since encrypting RESTful API requests and responses is the most common use-case.

Client implementation creates an encryptor object that allows encrypting the request and decrypting the response. When encrypting the request, encryptor object accepts a `byte[]` and a public key (for example, `MASTER_SERVER_PUBLIC_KEY`) and produces an instance of `EciesPayload` class. After it receives an encrypted response from the server, which is essentially another instance of `EciesPayload`, it is able to use the original encryption context (the shared encryption keys) to decrypt the response.
Client implementation creates an encryptor object that allows encrypting the request and decrypting the response. When encrypting the request, encryptor object accepts a `byte[]` and a [temporary public key](./Temporary-Encryption-Keys.md) . Then, it produces an instance of `EciesPayload` class. After it receives an encrypted response from the server, which is essentially another instance of `EciesPayload`, it is able to use the original encryption context (the shared encryption keys) to decrypt the response.

Server implementation creates a decryptor object that allows decrypting the original request data and encrypting the response. When server receives an encrypted request, essentially as an `EciesPayload` instance again, it uses a private key (for example, `MASTER_SERVER_PRIVATE_KEY`) to decrypt the original bytes and uses the encryption context to encrypt a response to the client.
Server implementation creates a decryptor object that allows decrypting the original request data and encrypting the response. When server receives an encrypted request, essentially as an `EciesPayload` instance again, it uses a [temporary private key](./Temporary-Encryption-Keys.md) (looked up based on the temporary key ID) to decrypt the original bytes and uses the encryption context to encrypt a response to the client.

Since the client and server use the same encryption context, the ephemeral public key needs to be only sent with the request from the client. Response may only contain encrypted data and MAC value.

Expand All @@ -202,6 +205,7 @@ The typical JSON encoded request is following:

```json
{
"temporaryKeyId": "dc497e8a-8faa-44bc-a52a-20d8393005d2",
"ephemeralPublicKey" : "A97NlW0JPLJfpG0AUvaRHRGSHh+quZu+u0c+yxsK7Xji",
"encryptedData" : "qYLONkDWFpXefTKPbaKTA/PWdRYH5pk9uvGjUqSYbeK7Q0aOohK2MknTyviyNuSp",
"mac" : "DNlZdsM1wgH8v2mAROjj3vmQu4DI4ZJnuTBzQMrHsew=",
Expand Down Expand Up @@ -246,18 +250,18 @@ The response doesn't use HTTP headers.

PowerAuth protocol defines following `SHARED_INFO_1` (also called as `sh1` or `sharedInfo1`) constants for its own internal purposes:

| RESTful endpoint | ECIES scope | `SHARED_INFO_1` value |
| ------------------------------------- | ------------ | --------------------- |
| RESTful endpoint | ECIES scope | `SHARED_INFO_1` value |
| ------------------------------------- | ------------ |---------------------------|
| `/pa/v3/activation/create` (level 1) | application | `/pa/generic/application` |
| `/pa/v3/activation/create` (level 2) | application | `/pa/activation` |
| `/pa/v3/upgrade` | activation | `/pa/upgrade` |
| `/pa/v3/vault/unlock` | activation | `/pa/vault/unlock` |
| `/pa/v3/token/create` | activation | `/pa/token/create` |
| `/pa/v3/recovery/confirm` | activation | `/pa/recovery/confirm` |
| `/pa/v3/activation/create` (level 2) | application | `/pa/activation` |
| `/pa/v3/upgrade` | activation | `/pa/upgrade` |
| `/pa/v3/vault/unlock` | activation | `/pa/vault/unlock` |
| `/pa/v3/token/create` | activation | `/pa/token/create` |
| `/pa/v3/recovery/confirm` | activation | `/pa/recovery/confirm` |

On top of that, following constants can be used for application-specific purposes:

| Purpose | ECIES scope | `SHARED_INFO_1` value |
| ---------------------------------------- | ------------ | --------------------- |
| Purpose | ECIES scope | `SHARED_INFO_1` value |
| ---------------------------------------- | ------------ |---------------------------|
| Generic encryptor for application scope | application | `/pa/generic/application` |
| Generic encryptor for activation scope | activation | `/pa/generic/activation` |
| Generic encryptor for activation scope | activation | `/pa/generic/activation` |

0 comments on commit 048ae43

Please sign in to comment.