Skip to content

Commit

Permalink
Merge branch 'develop' into issues/318-digest-len
Browse files Browse the repository at this point in the history
  • Loading branch information
petrdvorak authored Dec 9, 2019
2 parents bd4b896 + 5c7f16f commit 465bc09
Show file tree
Hide file tree
Showing 29 changed files with 1,046 additions and 184 deletions.
14 changes: 11 additions & 3 deletions docs/Activation-Status.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Following sequence diagram shows the activation status check in more detail.
When obtaining the activation status, application receives the binary status blob. Structure of the 32B long status blob is following:

```
0xDEC0DED1 1B:${STATUS} 1B:${CURRENT_VERSION} 1B:${UPGRADE_VERSION} 6B:${RESERVED} 1B:${FAIL_COUNT} 1B:${MAX_FAIL_COUNT} 1B:${CTR_LOOK_AHEAD} 16B:${CTR_DATA}
0xDEC0DED1 1B:${STATUS} 1B:${CURRENT_VERSION} 1B:${UPGRADE_VERSION} 5B:${RESERVED} 1B:${CTR_BYTE} 1B:${FAIL_COUNT} 1B:${MAX_FAIL_COUNT} 1B:${CTR_LOOK_AHEAD} 16B:${CTR_DATA_HASH}
```

where:
Expand All @@ -60,9 +60,17 @@ where:
- `0x02` - PowerAuth protocol version `2.x`
- `0x03` - PowerAuth protocol version `3.x`
- `${UPGRADE_VERSION}` - 1 byte representing maximum protocol version supported by the PowerAuth Server. The set of possible values is identical to `${CURRENT_VERSION}`
- `${RESERVED}` - 6 bytes reserved for the future use.
- `${RESERVED}` - 5 bytes reserved for the future use.
- `${CTR_BYTE}` - 1 byte representing least significant byte from current value of counter, calculated as:
```java
byte CTR_BYTE = (byte)(CTR & 0xFF);
```
- `${FAIL_COUNT}` - 1 byte representing information about the number of failed attempts at the moment.
- `${MAX_FAIL_COUNT}` - 1 byte representing information about the maximum allowed number of failed attempts.
- `${CTR_LOOK_AHEAD}` - 1 byte representing constant for a look ahead window, used on the server to validate the signature.
- `${CTR_DATA}` - 16 bytes containing current value of hash-based counter.
- `${CTR_DATA_HASH}` - 16 bytes containing hash from current value of hash-based counter:
```java
SecretKey KEY_TRANSPORT_CTR = KDF.derive(KEY_TRANSPORT, 4000);
byte[] CTR_DATA_HASH = KeyConversion.getBytes(KDF_INTERNAL.derive(KEY_TRANSPORT_CTR, CTR_DATA));
```

2 changes: 1 addition & 1 deletion docs/Architecture-Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ As you can see, there are only couple components present:

### Our Back-End Components

PowerAuth Deplyment requires installation of just one easy to deploy component.
PowerAuth Deployment requires installation of just one easy to deploy component.

- PowerAuth Server
- The core server component that manages activated devices, enables the activation process, verifies signatures and keeps system configuration.
Expand Down
14 changes: 7 additions & 7 deletions docs/Basic-definitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ Following basic cryptography algorithms and parameters are used in the PowerAuth
- **PBKDF2** - An algorithm for key stretching, converts a short password into long key by performing repeated hash iteration on the original data, HMAC-SHA1 algorithm is used for a pseudo-random function. Implementations must make sure resulting key is converted in format usable by AES algorithm. One method is defined for this algorithm:
- `SecretKey expandedKey = PBKDF2.expand(char[] password, byte[] salt, long iterations, long lengthInBits)` - stretch the password using given number of iterations to achieve key of given length in bits, use given salt.

- **X9.63 (with SHA256)** - A standard KDF function based on X9.63, with SHA256 as an internal hash function. It uses iterations of SHA256 hash function to derive a key of expected length of 32B.
- `byte[] bytes = KDF_X9_63_SHA256.derive(byte[] secret, byte[] info)`
- **X9.63 (with SHA256)** - A standard KDF function based on X9.63, with SHA256 as an internal hash function. It uses iterations of SHA256 hash function to derive a key of expected `length` of bytes.
- `byte[] bytes = KDF_X9_63_SHA256.derive(byte[] secret, byte[] info, int length)`

- **ECDSA** - An algorithm for elliptic curve based signatures, uses SHA256 hash algorithm. It defines two operations:
- **ECDSA** - An algorithm for elliptic curve based signatures, uses SHA256 hash algorithm and P256r1 EC curve. It defines two operations:
- `byte[] signature = ECDSA.sign(byte[] data, PrivateKey privateKey)` - compute signature of given data and private key.
- `boolean isValid = ECDSA.verify(byte[] data, byte[] signature, PublicKey publicKey)` - verify the signature for given data using a given public key.

