Dynamic Factor Keys
Dynamic Factor Keys ensure that changes to authentication factors—such as passwords or biometric enrollment—are always reflected in freshly derived cryptographic material shared between the client and server.
Instead of relying on purely local updates, any modification of a factor (for example, changing a password or enabling/disabling biometrics) is accompanied by a server-assisted key exchange. This process results in new factor keys on both sides, effectively binding the updated factor state to newly established secrets.
This design provides two important properties:
- Password changes produce new knowledge factor keys. Each password update triggers a shared-secret exchange with the server, yielding a new knowledge factor key.
- Biometric state is synchronized with the server. Enabling or removing biometrics also derives fresh biometric factor keys and updates the activation record on the server, ensuring that biometric authentication is only possible when it is explicitly enabled.
Password change
/pa/v4/password/changeendpoint- Authenticated with “possession_knowledge”,
uriId="/pa/password/change" - E2EE, activation scope,
sh1="/pa/password/change" - Request body (before encryption):
{ "algorithm": "EC_P384_ML_L3", "ecdhe": "Base64", "mlkem": "Base64" } - Response body (before encryption):
{ "ecdhe": "Base64", "mlkem": "Base64" } - The result of shared secret calculation is a new factor key stored to
knowledge_factor_key_nexton the server side.
- Authenticated with “possession_knowledge”,
Full flow
-
User enters old and new PIN/password:
passwordOld,passwordNew - Client prepares data for the shared secret:
Pair<SharedSecretRequest, SharedSecretClientContext> requestWithContext = SharedSecret.generateRequestCryptogram(); return requestWithContext.getFirst(); -
Client encrypts
SharedSecretRequestpayload with E2EE, activation scope,sh1="/pa/password/change" -
Client sends the encrypted request to
/pa/v4/password/change. The request is authenticated with PowerAuth V4 authentication code, withpasswordOld - Server verifies authentication code, if valid, then decrypts the payload and then:
// [1] Deduce shared secret Pair<SharedSecretResponse, SecretKey> responseWithSecret = ServerSharedSecret.generateResponseCryptogram(request); // [2] Store deduced shared secret as a next knowledge factor key. activationRecord.setKnowledgeFactorKeyNext(responseWithSecret.getSecond()); // [3] Return response to the client return responseWithSecret.getFirst(); - Client receives response from the server and decrypts the response and compute new
KEY_AUTHENTICATION_CODE_KNOWLEDGE:SecretKey KEY_AUTHENTICATION_CODE_KNOWLEDGE = SharedSecret.computeSharedSecret(requestWithContext.getSecond(), response); - Store new factor key with using
passwordNew:byte[] salt = Generator.randomBytes(32); SecretKey kek = KDF.derivePassword(passwordNew, salt); byte[] C_KEY_AUTHENTICATION_CODE_KNOWLEDGE = UKE.wrap(KEY_AUTHENTICATION_CODE_KNOWLEDGE, kek); // Keep (salt, C_KEY_AUTHENTICATION_CODE_KNOWLEDGE) in persistent storage - Optional: Client can verify the knowledge authentication code by calling
/pa/v4/auth/validateauthenticated withpossession+knowledge- Reason: “CONFIRM_NEW_PASSWORD”
Biometry setup
/pa/v4/biometry/add- Authenticated with “possession_knowledge”,
uriId="/pa/biometry/add" - E2EE, activation scope,
sh1="/pa/biometry/add" - Request body (before encryption):
{ "algorithm": "EC_P384_ML_L3", "ecdhe": "Base64", "mlkem": "Base64" } - Response body (before encryption):
{ "ecdhe": "Base64", "mlkem": "Base64" }- The result of shared secret calculation is a new factor key stored to
biometric_factor_key_nexton the server side.
- The result of shared secret calculation is a new factor key stored to
- Authenticated with “possession_knowledge”,
/pa/v4/biometry/remove- Authenticated with “possession”,
uriId="/pa/biometry/remove" - Request body
{} - Response body
{}
- Authenticated with “possession”,
Full flow (add)
-
User enters current PIN/password:
password - Client prepares data for the shared secret:
Pair<SharedSecretRequest, SharedSecretClientContext> requestWithContext = SharedSecret.generateRequestCryptogram(); return requestWithContext.getFirst(); -
Client encrypts
SharedSecretRequestpayload with E2EE, activation scope,sh1="/pa/biometry/add" -
Encrypted request is authenticated with
possession+knowledge, with usingpasswordfor knowledge factor -
Client sends encrypted and authenticated Request to
/pa/v4/biometry/add. - Server verifies authentication code, if valid, then decrypts the payload and then:
// [1] Deduce shared secret Pair<SharedSecretResponse, SecretKey> responseWithSecret = ServerSharedSecret.generateResponseCryptogram(request); // [2] Update activation record and store deduced shared secret as a next biometric factor key activationRecord.setBiometricFactorEnabled(true); if (activationRecord.getBiometricFactorKey() == null) { // If current key is null, then update the current key activationRecord.setBiometricFactorKey(responseWithSecret.getSecond()); } else { // otherwise update the next key activationRecord.setBiometricFactorKeyNext(responseWithSecret.getSecond()); } // [3] Return response to the client return responseWithSecret.getFirst(); - Client receives response from the server and decrypts the response and compute new
KEY_AUTHENTICATION_CODE_BIOMETRY:SecretKey KEY_AUTHENTICATION_CODE_BIOMETRY = SharedSecret.computeSharedSecret(requestWithContext.getSecond(), response); - Store new factor key with using appropriate KEK:
// iOS: Generate new KEK and store it to the keychain item with biometric protection // Android: Generate new KEK and encrypt it with RSA, or derive new KEK by using AES KDF SecretKey kek; // byte[] C_KEY_AUTHENTICATION_CODE_BIOMETRY = UKE.wrap(KEY_AUTHENTICATION_CODE_BIOMETRY, kek); // Keep C_KEY_AUTHENTICATION_CODE_BIOMETRY in persistent storage - Optional: Client can verify the biometry authentication code by calling
/pa/v4/auth/validateauthenticated withpossession+biometry- Reason: “CONFIRM_BIOMETRY_ADD”
Full flow (remove)
- Client removes local KEK protecting
KEY_AUTHENTICATION_CODE_BIOMETRY- On iOS: Remove key from keychain
- On Android: Remove key from KeyStore
- Remove
C_KEY_AUTHENTICATION_CODE_BIOMETRYfrom the persistent storage
-
Request to
/pa/v4/biometry/removeis authenticated withpossessionfactor - Server verifies authentication code, if valid, then
// [1] Update database activationRecord.setBiometricFactorEnabled(false); activationRecord.setBiometricFactorKey(null); activationRecord.setBiometricFactorKeyNext(null);