End-To-End Encryption
This document describes PowerAuth End-To-End Encryption.
The End-to-End encryption is used for sending encrypted data to the server and back to the client in a request-response cycle.
PowerAuth End-to-End encryption consists of two phases:
- Temporary shared secret establishment - the client and server agree on shared key
KEY_TEMPORARY_SHARED_SECRET, the actual process depends on the selected cryptographic algorithm suite. - AEAD encryption - the client encrypts the request, sends to the server; the server encrypts the response and sends it to the client.
Both client and server use the same encryption key for authenticated symmetric encryption. See the chapters below for details.
Temporary Encryption Keys
A temporary encryption key is identified by its identifier, for which the UUID format is used. The temporary key is stored on the server in the database together with its identifier, shared key KEY_TEMPORARY_SHARED_SECRET (encrypted) and expiration timestamp.
A single temporary key may be reused for multiple encrypted request/response cycles until it expires (the default expiration period is 5 minutes).
After the temporary key expiration is reached, both client and server must discard the temporary key. This mechanism provides replay protection and forward secrecy.
Forward Secrecy
Forward secrecy is achieved by using temporary shared secrets.
Because KEY_TEMPORARY_SHARED_SECRET is derived when requesting the temporary key from the server and discarded after temporary key expiration, compromise of long-term application or activation keys does not expose traffic outside the temporary key lifetime.
Basic Definitions
Constants
VERSION– String with current protocol version (for example"4.0")APPLICATION_KEY– Application keyACTIVATION_ID– Optional activation identifierAPPLICATION_SECRET– Application secret (Base64-encoded string, must be decoded to raw bytes before use)SHARED_INFO_1– Endpoint-specific pre-shared constantTEMPORARY_KEY_ID– UUID identifying the temporary keyKEY_TEMPORARY_SHARED_SECRET– Temporary shared secret derived via KEM
Variables
PLAINTEXT– Data to be encryptedASSOCIATED_DATA– Plaintext data authenticated by AEADTIMESTAMP– Unix timestamp in millisecondsNONCE– 24 bytes long nonce (12 byteREQUEST_NONCEconcatenated with 12 byteRESPONSE_NONCE)
Encryption Scopes
PowerAuth defines two scopes for End-To-End encryption:
- Application scope
- Encryption is available without activation (non-personalized use case)
- Activation scope
- Encryption is bound to a specific activation (personalized use case)
Both scopes use identical cryptographic primitives but differ in how ASSOCIATED_DATA and SHARED_INFO_2 are constructed.
Temporary Shared Secret Establishment
Before encrypting any request, client and server must establish a temporary shared secret.
This is done using Create New Temporary Key endpoint in the REST API with KEM-based key exchange.
Request
{
"applicationKey": "4QSaLlQ/GphFOQeB2mnOYQ==",
"activationId": "3b09d6fd-9640-4731-bc99-8324672f4b27",
"challenge": "Iv6oBttCKqODNy2DPoYCDQ==",
"sharedSecretRequest": {
"algorithm": "EC_P384_ML_L5",
"encapsulationKeys": ["NuIkMs...", "IqWwre..."]
}
}
Supported algorithms: EC_P384, EC_P384_ML_L3, and EC_P384_ML_L5.
Response
{
"applicationKey": "4QSaLlQ/GphFOQeB2mnOYQ==",
"activationId": "3b09d6fd-9640-4731-bc99-8324672f4b27",
"challenge": "Iv6oBttCKqODNy2DPoYCDQ==",
"keyId": "dc497e8a-8faa-44bc-a52a-20d8393005d2",
"sharedSecretResponse": {
"salt": "GwHlUc4dXvDobaBlInu8gDAQd5fIZiPyYVYt69YUjYo=",
"encapsulatedKeys": [
"BIJ8Eoxi2tgxQetJ5h+Eee6K/NGjQdshSK+pul1mOUn9K++xIWU39SkcUc..."
]
},
"expiration": 1769425623112,
"serverTime": 1769425681453
}
Result:
- Server assigns
TEMPORARY_KEY_ID, stores the key in the database (with the shared secret encrypted at rest), and returns the identifier to the client. - Both sides derive the same
KEY_TEMPORARY_SHARED_SECRET. - Client stores the temporary key locally until expiration.
Each temporary key is used until expiration, after this a new temporary key is requested for additional encryption request/response cycles.
SHARED_INFO_2
SHARED_INFO_2 binds encryption to application or activation-level secrets.
Note: APPLICATION_SECRET is provided as Base64 and must be converted to raw bytes before use.
Application scope:
byte[] SHARED_INFO_2 = Hash.sha3_256(APPLICATION_SECRET);
Activation scope:
byte[] SHARED_INFO_2 = Mac.kmac256(KEY_E2EE_SHARED_INFO2, APPLICATION_SECRET, 32, "PA4SH2");
ASSOCIATED_DATA
ASSOCIATED_DATA is transmitted in plaintext and authenticated by AEAD.
| Scope | Calculation |
|---|---|
| Application | ByteUtils.concatWithSizes(VERSION, APPLICATION_KEY, TEMPORARY_KEY_ID) |
| Activation | ByteUtils.concatWithSizes(VERSION, APPLICATION_KEY, ACTIVATION_ID, TEMPORARY_KEY_ID) |
The same value is used for request and response.
Structure of Encrypted Payload
Encrypted Request
Fields:
temporaryKeyIdencryptedDatanoncetimestamp
Encrypted Response
Fields:
encryptedDatatimestamp
The response does not include nonce or temporaryKeyId, as both are already known from the request context.
AEAD Encryption (Client Request)
Assume we have:
KEY_TEMPORARY_SHARED_SECRETPLAINTEXTASSOCIATED_DATASHARED_INFO_1SHARED_INFO_2
Steps
- Generate timestamp:
long TIMESTAMP = Time.getTimestamp();
byte[] TIMESTAMP_BYTES = ByteUtils.encode(TIMESTAMP);
- Generate nonces:
byte[] REQUEST_NONCE = Generator.randomBytes(12);
byte[] RESPONSE_NONCE = Generator.randomBytes(12);
byte[] NONCE = Bytes.concat(REQUEST_NONCE, RESPONSE_NONCE);
byte[] IV = REQUEST_NONCE;
Client must ensure that both request and response nonces are unique and must maintain per-temporary-key history to prevent nonce reuse. Both request and response nonces must never be reused with the same temporary key.
- Build Key Context and AEAD Associated Data:
byte[] KC = Bytes.concat(VERSION, SHARED_INFO_1, NONCE);
byte[] AD = Bytes.concat(
ASSOCIATED_DATA,
ByteUtils.concatWithSizes(TIMESTAMP_BYTES, NONCE, SHARED_INFO_2)
);
- Encrypt using AEAD:
byte[] CIPHERTEXT = AEAD.seal(
KEY_TEMPORARY_SHARED_SECRET,
KC,
IV,
AD,
PLAINTEXT
);
Request Payload
{
"temporaryKeyId": "UUID",
"encryptedData": "Base64",
"nonce": "Base64",
"timestamp": "long"
}
AEAD Decryption (Server Request)
Given:
KEY_TEMPORARY_SHARED_SECRET(encryptedData, nonce, timestamp)ASSOCIATED_DATASHARED_INFO_1SHARED_INFO_2
Steps:
- Extract IV from first 12 bytes of
NONCE. - Recompute
KCandADusing same algorithm as described above. - Decrypt:
byte[] CIPHERTEXT_NONCE = AEAD.extractNonce(encryptedData);
if (!Arrays.equals(CIPHERTEXT_NONCE, IV)) {
throw new SecurityException("Invalid ciphertext nonce");
}
byte[] PLAINTEXT = AEAD.open(
KEY_TEMPORARY_SHARED_SECRET,
KC,
IV,
AD,
encryptedData
);
If authentication fails, terminate processing.
AEAD Encryption (Server Response)
For the response:
- Reuse the same
NONCE - Set
IV = RESPONSE_NONCE - Generate new
TIMESTAMP - Rebuild
ADwith response timestamp - Encrypt response payload using AEAD
Response Payload
{
"encryptedData": "l7f9qtwZ9v0uZN6Kzfxwy32hE3TQ4m1ALwuYHMbBI5cS...",
"timestamp": 1769425681453
}
Replay Protection
Server validates every encrypted request:
- Timestamp must be within ±5 minutes of server time.
NONCEmust be unique for givenTEMPORARY_KEY_ID.
Duplicates are rejected and stored values expire automatically.
Client must also validate the response timestamp and relies on AEAD authentication to ensure response freshness and integrity.
Client–Server Implementation
Client
- Obtain temporary shared secret from the temporary key.
- Encrypt request using AEAD with a temporary shared secret.
- Send encrypted payload and wait for response from the server.
- Receive encrypted response.
- Decrypt response.
Server
- Receive encrypted request.
- Look up temporary shared secret in the database using the temporary key identifier in the request.
- Validate timestamp and nonce uniqueness to avoid replay attacks.
- Decrypt request.
- Encrypt response using the same temporary shared secret.
Encrypted Request Example
{
"temporaryKeyId": "dc497e8a-8faa-44bc-a52a-20d8393005d2",
"encryptedData" : "H1nYcs4WqSC8/Vo3lZYd/wHAb8qC1yHOlzjQlkUOqmu1...",
"nonce" : "H1nYcs4WqSC8/Vo3M0VBe+RyQ1FGlw1l",
"timestamp" : 1769422877113
}
HTTP header in application scope:
X-PowerAuth-Encryption: PowerAuth version="4.0", application_key="UNfS0VZX3JhbmRvbQ=="
HTTP header in activation scope:
X-PowerAuth-Encryption: PowerAuth version="4.0", application_key="UNfS0VZX3JhbmRvbQ==", activation_id="3b09d6fd-9640-4731-bc99-8324672f4b27"
Encrypted Response Example
{
"encryptedData": "l7f9qtwZ9v0uZN6Kzfxwy32hE3TQ4m1ALwuYHMbBI5cS...",
"timestamp": 1691762307385
}
Responses do not use encryption headers.
Pre-Shared Constants
SHARED_INFO_1 is a pre-shared endpoint-specific constant ensuring key separation.
Following constants can be used for application-specific purposes:
| Purpose | Scope | Value |
|---|---|---|
| Generic application encryptor | Application | /pa/generic/application |
| Generic activation encryptor | Activation | /pa/generic/activation |
Concrete REST endpoints define their own SHARED_INFO_1 constants to ensure cryptographic separation between use cases.
List of defined constants:
| Endpoint | Scope | SHARED_INFO_1 |
|---|---|---|
| Activation layer 2 | Application | /pa/activation |
| Upgrade start | Application | /pa/upgrade/start |
| Vault unlock | Activation | /pa/vault/unlock |
| Create token | Activation | /pa/token/create |
| Change password | Activation | /pa/password/change |
| Enable biometry | Activation | /pa/biometry/add |