Expand Down Expand Up @@ -51,12 +51,12 @@ These functions are used in the pseudo-codes:
- `String randomBase32 Generator.randomBase32String(int N)` - Generate string in Base32 encoding with N characters using a secure random generator.
- `String uuid = Generator.randomUUID()` - Generate a new UUID level 4 and return it in string representation.
- `String code = Generator.randomActivationCode()` - Generate a new `ACTIVATION_CODE`. See [Activation Code](./Activation-Code.md) for more details.
- `String code = Generator.buildActivationCode(byte[10] randomBytes)` - Function return an activation code from given random data.
- `String code = Generator.buildActivationCode(byte[10] randomBytes)` - Function return an activation code from given random data.

- Hashing and MAC functions.
- `byte[] signature = Mac.hmacSha256(SecretKey key, byte[] message)` - Compute HMAC-SHA256 signature for given message using provided symmetric key.
- `byte[] hash = Hash.sha256(byte[] original)` - Compute SHA256 hash of a given input.

- Password hashing.
- `String hash = PasswordHash.hash(byte[] password)` - Compute Argon2 hash for given password. Hash is stored in Modular Crypt Format.
- `boolean matches = PasswordHash.verify(byte[] password, String hash)` - Verify password against Argon2 hash stored in Modular Crypt Format.
Expand All @@ -67,6 +67,6 @@ These functions are used in the pseudo-codes:
- `int integer = ByteUtils.getInt(byte[4] bytes)` - Get integer from 4 byte long byte array.
- `long value = ByteUtils.getLong(byte[8] bytes)` - Get long value from 8 byte long byte array.
- `byte[] result = ByteUtils.concat(byte[] a, byte[] b)` - Concatenate two byte arrays - append `b` after `a`.
- `byte[] result = ByteUtils.convert32Bto16B(byte[] bytes32, byte[] b)` - Converts 32b long byte array to 16b long array by xor-ing the first 16b with the second 16b, byte-by-byte.
- `byte[] result = ByteUtils.convert32Bto16B(byte[] bytes32)` - Converts 32b long byte array to 16b long array by xor-ing the first 16b with the second 16b, byte-by-byte.
- `byte[] result = ByteUtils.subarray(byte[] bytes, int startIndex, int length)` - Obtain subarray of a byte array, starting with index `startIndex` with a given length.
- `ByteUtils.copy(byte[] source, int sourcePosition, byte[] destination, int destinationPosition, int length)` - Copies `length` of bytes from the specified source array of bytes, beginning at the specified position, to the specified position of the destination array.
77 changes: 42 additions & 35 deletions docs/End-To-End-Encryption.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Standard ECIES Based End-to-End Encryption

PowerAuth supports a standard ECIES encryption (integrated encryption scheme that uses elliptic curve cryptography) with the standard X9.63 (SHA256) KDF function (that produces 32b long keys).
PowerAuth supports a standard ECIES encryption (integrated encryption scheme that uses elliptic curve cryptography) with the P256r1 curve and standard X9.63 (SHA256) KDF function (that produces 32B long keys).

### ECIES Encryption

Expand All @@ -12,67 +12,73 @@ Assume we have a public key `KEY_ENC_PUB`, data `DATA_ORIG` to be encrypted and
```java
EPH_KEYPAIR = (KEY_EPH_PRIV, KEY_EPH_PUB).
```
2. Derive base secret key (in this step, we do not trim the key to 16b only, we keep all 32b).
1. Derive base secret key (in this step, we do not trim the key to 16b only, we keep all 32b).
```java
SecretKey KEY_BASE = ECDH.phase(KEY_EPH_PRIV, KEY_ENC_PUB)
```
3. Derive a secret key using X9.63 KDF function (using SHA256 internally). When calling the KDF, we use `SHARED_INFO_1` together with `KEY_EPH_PUB` value (as raw `byte[]`) as an `info` parameter.
1. Derive a secret key using X9.63 KDF function (using SHA256 internally). When calling the KDF, we use `SHARED_INFO_1` together with `KEY_EPH_PUB` value (as raw `byte[]`) as an `info` parameter.
```java
byte[] INFO = Bytes.concat(SHARED_INFO_1, KEY_EPH_PUB);
SecretKey KEY_SECRET = KDF_X9_63_SHA256.derive(KEY_BASE, INFO)
SecretKey KEY_SECRET = KDF_X9_63_SHA256.derive(KEY_BASE, INFO, 48)
```
4. Split the 32 bytes long `KEY_SECRET` to two 16B keys. The first part is used as an encryption key `KEY_ENC`. The second part is used as MAC key `KEY_MAC`.
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
byte[] KEY_SECRET_BYTES = KeyConversion.getBytes(KEY_SECRET);
SecretKey KEY_ENC = KeyConversion.secretKeyFromBytes(ByteUtils.subarray(KEY_SECRET, 0, 16));
SecretKey KEY_MAC = KeyConversion.secretKeyFromBytes(ByteUtils.subarray(KEY_SECRET, 16, 16));
SecretKey KEY_IV = KeyConversion.secretKeyFromBytes(ByteUtils.subarray(KEY_SECRET, 32, 16));
```
5. Compute the encrypted data using AES, with zero `iv` value.
1. Generate random `NONCE` and derive `IV` for encryption.
```java
byte[] iv = ByteUtils.zeroBytes(16);
byte[] DATA_ENCRYPTED = AES.encrypt(DATA_ORIG, iv, KEY_ENC)
byte[] NONCE = Generator.randomBytes(16);
byte[] IV = KDF_INTERNAL.derive(KEY_IV, NONCE);
```
6. Compute the MAC of encrypted data, include `SHARED_INFO_2`.
1. Compute the encrypted data using AES, with `iv` value.
```java
byte[] DATA_ENCRYPTED = AES.encrypt(DATA_ORIG, 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)
```
7. Prepare ECIES payload.
1. Prepare ECIES payload.
```java
EciesPayload payload = (DATA_ENCRYPTED, MAC, KEY_EPH_PUB)
EciesPayload payload = (DATA_ENCRYPTED, MAC, KEY_EPH_PUB, NONCE)
```

### ECIES Decryption

Assume we have a private key `KEY_ENC_PRIV`, encrypted data as an instance of the ECIES payload `(DATA_ENCRYPTED, MAC, KEY_EPH_PUB)` and a `SHARED_INFO_1` and `SHARED_INFO_2` constants (`byte[]`) as decryption parameters. ECIES decryption works in a following way:
Assume we have a private key `KEY_ENC_PRIV`, encrypted data as an instance of the ECIES payload `(DATA_ENCRYPTED, MAC, KEY_EPH_PUB, NONCE)` and a `SHARED_INFO_1` and `SHARED_INFO_2` constants (`byte[]`) as decryption parameters. ECIES decryption works in a following way:

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)
```
2. Derive a secret key using X9.63 KDF function (using SHA256 internally). When calling the KDF, we use `KEY_EPH_PUB` value (as raw `byte[]`) as an `info` parameter.
1. Derive a secret key using X9.63 KDF function (using SHA256 internally). When calling the KDF, we use `KEY_EPH_PUB` value (as raw `byte[]`) as an `info` parameter.
```java
byte[] INFO = Bytes.concat(SHARED_INFO_1, KEY_EPH_PUB);
SecretKey KEY_SECRET = KDF_X9_63_SHA256.derive(KEY_BASE, INFO)
SecretKey KEY_SECRET = KDF_X9_63_SHA256.derive(KEY_BASE, INFO, 48)
```
3. Split the 32 bytes long `KEY_SECRET` to two 16B keys. The first part is used as an encryption key `KEY_ENC`. The second part is used as MAC key `KEY_MAC`.
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
byte[] KEY_SECRET_BYTES = KeyConversion.getBytes(KEY_SECRET);
SecretKey KEY_ENC = KeyConversion.secretKeyFromBytes(ByteUtils.subarray(KEY_SECRET_BYTES, 0, 16));
SecretKey KEY_MAC = KeyConversion.secretKeyFromBytes(ByteUtils.subarray(KEY_SECRET_BYTES, 16, 16));
SecretKey KEY_IV = KeyConversion.secretKeyFromBytes(ByteUtils.subarray(KEY_SECRET_BYTES, 32, 16));
```
4. Validate the MAC value in payload against expected MAC value. Include `SHARED_INFO_2`. If the MAC values are different, terminate the decryption.
1. Validate the MAC value in payload against expected MAC value. Include `SHARED_INFO_2`. If the MAC values are different, terminate the decryption.
```java
byte[] DATA = Bytes.concat(DATA_ENCRYPTED, SHARED_INFO_2);
byte[] MAC_EXPECTED = Mac.hmacSha256(KEY_MAC, DATA);
if (MAC_EXPECTED != MAC) {
throw EciesException("Invalid MAC"); // terminate the validation with an error
}
```
5. Decrypt the data using AES, with zero `iv` value.
1. Decrypt the data using AES, with `IV` value derived from `NONCE`.
```java
byte[] iv = ByteUtils.zeroBytes(16);
byte[] DATA_ORIG = AES.decrypt(DATA_ENCRYPTED, iv, KEY_ENC)
byte[] IV = KDF_INTERNAL.derive(KEY_IV, NONCE);
byte[] DATA_ORIG = AES.decrypt(DATA_ENCRYPTED, IV, KEY_ENC)
```

### Client-Server Implementation
Expand All @@ -96,41 +102,43 @@ public class EciesPayload {
private byte[] encryptedData;
private byte[] mac;
private byte[] ephemeralPublicKey;
private byte[] nonce;
}
```

The typical JSON encoded request is following:

```json
{
"ephemeralPublicKey" : "MSUNfS0VZX3JhbmRvbQNESF9QVUJMSUNfS0VZX3JhbmRvbQNESF9QVUJ==",
"encryptedData" : "19gyYaW5ZhdGlvblkb521fYWN0aX9JRaAhbG9duZ==",
"mac" : "QNESF9QVUJMSUNfS0VZX3JhbmRvbQ=="
"ephemeralPublicKey" : "A5Iuit2vV1zgLb/ewROYGEMWxw4zjSoM2e2dO6cABY78",
"encryptedData" : "7BzoLuLYKZrfFfhlom1zMA==",
"mac" : "JpDckCpQ6Kh/gGCdBZQSh11x38EaU/DL2r/2BCXohMI=",
"nonce" : "v1y015uEP5RuT2g9RS6LIw=="
}
```

The JSON response is similar, but without "ephemeralPublicKey" field:
The JSON response is similar, but without `ephemeralPublicKey` and `nonce` fields:

```json
{
"encryptedData" : "19gyYaW5ZhdGlvblkb521fYWN0aX9JRaAhbG9duZ==",
"mac" : "QNESF9QVUJMSUNfS0VZX3JhbmRvbQ=="
"encryptedData" : "Q/7Pu29LRw5ymqkqVx+6IQ==",
"mac" : "oBPnpQ1r6YU4VtMB8sKEX4uXqdGGNzCnyLSCQrg659E="
}
```

## ECIES Scopes

PowerAuth protocol defines two basic usage scopes for ECIES encryption:

- In "application scope", ECIES encryption is available for a whole PowerAuth Client lifetime. In other words, your application can use this mode anytime in its lifetime.
- In "application scope", ECIES encryption is available for a whole PowerAuth Client lifetime. In other words, your application can use this mode anytime in its lifetime.
- In "activation scope", ECIES encryption is available once the PowerAuth Client has a valid activation. In this mode, the encryptor is cryptographically bound to keys exchanged during the activation process.

### Application scope

ECIES in application scope has following configuration of parameters:

- `KEY_ENC_PUB` is `KEY_SERVER_MASTER_PUBLIC`
- `SHARED_INFO_1` is a pre-shared constant and is different for each endpoint (see [Pre-shared constants](#pre-shared-constants))
- `SHARED_INFO_1` is a pre-shared constant and is different for each endpoint (see [Pre-shared constants](#pre-shared-constants))
- `SHARED_INFO_2` is calculated from `APPLICATION_SECRET`:
```java
byte[] SHARED_INFO_2 = Hash.sha256(APPLICATION_SECRET);
Expand All @@ -148,26 +156,26 @@ X-PowerAuth-Encryption: PowerAuth version="3.1", application_key="UNfS0VZX3JhbmR
ECIES in activation scope has following configuration of parameters:

- `KEY_ENC_PUB` is `KEY_SERVER_PUBLIC` (e.g. key which is unique for each activation)
- `SHARED_INFO_1` is a pre-shared constant and is different for each endpoint (see [Pre-shared constants](#pre-shared-constants))
- `SHARED_INFO_1` is a pre-shared constant and is different for each endpoint (see [Pre-shared constants](#pre-shared-constants))
- `SHARED_INFO_2` is calculated from `APPLICATION_SECRET` and `KEY_TRANSPORT`:
```java
byte[] SHARED_INFO_2 = Mac.hmacSha256(KEY_TRANSPORT, APPLICATION_SECRET);
```

*Note that the `APPLICATION_SECRET` constant is in Base64 form, so we need to reinterpret that string as a sequence of ASCII encoded bytes.*

HTTP header example:
```
X-PowerAuth-Encryption: PowerAuth version="3.1", application_key="UNfS0VZX3JhbmRvbQ==", activation_id="c564e700-7e86-4a87-b6c8-a5a0cc89683f"
```
Note, that the header must not be added to the request, when ECIES encryption is combined with [PowerAuth Signature](./Computing-and-Validating-Signatures.md).

### Pre-shared constants

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` |
Expand All @@ -177,8 +185,7 @@ PowerAuth protocol defines following `SHARED_INFO_1` (also called as `sh1` or `s

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` |

Loading

0 comments on commit 465bc09

Please sign in to comment.